diff options
286 files changed, 7974 insertions, 3864 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 1c8260ecb6..11959fce24 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -349,6 +349,7 @@ jobs: if: needs.ci-config.outputs.enabled == 'yes' env: CC: clang + CI_JOB_IMAGE: ubuntu-latest runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -432,6 +433,7 @@ jobs: if: needs.ci-config.outputs.enabled == 'yes' env: jobname: StaticAnalysis + CI_JOB_IMAGE: ubuntu-22.04 runs-on: ubuntu-22.04 concurrency: group: static-analysis-${{ github.ref }} @@ -446,6 +448,7 @@ jobs: if: needs.ci-config.outputs.enabled == 'yes' env: jobname: sparse + CI_JOB_IMAGE: ubuntu-22.04 runs-on: ubuntu-22.04 concurrency: group: sparse-${{ github.ref }} @@ -464,6 +467,7 @@ jobs: cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }} env: jobname: Documentation + CI_JOB_IMAGE: ubuntu-latest runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/.gitignore b/.gitignore index 08a66ca508..04c444404e 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,7 @@ /git-diff /git-diff-files /git-diff-index +/git-diff-pairs /git-diff-tree /git-difftool /git-difftool--helper diff --git a/Documentation/BreakingChanges.adoc b/Documentation/BreakingChanges.adoc index bdfad29d8a..61bdd586b9 100644 --- a/Documentation/BreakingChanges.adoc +++ b/Documentation/BreakingChanges.adoc @@ -178,6 +178,12 @@ references. + These features will be removed. +* Support for "--stdin" option in the "name-rev" command was + deprecated (and hidden from the documentation) in the Git 2.40 + timeframe, in preference to its synonym "--annotate-stdin". Git 3.0 + removes the support for "--stdin" altogether. + + == Superseded features that will not be deprecated Some features have gained newer replacements that aim to improve the design in diff --git a/Documentation/Makefile b/Documentation/Makefile index 671267a8ac..0d3a2c6bfe 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -109,6 +109,7 @@ SP_ARTICLES += howto/coordinate-embargoed-releases API_DOCS = $(patsubst %.adoc,%,$(filter-out technical/api-index-skel.adoc technical/api-index.adoc, $(wildcard technical/api-*.adoc))) SP_ARTICLES += $(API_DOCS) +TECH_DOCS += BreakingChanges TECH_DOCS += DecisionMaking TECH_DOCS += ReviewingGuidelines TECH_DOCS += MyFirstContribution @@ -509,7 +510,7 @@ lint-docs-meson: awk "/^manpages = {$$/ {flag=1 ; next } /^}$$/ { flag=0 } flag { gsub(/^ \047/, \"\"); gsub(/\047 : [157],\$$/, \"\"); print }" meson.build | \ grep -v -e '#' -e '^$$' | \ sort >tmp-meson-diff/meson.adoc && \ - ls git*.adoc scalar.adoc | grep -v -e git-bisect-lk2009.adoc -e git-tools.adoc >tmp-meson-diff/actual.adoc && \ + ls git*.adoc scalar.adoc | grep -v -e git-bisect-lk2009.adoc -e git-pack-redundant.adoc -e git-tools.adoc >tmp-meson-diff/actual.adoc && \ if ! cmp tmp-meson-diff/meson.adoc tmp-meson-diff/actual.adoc; then \ echo "Meson man pages differ from actual man pages:"; \ diff -u tmp-meson-diff/meson.adoc tmp-meson-diff/actual.adoc; \ diff --git a/Documentation/MyFirstContribution.adoc b/Documentation/MyFirstContribution.adoc index afcf4b46c1..ca1d688c9b 100644 --- a/Documentation/MyFirstContribution.adoc +++ b/Documentation/MyFirstContribution.adoc @@ -367,6 +367,7 @@ But as we drill down, we can find that `status_init_config()` wraps a call to `git_config()`. Let's modify the code we wrote in the previous commit. Be sure to include the header to allow you to use `struct wt_status`: + ---- #include "wt-status.h" ---- diff --git a/Documentation/MyFirstObjectWalk.adoc b/Documentation/MyFirstObjectWalk.adoc index d6e9dfdbbe..bfe8f5f561 100644 --- a/Documentation/MyFirstObjectWalk.adoc +++ b/Documentation/MyFirstObjectWalk.adoc @@ -287,6 +287,7 @@ static void final_rev_info_setup(struct rev_info *rev) ==== Instead of using the shorthand `add_head_to_pending()`, you could do something like this: + ---- struct setup_revision_opt opt; @@ -295,6 +296,7 @@ something like this: opt.revarg_opt = REVARG_COMMITTISH; setup_revisions(argc, argv, rev, &opt); ---- + Using a `setup_revision_opt` gives you finer control over your walk's starting point. ==== diff --git a/Documentation/RelNotes/2.50.0.adoc b/Documentation/RelNotes/2.50.0.adoc new file mode 100644 index 0000000000..1023e61a9e --- /dev/null +++ b/Documentation/RelNotes/2.50.0.adoc @@ -0,0 +1,155 @@ +Git v2.50 Release Notes +======================= + +UI, Workflows & Features +------------------------ + + * A post-processing filter for "diff --raw" output has been + introduced. + + * "git repack" learned "--combine-cruft-below-size" option that + controls how cruft-packs are combined. + + * TCP keepalive behaviour on http transports can now be configured by + calling cURL library. + + * 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. + + +Performance, Internal Implementation, Development Support etc. +-------------------------------------------------------------- + + * A handful of built-in command implementations have been rewritten + to use the repository instance supplied by git.c:run_builtin(), its + caller. + + * "git fsck" becomes more careful when checking the refs. + + * "git fast-export | git fast-import" learns to deal with commit and + tag objects with embedded signatures a bit better. + + * The code paths to check whether a refname X is available (by seeing + if another ref X/Y exists, etc.) have been optimized. + + * First step of deprecating and removing merge-recursive. + + * In protocol v2 where the refs advertisement is constrained, we try + to tell the server side not to limit the advertisement when there + is no specific need to, which has been the source of confusion and + recent bugs. Revamp the logic to simplify. + + * Update meson based build procedure for breaking changes support. + + * Enable -Wunreachable-code for developer builds. + + * Ensure what we write in assert() does not have side effects, + and introduce ASSERT() macro to mark those that cannot be + mechanically checked for lack of side effects. + + * Give more meaningful error return values from block writer layer of + the reftable ref-API backend. + + * Make the code in reftable library less reliant on the service + routines it used to borrow from Git proper, to make it easier to + use by external users of the library. + + * CI update. + + * The object layer has been updated to take an explicit repository + instance as a parameter in more code paths. + + +Fixes since v2.49 +----------------- + + * The refname exclusion logic in the packed-ref backend has been + broken for some time, which confused upload-pack to advertise + different set of refs. This has been corrected. + (merge 10e8a9352b tb/refs-exclude-fixes later to maint). + + * The merge-recursive and merge-ort machinery crashed in corner cases + when certain renames are involved. + (merge 3adba40858 en/merge-process-renames-crash-fix later to maint). + + * Certain "cruft" objects would have never been refreshed when there + are multiple cruft packs in the repository, which has been + corrected. + (merge 08f612ba70 tb/multi-cruft-pack-refresh-fix later to maint). + + * The xdiff code on 32-bit platform misbehaved when an insanely large + context size is given, which has been corrected. + (merge d39e28e68c rs/xdiff-context-length-fix later to maint). + + * GitHub Actions CI switched on a CI/CD variable that does not exist + when choosing what packages to install etc., which has been + corrected. + (merge ee89f7c79d kn/ci-meson-check-build-docs-fix later to maint). + + * Using "git name-rev --stdin" as an example, improve the framework to + prepare tests to pretend to be in the future where the breaking + changes have already happened. + (merge de3dec1187 jc/name-rev-stdin later to maint). + + * An earlier code refactoring of the hash machinery missed a few + required calls to init_fn. + (merge d39f04b638 jh/hash-init-fixes later to maint). + + * A documentation page was left out from formatting and installation, + which has been corrected. + (merge ae85116f18 pw/build-breaking-changes-doc later to maint). + + * The bash command line completion script (in contrib/) has been + updated to cope with remote repository nicknames with slashes in + them. + (merge 778d2f1760 dm/completion-remote-names-fix later to maint). + + * "Dubious ownership" checks on Windows has been tightened up. + (merge 5bb88e89ef js/mingw-admins-are-special later to maint). + + * Layout configuration in vimdiff backend didn't work as advertised, + 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). + + * 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). + (merge 5337daddc7 am/dir-dedup-decl-of-repository later to maint). + (merge 554051d691 en/diff-rename-follow-fix later to maint). + (merge a18c18b470 en/random-cleanups later to maint). + (merge 5af21c9acb hj/doc-rev-list-ancestry-fix later to maint). + (merge 26d76ca284 aj/doc-restore-p-update later to maint). + (merge 2c0dcb9754 cc/lop-remote later to maint). + (merge 7b399322a2 ja/doc-branch-markup later to maint). + (merge ee434e1807 pw/doc-pack-refs-markup-fix later to maint). + (merge c000918eb7 tb/bitamp-typofix later to maint). + (merge fa8cd29676 js/imap-send-peer-cert-verify later to maint). + (merge 98b423bc1c rs/clear-commit-marks-simplify later to maint). + (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). diff --git a/Documentation/ToolsForGit.adoc b/Documentation/ToolsForGit.adoc index ae7690b45d..a842c13327 100644 --- a/Documentation/ToolsForGit.adoc +++ b/Documentation/ToolsForGit.adoc @@ -34,6 +34,7 @@ This is adapted from Linux's suggestion in its CodingStyle document: - To follow the rules in CodingGuidelines, it's useful to put the following in GIT_CHECKOUT/.dir-locals.el, assuming you use cperl-mode: + ---- ;; note the first part is useful for C editing, too ((nil . ((indent-tabs-mode . t) diff --git a/Documentation/config/branch.adoc b/Documentation/config/branch.adoc index 432b9cd2c0..e35ea7ac64 100644 --- a/Documentation/config/branch.adoc +++ b/Documentation/config/branch.adoc @@ -1,41 +1,42 @@ -branch.autoSetupMerge:: - Tells 'git branch', 'git switch' and 'git checkout' to set up new branches +`branch.autoSetupMerge`:: + Tells `git branch`, `git switch` and `git checkout` to set up new branches so that linkgit:git-pull[1] will appropriately merge from the starting point branch. Note that even if this option is not set, this behavior can be chosen per-branch using the `--track` - and `--no-track` options. The valid settings are: `false` -- no - automatic setup is done; `true` -- automatic setup is done when the - starting point is a remote-tracking branch; `always` -- - automatic setup is done when the starting point is either a - local branch or remote-tracking branch; `inherit` -- if the starting point - has a tracking configuration, it is copied to the new - branch; `simple` -- automatic setup is done only when the starting point + and `--no-track` options. This option defaults to `true`. The valid settings + are: +`false`;; no automatic setup is done +`true`;; automatic setup is done when the starting point is a remote-tracking branch +`always`;; automatic setup is done when the starting point is either a + local branch or remote-tracking branch +`inherit`;; if the starting point has a tracking configuration, it is copied to the new + branch +`simple`;; automatic setup is done only when the starting point is a remote-tracking branch and the new branch has the same name as the - remote branch. This option defaults to true. + remote branch. -branch.autoSetupRebase:: - When a new branch is created with 'git branch', 'git switch' or 'git checkout' +`branch.autoSetupRebase`:: + When a new branch is created with `git branch`, `git switch` or `git checkout` that tracks another branch, this variable tells Git to set - up pull to rebase instead of merge (see "branch.<name>.rebase"). - When `never`, rebase is never automatically set to true. - When `local`, rebase is set to true for tracked branches of - other local branches. - When `remote`, rebase is set to true for tracked branches of - remote-tracking branches. - When `always`, rebase will be set to true for all tracking - branches. - See "branch.autoSetupMerge" for details on how to set up a - branch to track another branch. - This option defaults to never. + up pull to rebase instead of merge (see `branch.<name>.rebase`). + The valid settings are: +`never`;; rebase is never automatically set to true. +`local`;; rebase is set to true for tracked branches of other local branches. +`remote`;; rebase is set to true for tracked branches of remote-tracking branches. +`always`;; rebase will be set to true for all tracking branches. -branch.sort:: ++ +See `branch.autoSetupMerge` for details on how to set up a branch to track another branch. +This option defaults to `never`. + +`branch.sort`:: This variable controls the sort ordering of branches when displayed by - linkgit:git-branch[1]. Without the "--sort=<value>" option provided, the + linkgit:git-branch[1]. Without the `--sort=<value>` option provided, the value of this variable will be used as the default. See linkgit:git-for-each-ref[1] field names for valid values. -branch.<name>.remote:: - When on branch <name>, it tells 'git fetch' and 'git push' +`branch.<name>.remote`:: + When on branch _<name>_, it tells `git fetch` and `git push` which remote to fetch from or push to. The remote to push to may be overridden with `remote.pushDefault` (for all branches). The remote to push to, for the current branch, may be further @@ -46,58 +47,58 @@ branch.<name>.remote:: Additionally, `.` (a period) is the current local repository (a dot-repository), see `branch.<name>.merge`'s final note below. -branch.<name>.pushRemote:: - When on branch <name>, it overrides `branch.<name>.remote` for +`branch.<name>.pushRemote`:: + When on branch _<name>_, it overrides `branch.<name>.remote` for pushing. It also overrides `remote.pushDefault` for pushing - from branch <name>. When you pull from one place (e.g. your + from branch _<name>_. When you pull from one place (e.g. your upstream) and push to another place (e.g. your own publishing repository), you would want to set `remote.pushDefault` to specify the remote to push to for all branches, and use this option to override it for a specific branch. -branch.<name>.merge:: - Defines, together with branch.<name>.remote, the upstream branch - for the given branch. It tells 'git fetch'/'git pull'/'git rebase' which - branch to merge and can also affect 'git push' (see push.default). - When in branch <name>, it tells 'git fetch' the default - refspec to be marked for merging in FETCH_HEAD. The value is +`branch.<name>.merge`:: + Defines, together with `branch.<name>.remote`, the upstream branch + for the given branch. It tells `git fetch`/`git pull`/`git rebase` which + branch to merge and can also affect `git push` (see `push.default`). + When in branch _<name>_, it tells `git fetch` the default + refspec to be marked for merging in `FETCH_HEAD`. The value is handled like the remote part of a refspec, and must match a ref which is fetched from the remote given by - "branch.<name>.remote". - The merge information is used by 'git pull' (which first calls - 'git fetch') to lookup the default branch for merging. Without - this option, 'git pull' defaults to merge the first refspec fetched. + `branch.<name>.remote`. + The merge information is used by `git pull` (which first calls + `git fetch`) to lookup the default branch for merging. Without + this option, `git pull` defaults to merge the first refspec fetched. Specify multiple values to get an octopus merge. - If you wish to setup 'git pull' so that it merges into <name> from + If you wish to setup `git pull` so that it merges into <name> from another branch in the local repository, you can point branch.<name>.merge to the desired branch, and use the relative path - setting `.` (a period) for branch.<name>.remote. + setting `.` (a period) for `branch.<name>.remote`. -branch.<name>.mergeOptions:: - Sets default options for merging into branch <name>. The syntax and +`branch.<name>.mergeOptions`:: + Sets default options for merging into branch _<name>_. The syntax and supported options are the same as those of linkgit:git-merge[1], but option values containing whitespace characters are currently not supported. -branch.<name>.rebase:: - When true, rebase the branch <name> on top of the fetched branch, +`branch.<name>.rebase`:: + When true, rebase the branch _<name>_ on top of the fetched branch, instead of merging the default branch from the default remote when - "git pull" is run. See "pull.rebase" for doing this in a non + `git pull` is run. See `pull.rebase` for doing this in a non branch-specific manner. + -When `merges` (or just 'm'), pass the `--rebase-merges` option to 'git rebase' +When `merges` (or just `m`), pass the `--rebase-merges` option to `git rebase` so that the local merge commits are included in the rebase (see linkgit:git-rebase[1] for details). + -When the value is `interactive` (or just 'i'), the rebase is run in interactive +When the value is `interactive` (or just `i`), the rebase is run in interactive mode. + *NOTE*: this is a possibly dangerous operation; do *not* use it unless you understand the implications (see linkgit:git-rebase[1] for details). -branch.<name>.description:: +`branch.<name>.description`:: Branch description, can be edited with `git branch --edit-description`. Branch description is - automatically added to the format-patch cover letter or - request-pull summary. + automatically added to the `format-patch` cover letter or + `request-pull` summary. diff --git a/Documentation/config/http.adoc b/Documentation/config/http.adoc index 22a8803dea..67393282fa 100644 --- a/Documentation/config/http.adoc +++ b/Documentation/config/http.adoc @@ -296,6 +296,24 @@ http.lowSpeedLimit, http.lowSpeedTime:: Can be overridden by the `GIT_HTTP_LOW_SPEED_LIMIT` and `GIT_HTTP_LOW_SPEED_TIME` environment variables. +http.keepAliveIdle:: + Specifies how long in seconds to wait on an idle connection + before sending TCP keepalive probes (if supported by the OS). If + unset, curl's default value is used. Can be overridden by the + `GIT_HTTP_KEEPALIVE_IDLE` environment variable. + +http.keepAliveInterval:: + Specifies how long in seconds to wait between TCP keepalive + probes (if supported by the OS). If unset, curl's default value + is used. Can be overridden by the `GIT_HTTP_KEEPALIVE_INTERVAL` + environment variable. + +http.keepAliveCount:: + Specifies how many TCP keepalive probes to send before giving up + and terminating the connection (if supported by the OS). If + unset, curl's default value is used. Can be overridden by the + `GIT_HTTP_KEEPALIVE_COUNT` environment variable. + http.noEPSV:: A boolean which disables using of EPSV ftp command by curl. This can be helpful with some "poor" ftp servers which don't diff --git a/Documentation/config/maintenance.adoc b/Documentation/config/maintenance.adoc index 72a9d6cf81..42f9545da0 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, diff --git a/Documentation/config/promisor.adoc b/Documentation/config/promisor.adoc index 9192acfd24..2638b01f83 100644 --- a/Documentation/config/promisor.adoc +++ b/Documentation/config/promisor.adoc @@ -26,5 +26,5 @@ promisor.acceptFromServer:: server will be accepted. By accepting a promisor remote, the client agrees that the server might omit objects that are lazily fetchable from this promisor remote from its responses - to "fetch" and "clone" requests from the client. See - linkgit:gitprotocol-v2[5]. + to "fetch" and "clone" requests from the client. Name and URL + comparisons are case sensitive. See linkgit:gitprotocol-v2[5]. diff --git a/Documentation/fsck-msgids.adoc b/Documentation/fsck-msgids.adoc index b14bc44ca4..9601fff228 100644 --- a/Documentation/fsck-msgids.adoc +++ b/Documentation/fsck-msgids.adoc @@ -16,6 +16,13 @@ `badObjectSha1`:: (ERROR) An object has a bad sha1. +`badPackedRefEntry`:: + (ERROR) The "packed-refs" file contains an invalid entry. + +`badPackedRefHeader`:: + (ERROR) The "packed-refs" file contains an invalid + header. + `badParentSha1`:: (ERROR) A commit object has a bad parent sha1. @@ -176,6 +183,13 @@ `nullSha1`:: (WARN) Tree contains entries pointing to a null sha1. +`packedRefEntryNotTerminated`:: + (ERROR) The "packed-refs" file contains an entry that is + not terminated by a newline. + +`packedRefUnsorted`:: + (ERROR) The "packed-refs" file is not sorted. + `refMissingNewline`:: (INFO) A loose ref that does not end with newline(LF). As valid implementations of Git never created such a loose ref diff --git a/Documentation/git-bisect.adoc b/Documentation/git-bisect.adoc index 82f944dc03..58dbb74a15 100644 --- a/Documentation/git-bisect.adoc +++ b/Documentation/git-bisect.adoc @@ -495,6 +495,7 @@ $ git bisect old HEAD~10 # the tenth commit from now is marked as old ------------ + or: ++ ------------ $ git bisect start --term-old broken --term-new fixed $ git bisect fixed diff --git a/Documentation/git-branch.adoc b/Documentation/git-branch.adoc index 7a073a36d6..50a1e13e1f 100644 --- a/Documentation/git-branch.adoc +++ b/Documentation/git-branch.adoc @@ -7,23 +7,23 @@ git-branch - List, create, or delete branches SYNOPSIS -------- -[verse] -'git branch' [--color[=<when>] | --no-color] [--show-current] - [-v [--abbrev=<n> | --no-abbrev]] - [--column[=<options>] | --no-column] [--sort=<key>] - [--merged [<commit>]] [--no-merged [<commit>]] - [--contains [<commit>]] [--no-contains [<commit>]] - [--points-at <object>] [--format=<format>] - [(-r | --remotes) | (-a | --all)] - [--list] [<pattern>...] -'git branch' [--track[=(direct|inherit)] | --no-track] [-f] - [--recurse-submodules] <branchname> [<start-point>] -'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>] -'git branch' --unset-upstream [<branchname>] -'git branch' (-m | -M) [<oldbranch>] <newbranch> -'git branch' (-c | -C) [<oldbranch>] <newbranch> -'git branch' (-d | -D) [-r] <branchname>... -'git branch' --edit-description [<branchname>] +[synopsis] +git branch [--color[=<when>] | --no-color] [--show-current] + [-v [--abbrev=<n> | --no-abbrev]] + [--column[=<options>] | --no-column] [--sort=<key>] + [--merged [<commit>]] [--no-merged [<commit>]] + [--contains [<commit>]] [--no-contains [<commit>]] + [--points-at <object>] [--format=<format>] + [(-r|--remotes) | (-a|--all)] + [--list] [<pattern>...] +git branch [--track[=(direct|inherit)] | --no-track] [-f] + [--recurse-submodules] <branch-name> [<start-point>] +git branch (--set-upstream-to=<upstream>|-u <upstream>) [<branch-name>] +git branch --unset-upstream [<branch-name>] +git branch (-m|-M) [<old-branch>] <new-branch> +git branch (-c|-C) [<old-branch>] <new-branch> +git branch (-d|-D) [-r] <branch-name>... +git branch --edit-description [<branch-name>] DESCRIPTION ----------- @@ -49,173 +49,184 @@ With `--contains`, shows only the branches that contain the named commit named commit), `--no-contains` inverts it. With `--merged`, only branches merged into the named commit (i.e. the branches whose tip commits are reachable from the named commit) will be listed. With `--no-merged` only -branches not merged into the named commit will be listed. If the <commit> +branches not merged into the named commit will be listed. If the _<commit>_ argument is missing it defaults to `HEAD` (i.e. the tip of the current branch). -The command's second form creates a new branch head named <branchname> -which points to the current `HEAD`, or <start-point> if given. As a -special case, for <start-point>, you may use `"A...B"` as a shortcut for -the merge base of `A` and `B` if there is exactly one merge base. You -can leave out at most one of `A` and `B`, in which case it defaults to -`HEAD`. +The command's second form creates a new branch head named _<branch-name>_ +which points to the current `HEAD`, or _<start-point>_ if given. As a +special case, for _<start-point>_, you may use `<rev-A>...<rev-B>` as a +shortcut for the merge base of _<rev-A>_ and _<rev-B>_ if there is exactly +one merge base. You can leave out at most one of _<rev-A>_ and _<rev-B>_, +in which case it defaults to `HEAD`. Note that this will create the new branch, but it will not switch the -working tree to it; use "git switch <newbranch>" to switch to the +working tree to it; use `git switch <new-branch>` to switch to the new branch. When a local branch is started off a remote-tracking branch, Git sets up the branch (specifically the `branch.<name>.remote` and `branch.<name>.merge` -configuration entries) so that 'git pull' will appropriately merge from +configuration entries) so that `git pull` will appropriately merge from the remote-tracking branch. This behavior may be changed via the global `branch.autoSetupMerge` configuration flag. That setting can be overridden by using the `--track` and `--no-track` options, and changed later using `git branch --set-upstream-to`. -With a `-m` or `-M` option, <oldbranch> will be renamed to <newbranch>. -If <oldbranch> had a corresponding reflog, it is renamed to match -<newbranch>, and a reflog entry is created to remember the branch -renaming. If <newbranch> exists, -M must be used to force the rename +With a `-m` or `-M` option, _<old-branch>_ will be renamed to _<new-branch>_. +If _<old-branch>_ had a corresponding reflog, it is renamed to match +_<new-branch>_, and a reflog entry is created to remember the branch +renaming. If _<new-branch>_ exists, `-M` must be used to force the rename to happen. The `-c` and `-C` options have the exact same semantics as `-m` and `-M`, except instead of the branch being renamed, it will be copied to a new name, along with its config and reflog. -With a `-d` or `-D` option, `<branchname>` will be deleted. You may +With a `-d` or `-D` option, _<branch-name>_ will be deleted. You may specify more than one branch for deletion. If the branch currently has a reflog then the reflog will also be deleted. Use `-r` together with `-d` to delete remote-tracking branches. Note, that it only makes sense to delete remote-tracking branches if they no longer exist -in the remote repository or if 'git fetch' was configured not to fetch -them again. See also the 'prune' subcommand of linkgit:git-remote[1] for a +in the remote repository or if `git fetch` was configured not to fetch +them again. See also the `prune` subcommand of linkgit:git-remote[1] for a way to clean up all obsolete remote-tracking branches. OPTIONS ------- --d:: ---delete:: +`-d`:: +`--delete`:: Delete a branch. The branch must be fully merged in its upstream branch, or in `HEAD` if no upstream was set with `--track` or `--set-upstream-to`. --D:: +`-D`:: Shortcut for `--delete --force`. ---create-reflog:: +`--create-reflog`:: Create the branch's reflog. This activates recording of all changes made to the branch ref, enabling use of date - based sha1 expressions such as "<branchname>@\{yesterday}". + based sha1 expressions such as `<branch-name>@{yesterday}`. Note that in non-bare repositories, reflogs are usually enabled by default by the `core.logAllRefUpdates` config option. The negated form `--no-create-reflog` only overrides an earlier `--create-reflog`, but currently does not negate the setting of `core.logAllRefUpdates`. --f:: ---force:: - Reset <branchname> to <start-point>, even if <branchname> exists - already. Without `-f`, 'git branch' refuses to change an existing branch. +`-f`:: +`--force`:: + Reset _<branch-name>_ to _<start-point>_, even if _<branch-name>_ exists + already. Without `-f`, `git branch` refuses to change an existing branch. In combination with `-d` (or `--delete`), allow deleting the branch irrespective of its merged status, or whether it even points to a valid commit. In combination with `-m` (or `--move`), allow renaming the branch even if the new branch name already exists, the same applies for `-c` (or `--copy`). + -Note that 'git branch -f <branchname> [<start-point>]', even with '-f', -refuses to change an existing branch `<branchname>` that is checked out +Note that `git branch -f <branch-name> [<start-point>]`, even with `-f`, +refuses to change an existing branch _<branch-name>_ that is checked out in another worktree linked to the same repository. --m:: ---move:: +`-m`:: +`--move`:: Move/rename a branch, together with its config and reflog. --M:: +`-M`:: Shortcut for `--move --force`. --c:: ---copy:: +`-c`:: +`--copy`:: Copy a branch, together with its config and reflog. --C:: +`-C`:: Shortcut for `--copy --force`. ---color[=<when>]:: +`--color[=<when>]`:: Color branches to highlight current, local, and remote-tracking branches. - The value must be always (the default), never, or auto. + The value must be `always` (the default), `never`, or `auto`. ---no-color:: +`--no-color`:: Turn off branch colors, even when the configuration file gives the default to color output. Same as `--color=never`. --i:: ---ignore-case:: +`-i`:: +`--ignore-case`:: Sorting and filtering branches are case insensitive. ---omit-empty:: +`--omit-empty`:: Do not print a newline after formatted refs where the format expands to the empty string. ---column[=<options>]:: ---no-column:: +`--column[=<options>]`:: +`--no-column`:: Display branch listing in columns. See configuration variable `column.branch` for option syntax. `--column` and `--no-column` - without options are equivalent to 'always' and 'never' respectively. + without options are equivalent to `always` and `never` respectively. + This option is only applicable in non-verbose mode. --r:: ---remotes:: - List or delete (if used with -d) the remote-tracking branches. +`--sort=<key>`:: + Sort based on _<key>_. Prefix `-` to sort in descending + order of the value. You may use the `--sort=<key>` option + multiple times, in which case the last key becomes the primary + key. The keys supported are the same as those in linkgit:git-for-each-ref[1]. + Sort order defaults to the value configured for the + `branch.sort` variable if it exists, or to sorting based on the + full refname (including `refs/...` prefix). This lists + detached `HEAD` (if present) first, then local branches and + finally remote-tracking branches. See linkgit:git-config[1]. + +`-r`:: +`--remotes`:: + List or delete (if used with `-d`) the remote-tracking branches. Combine with `--list` to match the optional pattern(s). --a:: ---all:: +`-a`:: +`--all`:: List both remote-tracking branches and local branches. Combine with `--list` to match optional pattern(s). --l:: ---list:: +`-l`:: +`--list`:: List branches. With optional `<pattern>...`, e.g. `git branch --list 'maint-*'`, list only the branches that match the pattern(s). ---show-current:: - Print the name of the current branch. In detached HEAD state, +`--show-current`:: + Print the name of the current branch. In detached `HEAD` state, nothing is printed. --v:: --vv:: ---verbose:: +`-v`:: +`-vv`:: +`--verbose`:: When in list mode, show sha1 and commit subject line for each head, along with relationship to upstream branch (if any). If given twice, print the path of the linked worktree (if any) and the name of the upstream branch, as well (see also `git remote show <remote>`). Note that the - current worktree's HEAD will not have its path printed (it will always + current worktree's `HEAD` will not have its path printed (it will always be your current directory). --q:: ---quiet:: +`-q`:: +`--quiet`:: Be more quiet when creating or deleting a branch, suppressing non-error messages. ---abbrev=<n>:: +`--abbrev=<n>`:: In the verbose listing that show the commit object name, - show the shortest prefix that is at least '<n>' hexdigits + show the shortest prefix that is at least _<n>_ hexdigits long that uniquely refers the object. The default value is 7 and can be overridden by the `core.abbrev` config option. ---no-abbrev:: +`--no-abbrev`:: Display the full sha1s in the output listing rather than abbreviating them. --t:: ---track[=(direct|inherit)]:: +`-t`:: +`--track[=(direct|inherit)]`:: When creating a new branch, set up `branch.<name>.remote` and `branch.<name>.merge` configuration entries to set "upstream" tracking configuration for the new branch. This @@ -229,7 +240,7 @@ The exact upstream branch is chosen depending on the optional argument: itself as the upstream; `--track=inherit` means to copy the upstream configuration of the start-point branch. + -The branch.autoSetupMerge configuration variable specifies how `git switch`, +The `branch.autoSetupMerge` configuration variable specifies how `git switch`, `git checkout` and `git branch` should behave when neither `--track` nor `--no-track` are specified: + @@ -238,106 +249,94 @@ were given whenever the start-point is a remote-tracking branch. `false` behaves as if `--no-track` were given. `always` behaves as though `--track=direct` were given. `inherit` behaves as though `--track=inherit` were given. `simple` behaves as though `--track=direct` were given only when -the start-point is a remote-tracking branch and the new branch has the same +the _<start-point>_ is a remote-tracking branch and the new branch has the same name as the remote branch. + See linkgit:git-pull[1] and linkgit:git-config[1] for additional discussion on how the `branch.<name>.remote` and `branch.<name>.merge` options are used. ---no-track:: +`--no-track`:: Do not set up "upstream" configuration, even if the - branch.autoSetupMerge configuration variable is set. + `branch.autoSetupMerge` configuration variable is set. ---recurse-submodules:: - THIS OPTION IS EXPERIMENTAL! Causes the current command to +`--recurse-submodules`:: + THIS OPTION IS EXPERIMENTAL! Cause the current command to recurse into submodules if `submodule.propagateBranches` is enabled. See `submodule.propagateBranches` in linkgit:git-config[1]. Currently, only branch creation is supported. + -When used in branch creation, a new branch <branchname> will be created +When used in branch creation, a new branch _<branch-name>_ will be created in the superproject and all of the submodules in the superproject's -<start-point>. In submodules, the branch will point to the submodule -commit in the superproject's <start-point> but the branch's tracking +_<start-point>_. In submodules, the branch will point to the submodule +commit in the superproject's _<start-point>_ but the branch's tracking information will be set up based on the submodule's branches and remotes e.g. `git branch --recurse-submodules topic origin/main` will create the submodule branch "topic" that points to the submodule commit in the superproject's "origin/main", but tracks the submodule's "origin/main". ---set-upstream:: +`--set-upstream`:: As this option had confusing syntax, it is no longer supported. Please use `--track` or `--set-upstream-to` instead. --u <upstream>:: ---set-upstream-to=<upstream>:: - Set up <branchname>'s tracking information so <upstream> is - considered <branchname>'s upstream branch. If no <branchname> +`-u <upstream>`:: +`--set-upstream-to=<upstream>`:: + Set up _<branch-name>_'s tracking information so _<upstream>_ is + considered _<branch-name>_'s upstream branch. If no _<branch-name>_ is specified, then it defaults to the current branch. ---unset-upstream:: - Remove the upstream information for <branchname>. If no branch +`--unset-upstream`:: + Remove the upstream information for _<branch-name>_. If no branch is specified it defaults to the current branch. ---edit-description:: +`--edit-description`:: Open an editor and edit the text to explain what the branch is for, to be used by various other commands (e.g. `format-patch`, `request-pull`, and `merge` (if enabled)). Multi-line explanations may be used. ---contains [<commit>]:: - Only list branches which contain the specified commit (HEAD +`--contains [<commit>]`:: + Only list branches which contain _<commit>_ (`HEAD` if not specified). Implies `--list`. ---no-contains [<commit>]:: - Only list branches which don't contain the specified commit - (HEAD if not specified). Implies `--list`. +`--no-contains [<commit>]`:: + Only list branches which don't contain _<commit>_ + (`HEAD` if not specified). Implies `--list`. ---merged [<commit>]:: - Only list branches whose tips are reachable from the - specified commit (HEAD if not specified). Implies `--list`. +`--merged [<commit>]`:: + Only list branches whose tips are reachable from + _<commit>_ (`HEAD` if not specified). Implies `--list`. ---no-merged [<commit>]:: - Only list branches whose tips are not reachable from the - specified commit (HEAD if not specified). Implies `--list`. +`--no-merged [<commit>]`:: + Only list branches whose tips are not reachable from + _<commit>_ (`HEAD` if not specified). Implies `--list`. -<branchname>:: +`--points-at <object>`:: + Only list branches of _<object>_. + +`--format <format>`:: + A string that interpolates `%(fieldname)` from a branch ref being shown + and the object it points at. _<format>_ is the same as + that of linkgit:git-for-each-ref[1]. + +_<branch-name>_:: The name of the branch to create or delete. The new branch name must pass all checks defined by linkgit:git-check-ref-format[1]. Some of these checks may restrict the characters allowed in a branch name. -<start-point>:: +_<start-point>_:: The new branch head will point to this commit. It may be given as a branch name, a commit-id, or a tag. If this - option is omitted, the current HEAD will be used instead. + option is omitted, the current `HEAD` will be used instead. -<oldbranch>:: +_<old-branch>_:: The name of an existing branch. If this option is omitted, the name of the current branch will be used instead. -<newbranch>:: +_<new-branch>_:: The new name for an existing branch. The same restrictions as for - <branchname> apply. - ---sort=<key>:: - Sort based on the key given. Prefix `-` to sort in descending - order of the value. You may use the --sort=<key> option - multiple times, in which case the last key becomes the primary - key. The keys supported are the same as those in `git - for-each-ref`. Sort order defaults to the value configured for the - `branch.sort` variable if it exists, or to sorting based on the - full refname (including `refs/...` prefix). This lists - detached HEAD (if present) first, then local branches and - finally remote-tracking branches. See linkgit:git-config[1]. - - ---points-at <object>:: - Only list branches of the given object. - ---format <format>:: - A string that interpolates `%(fieldname)` from a branch ref being shown - and the object it points at. The format is the same as - that of linkgit:git-for-each-ref[1]. + _<branch-name>_ apply. CONFIGURATION ------------- @@ -374,7 +373,7 @@ $ git branch -D test <2> ------------ + <1> Delete the remote-tracking branches "todo", "html" and "man". The next - 'fetch' or 'pull' will create them again unless you configure them not to. + `git fetch` or `git pullè will create them again unless you configure them not to. See linkgit:git-fetch[1]. <2> Delete the "test" branch even if the "master" branch (or whichever branch is currently checked out) does not have all commits from the test branch. @@ -386,8 +385,8 @@ $ git branch -r -l '<remote>/<pattern>' <1> $ git for-each-ref 'refs/remotes/<remote>/<pattern>' <2> ------------ + -<1> Using `-a` would conflate <remote> with any local branches you happen to - have been prefixed with the same <remote> pattern. +<1> Using `-a` would conflate _<remote>_ with any local branches you happen to + have been prefixed with the same _<remote>_ pattern. <2> `for-each-ref` can take a wide range of options. See linkgit:git-for-each-ref[1] Patterns will normally need quoting. @@ -396,24 +395,24 @@ NOTES ----- If you are creating a branch that you want to switch to immediately, -it is easier to use the "git switch" command with its `-c` option to +it is easier to use the `git switch` command with its `-c` option to do the same thing with a single command. The options `--contains`, `--no-contains`, `--merged` and `--no-merged` serve four related but different purposes: - `--contains <commit>` is used to find all branches which will need - special attention if <commit> were to be rebased or amended, since those - branches contain the specified <commit>. + special attention if _<commit>_ were to be rebased or amended, since those + branches contain the specified _<commit>_. - `--no-contains <commit>` is the inverse of that, i.e. branches that don't - contain the specified <commit>. + contain the specified _<commit>_. - `--merged` is used to find all branches which can be safely deleted, - since those branches are fully contained by HEAD. + since those branches are fully contained by `HEAD`. - `--no-merged` is used to find branches which are candidates for merging - into HEAD, since those branches are not fully contained by HEAD. + into `HEAD`, since those branches are not fully contained by `HEAD`. include::ref-reachability-filters.adoc[] @@ -422,8 +421,8 @@ SEE ALSO linkgit:git-check-ref-format[1], linkgit:git-fetch[1], linkgit:git-remote[1], -link:user-manual.html#what-is-a-branch[``Understanding history: What is -a branch?''] in the Git User's Manual. +link:user-manual.html#what-is-a-branch["Understanding history: What is +a branch?"] in the Git User's Manual. GIT --- diff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc index d5890ae368..30359f5dbd 100644 --- a/Documentation/git-cat-file.adoc +++ b/Documentation/git-cat-file.adoc @@ -322,10 +322,10 @@ of `%(objectsize)` bytes), followed by a newline. For example, `--batch` without a custom format would produce: ------------- +----------- <oid> SP <type> SP <size> LF <contents> LF ------------- +----------- Whereas `--batch-check='%(objectname) %(objecttype)'` would produce: diff --git a/Documentation/git-check-attr.adoc b/Documentation/git-check-attr.adoc index cb5a6c8f33..503b644657 100644 --- a/Documentation/git-check-attr.adoc +++ b/Documentation/git-check-attr.adoc @@ -76,6 +76,7 @@ EXAMPLES -------- In the examples, the following '.gitattributes' file is used: + --------------- *.java diff=java -crlf myAttr NoMyAttr.java !myAttr @@ -83,12 +84,14 @@ README caveat=unspecified --------------- * Listing a single attribute: ++ --------------- $ git check-attr diff org/example/MyClass.java org/example/MyClass.java: diff: java --------------- * Listing multiple attributes for a file: ++ --------------- $ git check-attr crlf diff myAttr -- org/example/MyClass.java org/example/MyClass.java: crlf: unset @@ -97,6 +100,7 @@ org/example/MyClass.java: myAttr: set --------------- * Listing all attributes for a file: ++ --------------- $ git check-attr --all -- org/example/MyClass.java org/example/MyClass.java: diff: java @@ -104,6 +108,7 @@ org/example/MyClass.java: myAttr: set --------------- * Listing an attribute for multiple files: ++ --------------- $ git check-attr myAttr -- org/example/MyClass.java org/example/NoMyAttr.java org/example/MyClass.java: myAttr: set @@ -111,6 +116,7 @@ org/example/NoMyAttr.java: myAttr: unspecified --------------- * Not all values are equally unambiguous: ++ --------------- $ git check-attr caveat README README: caveat: unspecified diff --git a/Documentation/git-column.adoc b/Documentation/git-column.adoc index 85fb87c94a..5a4f2b6fde 100644 --- a/Documentation/git-column.adoc +++ b/Documentation/git-column.adoc @@ -50,6 +50,7 @@ EXAMPLES -------- Format data by columns: ++ ------------ $ seq 1 24 | git column --mode=column --padding=5 1 4 7 10 13 16 19 22 @@ -58,6 +59,7 @@ $ seq 1 24 | git column --mode=column --padding=5 ------------ Format data by rows: ++ ------------ $ seq 1 21 | git column --mode=row --padding=5 1 2 3 4 5 6 7 @@ -66,6 +68,7 @@ $ seq 1 21 | git column --mode=row --padding=5 ------------ List some tags in a table with unequal column widths: ++ ------------ $ git tag --list 'v2.4.*' --column=row,dense v2.4.0 v2.4.0-rc0 v2.4.0-rc1 v2.4.0-rc2 v2.4.0-rc3 diff --git a/Documentation/git-cvsserver.adoc b/Documentation/git-cvsserver.adoc index 4c475efeab..fe822f571d 100644 --- a/Documentation/git-cvsserver.adoc +++ b/Documentation/git-cvsserver.adoc @@ -125,9 +125,11 @@ creation in your platform (e.g. mkpasswd in Linux, encrypt in OpenBSD or pwhash in NetBSD) and paste it in the right location. Then provide your password via the pserver method, for example: + ------ cvs -d:pserver:someuser:somepassword@server:/path/repo.git co <HEAD_name> ------ + No special setup is needed for SSH access, other than having Git tools in the PATH. If you have clients that do not accept the CVS_SERVER environment variable, you can rename 'git-cvsserver' to `cvs`. @@ -138,6 +140,7 @@ CVS_SERVER directly in CVSROOT like ------ cvs -d ":ext;CVS_SERVER=git cvsserver:user@server/path/repo.git" co <HEAD_name> ------ + This has the advantage that it will be saved in your 'CVS/Root' files and you don't need to worry about always setting the correct environment variable. SSH users restricted to 'git-shell' don't need to override the default @@ -168,6 +171,7 @@ All configuration variables can also be overridden for a specific method of access. Valid method names are "ext" (for SSH access) and "pserver". The following example configuration would disable pserver access while still allowing access over SSH. + ------ [gitcvs] enabled=0 diff --git a/Documentation/git-diff-pairs.adoc b/Documentation/git-diff-pairs.adoc new file mode 100644 index 0000000000..f99fcd1ead --- /dev/null +++ b/Documentation/git-diff-pairs.adoc @@ -0,0 +1,60 @@ +git-diff-pairs(1) +================= + +NAME +---- +git-diff-pairs - Compare the content and mode of provided blob pairs + +SYNOPSIS +-------- +[synopsis] +git diff-pairs -z [<diff-options>] + +DESCRIPTION +----------- +Show changes for file pairs provided on stdin. Input for this command must be +in the NUL-terminated raw output format as generated by commands such as `git +diff-tree -z -r --raw`. By default, the outputted diffs are computed and shown +in the patch format when stdin closes. + +A single NUL byte may be written to stdin between raw input lines to compute +file pair diffs up to that point instead of waiting for stdin to close. A NUL +byte is also written to the output to delimit between these batches of diffs. + +Usage of this command enables the traditional diff pipeline to be broken up +into separate stages where `diff-pairs` acts as the output phase. Other +commands, such as `diff-tree`, may serve as a frontend to compute the raw +diff format used as input. + +Instead of computing diffs via `git diff-tree -p -M` in one step, `diff-tree` +can compute the file pairs and rename information without the blob diffs. This +output can be fed to `diff-pairs` to generate the underlying blob diffs as done +in the following example: + +----------------------------- +git diff-tree -z -r -M $a $b | +git diff-pairs -z +----------------------------- + +Computing the tree diff upfront with rename information allows patch output +from `diff-pairs` to be progressively computed over the course of potentially +multiple invocations. + +Pathspecs are not currently supported by `diff-pairs`. Pathspec limiting should +be performed by the upstream command generating the raw diffs used as input. + +Tree objects are not currently supported as input and are rejected. + +Abbreviated object IDs in the `diff-pairs` input are not supported. Outputted +object IDs can be abbreviated using the `--abbrev` option. + +OPTIONS +------- + +include::diff-options.adoc[] + +include::diff-generate-patch.adoc[] + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-fast-export.adoc b/Documentation/git-fast-export.adoc index 752e4b9b01..413a527496 100644 --- a/Documentation/git-fast-export.adoc +++ b/Documentation/git-fast-export.adoc @@ -27,17 +27,33 @@ OPTIONS Insert 'progress' statements every <n> objects, to be shown by 'git fast-import' during import. ---signed-tags=(verbatim|warn|warn-strip|strip|abort):: +--signed-tags=(verbatim|warn-verbatim|warn-strip|strip|abort):: Specify how to handle signed tags. Since any transformation - after the export can change the tag names (which can also happen - when excluding revisions) the signatures will not match. + after the export (or during the export, such as excluding + revisions) can change the hashes being signed, the signatures + may become invalid. + When asking to 'abort' (which is the default), this program will die when encountering a signed tag. With 'strip', the tags will silently be made unsigned, with 'warn-strip' they will be made unsigned but a warning will be displayed, with 'verbatim', they will be silently -exported and with 'warn', they will be exported, but you will see a -warning. +exported and with 'warn-verbatim' (or 'warn', a deprecated synonym), +they will be exported, but you will see a warning. 'verbatim' and +'warn-verbatim' should only be used if you know that no transformation +affecting tags or any commit in their history will be performed by you +or by fast-export or fast-import, or if you do not care that the +resulting tag will have an invalid signature. + +--signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort):: + Specify how to handle signed commits. Behaves exactly as + '--signed-tags', but for commits. Default is 'abort'. ++ +Earlier versions this command that did not have '--signed-commits' +behaved as if '--signed-commits=strip'. As an escape hatch for users +of tools that call 'git fast-export' but do not yet support +'--signed-commits', you may set the environment variable +'FAST_EXPORT_SIGNED_COMMITS_NOABORT=1' in order to change the default +from 'abort' to 'warn-strip'. --tag-of-filtered-object=(abort|drop|rewrite):: Specify how to handle tags whose tagged object is filtered out. diff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc index 58a2eaa51a..7b107f5e8e 100644 --- a/Documentation/git-fast-import.adoc +++ b/Documentation/git-fast-import.adoc @@ -431,13 +431,22 @@ and control the current import process. More detailed discussion Create or update a branch with a new commit, recording one logical change to the project. +//// +Yes, it's intentional that the 'gpgsig' line doesn't have a trailing +`LF`; the definition of `data` has a byte-count prefix, so it +doesn't need an `LF` to act as a terminator (and `data` also already +includes an optional trailing `LF?` just in case you want to include +one). +//// + .... 'commit' SP <ref> LF mark? original-oid? ('author' (SP <name>)? SP LT <email> GT SP <when> LF)? 'committer' (SP <name>)? SP LT <email> GT SP <when> LF - ('encoding' SP <encoding>)? + ('gpgsig' SP <alg> LF data)? + ('encoding' SP <encoding> LF)? data ('from' SP <commit-ish> LF)? ('merge' SP <commit-ish> LF)* @@ -505,6 +514,15 @@ that was selected by the --date-format=<fmt> command-line option. See ``Date Formats'' above for the set of supported formats, and their syntax. +`gpgsig` +^^^^^^^^ + +The optional `gpgsig` command is used to include a PGP/GPG signature +that signs the commit data. + +Here <alg> specifies which hashing algorithm is used for this +signature, either `sha1` or `sha256`. + `encoding` ^^^^^^^^^^ The optional `encoding` command indicates the encoding of the commit diff --git a/Documentation/git-for-each-ref.adoc b/Documentation/git-for-each-ref.adoc index ffb97e62c2..5ef89fc0fe 100644 --- a/Documentation/git-for-each-ref.adoc +++ b/Documentation/git-for-each-ref.adoc @@ -441,6 +441,7 @@ Ref: %(*refname) A simple example showing the use of shell eval on the output, demonstrating the use of --shell. List the prefixes of all heads: + ------------ #!/bin/sh @@ -455,6 +456,7 @@ done A bit more elaborate report on tags, demonstrating that the format may be an entire script: + ------------ #!/bin/sh diff --git a/Documentation/git-fsck.adoc b/Documentation/git-fsck.adoc index 8f32800a83..11203ba925 100644 --- a/Documentation/git-fsck.adoc +++ b/Documentation/git-fsck.adoc @@ -12,7 +12,7 @@ SYNOPSIS 'git fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs] [--[no-]full] [--strict] [--verbose] [--lost-found] [--[no-]dangling] [--[no-]progress] [--connectivity-only] - [--[no-]name-objects] [<object>...] + [--[no-]name-objects] [--[no-]references] [<object>...] DESCRIPTION ----------- @@ -104,6 +104,11 @@ care about this output and want to speed it up further. progress status even if the standard error stream is not directed to a terminal. +--[no-]references:: + Control whether to check the references database consistency + via 'git refs verify'. See linkgit:git-refs[1] for details. + The default is to check the references database. + CONFIGURATION ------------- diff --git a/Documentation/git-maintenance.adoc b/Documentation/git-maintenance.adoc index 0450d74aff..c90b370b1f 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 diff --git a/Documentation/git-p4.adoc b/Documentation/git-p4.adoc index de5ee6748e..f97b786bf9 100644 --- a/Documentation/git-p4.adoc +++ b/Documentation/git-p4.adoc @@ -80,6 +80,7 @@ This: To reproduce the entire p4 history in Git, use the '@all' modifier on the depot path: + ------------ $ git p4 clone //depot/path/project@all ------------ @@ -89,19 +90,23 @@ Sync ~~~~ As development continues in the p4 repository, those changes can be included in the Git repository using: + ------------ $ git p4 sync ------------ + This command finds new changes in p4 and imports them as Git commits. P4 repositories can be added to an existing Git repository using 'git p4 sync' too: + ------------ $ mkdir repo-git $ cd repo-git $ git init $ git p4 sync //path/in/your/perforce/depot ------------ + This imports the specified depot into 'refs/remotes/p4/master' in an existing Git repository. The `--branch` option can be used to specify a different branch to @@ -125,6 +130,7 @@ and merge them with local uncommitted changes. Often, the p4 repository is the ultimate location for all code, thus a rebase workflow makes sense. This command does 'git p4 sync' followed by 'git rebase' to move local commits on top of updated p4 changes. + ------------ $ git p4 rebase ------------ @@ -140,16 +146,19 @@ will be created and populated if it does not already exist. To submit all changes that are in the current Git branch but not in the 'p4/master' branch, use: + ------------ $ git p4 submit ------------ To specify a branch other than the current one, use: + ------------ $ git p4 submit topicbranch ------------ To specify a single commit or a range of commits, use: + ------------ $ git p4 submit --commit <sha1> $ git p4 submit --commit <sha1..sha1> @@ -510,20 +519,24 @@ when cloning or syncing to have 'git p4' automatically find subdirectories in p4, and to generate these as branches in Git. For example, if the P4 repository structure is: + ---- //depot/main/... //depot/branch1/... ---- And "p4 branch -o branch1" shows a View line that looks like: + ---- //depot/main/... //depot/branch1/... ---- Then this 'git p4 clone' command: + ---- git p4 clone --detect-branches //depot@all ---- + produces a separate branch in 'refs/remotes/p4/' for //depot/main, called 'master', and one for //depot/branch1 called 'depot/branch1'. @@ -536,6 +549,7 @@ simple p4 branch specification, where the "source" and "destination" are the path elements in the p4 repository. The example above relied on the presence of the p4 branch. Without p4 branches, the same result will occur with: + ---- git init depot cd depot diff --git a/Documentation/git-pack-refs.adoc b/Documentation/git-pack-refs.adoc index 2dcabaf74c..652c549771 100644 --- a/Documentation/git-pack-refs.adoc +++ b/Documentation/git-pack-refs.adoc @@ -88,10 +88,10 @@ Do not pack refs matching the given `glob(7)` pattern. Repetitions of this optio accumulate exclusion patterns. Use `--no-exclude` to clear and reset the list of patterns. If a ref is already packed, including it with `--exclude` will not unpack it. - ++ When used with `--all`, pack only loose refs which do not match any of the provided `--exclude` patterns. - ++ When used with `--include`, refs provided to `--include`, minus refs that are provided to `--exclude` will be packed. diff --git a/Documentation/git-rebase.adoc b/Documentation/git-rebase.adoc index 153cb69a4f..956d3048f5 100644 --- a/Documentation/git-rebase.adoc +++ b/Documentation/git-rebase.adoc @@ -1107,10 +1107,12 @@ In that case, the fix is easy because 'git rebase' knows to skip changes that are already present in the new upstream (unless `--reapply-cherry-picks` is given). So if you say (assuming you're on 'topic') + ------------ $ git rebase subsystem ------------ you will end up with the fixed history + ------------ o---o---o---o---o---o---o---o master \ @@ -1145,6 +1147,7 @@ of the old 'subsystem', for example: You can then transplant the old `subsystem..topic` to the new tip by saying (for the reflog case, and assuming you are on 'topic' already): + ------------ $ git rebase --onto subsystem subsystem@{1} ------------ 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-repack.adoc b/Documentation/git-repack.adoc index 5852a5c973..e1cd75eebe 100644 --- a/Documentation/git-repack.adoc +++ b/Documentation/git-repack.adoc @@ -77,15 +77,18 @@ to the new separate pack will be written. Only useful with `--cruft -d`. --max-cruft-size=<n>:: - Repack cruft objects into packs as large as `<n>` bytes before - creating new packs. As long as there are enough cruft packs - smaller than `<n>`, repacking will cause a new cruft pack to - be created containing objects from any combined cruft packs, - along with any new unreachable objects. Cruft packs larger than - `<n>` will not be modified. When the new cruft pack is larger - than `<n>` bytes, it will be split into multiple packs, all of - which are guaranteed to be at most `<n>` bytes in size. Only - useful with `--cruft -d`. + Overrides `--max-pack-size` for cruft packs. Inherits the value of + `--max-pack-size` (if any) by default. See the documentation for + `--max-pack-size` for more details. + +--combine-cruft-below-size=<n>:: + When generating cruft packs without pruning, only repack + existing cruft packs whose size is strictly less than `<n>`, + where `<n>` represents a number of bytes, which can optionally + be suffixed with "k", "m", or "g". Cruft packs whose size is + greater than or equal to `<n>` are left as-is and not repacked. + Useful when you want to avoid repacking large cruft pack(s) in + repositories that have many and/or large unreachable objects. --expire-to=<dir>:: Write a cruft pack containing pruned objects (if any) to the diff --git a/Documentation/git-restore.adoc b/Documentation/git-restore.adoc index 751f01b441..877b7772e6 100644 --- a/Documentation/git-restore.adoc +++ b/Documentation/git-restore.adoc @@ -51,9 +51,6 @@ leave out at most one of _<rev-A>__ and _<rev-B>_, in which case it defaults to restore source and the restore location. See the "Interactive Mode" section of linkgit:git-add[1] to learn how to operate the `--patch` mode. -+ -Note that `--patch` can accept no pathspec and will prompt to restore -all modified paths. `-W`:: `--worktree`:: diff --git a/Documentation/gitattributes.adoc b/Documentation/gitattributes.adoc index a22d1ef1e1..f20041a323 100644 --- a/Documentation/gitattributes.adoc +++ b/Documentation/gitattributes.adoc @@ -531,13 +531,14 @@ must not send any response before it received the content and the final flush packet. Also note that the "value" of a "key=value" pair can contain the "=" character whereas the key would never contain that character. ------------------------- + +----------------------- packet: git> command=smudge packet: git> pathname=path/testfile.dat packet: git> 0000 packet: git> CONTENT packet: git> 0000 ------------------------- +----------------------- The filter is expected to respond with a list of "key=value" pairs terminated with a flush packet. If the filter does not experience @@ -559,6 +560,7 @@ packet: git< 0000 # empty list, keep "status=success" unchanged! If the result content is empty then the filter is expected to respond with a "success" status and a flush packet to signal the empty content. + ------------------------ packet: git< status=success packet: git< 0000 @@ -568,14 +570,16 @@ packet: git< 0000 # empty list, keep "status=success" unchanged! In case the filter cannot or does not want to process the content, it is expected to respond with an "error" status. ------------------------- + +----------------------- packet: git< status=error packet: git< 0000 ------------------------- +----------------------- If the filter experiences an error during processing, then it can send the status "error" after the content was (partially or completely) sent. + ------------------------ packet: git< status=success packet: git< 0000 @@ -589,10 +593,11 @@ In case the filter cannot or does not want to process the content as well as any future content for the lifetime of the Git process, then it is expected to respond with an "abort" status at any point in the protocol. ------------------------- + +----------------------- packet: git< status=abort packet: git< 0000 ------------------------- +----------------------- Git neither stops nor restarts the filter process in case the "error"/"abort" status is set. However, Git sets its exit code @@ -613,7 +618,8 @@ flag "can-delay" after the filter command and pathname. This flag denotes that the filter can delay filtering the current blob (e.g. to compensate network latencies) by responding with no content but with the status "delayed" and a flush packet. ------------------------- + +----------------------- packet: git> command=smudge packet: git> pathname=path/testfile.dat packet: git> can-delay=1 @@ -622,7 +628,7 @@ packet: git> CONTENT packet: git> 0000 packet: git< status=delayed packet: git< 0000 ------------------------- +----------------------- If the filter supports the "delay" capability then it must support the "list_available_blobs" command. If Git sends this command, then the @@ -647,10 +653,12 @@ packet: git< status=success packet: git< 0000 ------------------------ + After Git received the pathnames, it will request the corresponding blobs again. These requests contain a pathname and an empty content section. The filter is expected to respond with the smudged content in the usual way as explained above. + ------------------------ packet: git> command=smudge packet: git> pathname=path/testfile.dat diff --git a/Documentation/gitcli.adoc b/Documentation/gitcli.adoc index 04193ec907..1ea681b59d 100644 --- a/Documentation/gitcli.adoc +++ b/Documentation/gitcli.adoc @@ -209,13 +209,13 @@ $ git foo -o Arg However, this is *NOT* allowed for switches with an optional value, where the 'stuck' form must be used: + ---------------------------- $ git describe --abbrev HEAD # correct $ git describe --abbrev=10 HEAD # correct $ git describe --abbrev 10 HEAD # NOT WHAT YOU MEANT ---------------------------- - NOTES ON FREQUENTLY CONFUSED OPTIONS ------------------------------------ diff --git a/Documentation/gitprotocol-common.adoc b/Documentation/gitprotocol-common.adoc index cdc9d6e707..b4a5316ca4 100644 --- a/Documentation/gitprotocol-common.adoc +++ b/Documentation/gitprotocol-common.adoc @@ -21,11 +21,13 @@ ABNF Notation ABNF notation as described by RFC 5234 is used within the protocol documents, except the following replacement core rules are used: + ---- HEXDIG = DIGIT / "a" / "b" / "c" / "d" / "e" / "f" ---- We also define the following common rules: + ---- NUL = %x00 zero-id = 40*"0" diff --git a/Documentation/gitweb.adoc b/Documentation/gitweb.adoc index 5e2b491ec2..4261f9e235 100644 --- a/Documentation/gitweb.adoc +++ b/Documentation/gitweb.adoc @@ -103,6 +103,7 @@ You can generate the projects list index file using the project_index action "Generating projects list using gitweb" section below. Example contents: + ----------------------------------------------------------------------- foo.git Joe+R+Hacker+<joe@example.com> foo/bar.git O+W+Ner+<owner@example.org> @@ -124,6 +125,7 @@ Generating projects list using gitweb We assume that GITWEB_CONFIG has its default Makefile value, namely 'gitweb_config.perl'. Put the following in 'gitweb_make_index.perl' file: + ---------------------------------------------------------------------------- read_config_file("gitweb_config.perl"); $projects_list = $projectroot; @@ -518,12 +520,14 @@ rules. If you use the rewrite rules from the example you *might* also need something like the following in your gitweb configuration file (`/etc/gitweb.conf` following example): + ---------------------------------------------------------------------------- @stylesheets = ("/some/absolute/path/gitweb.css"); $my_uri = "/"; $home_link = "/"; $per_request_config = 1; ---------------------------------------------------------------------------- + Nowadays though gitweb should create HTML base tag when needed (to set base URI for relative links), so it should work automatically. @@ -535,6 +539,7 @@ Apache virtual host and gitweb configuration files in the following way. The virtual host configuration (in Apache configuration file) should look like this: + -------------------------------------------------------------------------- <VirtualHost *:80> ServerName git.example.org @@ -575,9 +580,11 @@ like this: Here actual project root is passed to gitweb via `GITWEB_PROJECT_ROOT` environment variable from a web server, so you need to put the following line in gitweb configuration file (`/etc/gitweb.conf` in above example): + -------------------------------------------------------------------------- $projectroot = $ENV{'GITWEB_PROJECTROOT'} || "/pub/git"; -------------------------------------------------------------------------- + *Note* that this requires to be set for each request, so either `$per_request_config` must be false, or the above must be put in code referenced by `$per_request_config`; @@ -604,9 +611,11 @@ the third and the fourth. PATH_INFO usage ~~~~~~~~~~~~~~~ If you enable PATH_INFO usage in gitweb by putting + ---------------------------------------------------------------------------- $feature{'pathinfo'}{'default'} = [1]; ---------------------------------------------------------------------------- + in your gitweb configuration file, it is possible to set up your server so that it consumes and produces URLs in the form @@ -636,6 +645,7 @@ complementary static files (stylesheet, favicon, JavaScript): </Directory> </VirtualHost> ---------------------------------------------------------------------------- + The rewrite rule guarantees that existing static files will be properly served, whereas any other URL will be passed to gitweb as PATH_INFO parameter. @@ -647,6 +657,7 @@ for fetching" section). A possible workaround for the latter is the following: in your project root dir (e.g. `/pub/git`) have the projects named *without* a .git extension (e.g. `/pub/git/project` instead of `/pub/git/project.git`) and configure Apache as follows: + ---------------------------------------------------------------------------- <VirtualHost *:80> ServerAlias git.example.com diff --git a/Documentation/gitweb.conf.adoc b/Documentation/gitweb.conf.adoc index 85983587fc..1348e9b125 100644 --- a/Documentation/gitweb.conf.adoc +++ b/Documentation/gitweb.conf.adoc @@ -603,6 +603,7 @@ Many gitweb features can be enabled (or disabled) and configured using the Each `%feature` hash element is a hash reference and has the following structure: + ---------------------------------------------------------------------- "<feature-name>" => { "sub" => <feature-sub-(subroutine)>, @@ -613,6 +614,7 @@ structure: Some features cannot be overridden per project. For those features the structure of appropriate `%feature` hash element has a simpler form: + ---------------------------------------------------------------------- "<feature-name>" => { "override" => 0, diff --git a/Documentation/merge-strategies.adoc b/Documentation/merge-strategies.adoc index 93822ebc4e..59f5ae36cc 100644 --- a/Documentation/merge-strategies.adoc +++ b/Documentation/merge-strategies.adoc @@ -82,6 +82,11 @@ find-renames[=<n>];; rename-threshold=<n>;; Deprecated synonym for `find-renames=<n>`. +no-renames;; + Turn off rename detection. This overrides the `merge.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 @@ -107,7 +112,7 @@ 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 three additional options that 'ort' ignores (not documented +there are two additional options that 'ort' ignores (not documented above) that are potentially useful with the 'recursive' strategy: patience;; @@ -121,11 +126,6 @@ diff-algorithm=[patience|minimal|histogram|myers];; specifically uses `diff-algorithm=histogram`, while `recursive` defaults to the `diff.algorithm` config setting. -no-renames;; - Turn off rename detection. This overrides the `merge.renames` - configuration variable. - See also linkgit:git-diff[1] `--no-renames`. - resolve:: This can only resolve two heads (i.e. the current branch and another branch you pulled from) using a 3-way merge diff --git a/Documentation/mergetools/vimdiff.adoc b/Documentation/mergetools/vimdiff.adoc index befa86d692..ab915df408 100644 --- a/Documentation/mergetools/vimdiff.adoc +++ b/Documentation/mergetools/vimdiff.adoc @@ -86,7 +86,7 @@ command. + -- When `MERGED` is not present in the layout, you must "mark" one of the -buffers with an asterisk. That will become the buffer you need to edit and +buffers with an arobase (`@`). That will become the buffer you need to edit and save after resolving the conflicts. .... ------------------------------------------ diff --git a/Documentation/meson.build b/Documentation/meson.build index 594546d68b..8b9e692c59 100644 --- a/Documentation/meson.build +++ b/Documentation/meson.build @@ -42,6 +42,7 @@ manpages = { 'git-diagnose.adoc' : 1, 'git-diff-files.adoc' : 1, 'git-diff-index.adoc' : 1, + 'git-diff-pairs.adoc' : 1, 'git-difftool.adoc' : 1, 'git-diff-tree.adoc' : 1, 'git-diff.adoc' : 1, @@ -96,7 +97,6 @@ manpages = { 'git-notes.adoc' : 1, 'git-p4.adoc' : 1, 'git-pack-objects.adoc' : 1, - 'git-pack-redundant.adoc' : 1, 'git-pack-refs.adoc' : 1, 'git-patch-id.adoc' : 1, 'git-prune-packed.adoc' : 1, @@ -205,6 +205,14 @@ manpages = { 'gitworkflows.adoc' : 7, } +manpages_breaking_changes = { + 'git-pack-redundant.adoc' : 1, +} + +if not get_option('breaking_changes') + manpages += manpages_breaking_changes +endif + docs_backend = get_option('docs_backend') if docs_backend == 'auto' if find_program('asciidoc', dirs: program_path, required: false).found() @@ -440,6 +448,7 @@ if get_option('docs').contains('html') ) articles = [ + 'BreakingChanges.adoc', 'DecisionMaking.adoc', 'MyFirstContribution.adoc', 'MyFirstObjectWalk.adoc', @@ -479,7 +488,9 @@ endif # Sanity check that we are not missing any tests present in 't/'. This check # only runs once at configure time and is thus best-effort, only. Furthermore, # it only verifies man pages for the sake of simplicity. -configured_manpages = manpages.keys() + [ 'git-bisect-lk2009.adoc', 'git-tools.adoc' ] +configured_manpages = manpages.keys() +configured_manpages += manpages_breaking_changes.keys() +configured_manpages += [ 'git-bisect-lk2009.adoc', 'git-tools.adoc' ] actual_manpages = run_command(shell, '-c', 'ls git*.adoc scalar.adoc', check: true, env: script_environment, diff --git a/Documentation/rev-list-options.adoc b/Documentation/rev-list-options.adoc index 785c0786e0..1c403c1e40 100644 --- a/Documentation/rev-list-options.adoc +++ b/Documentation/rev-list-options.adoc @@ -429,6 +429,7 @@ filtered for `foo`, they look different and equal, respectively.) In the following, we will always refer to the same example history to illustrate the differences between simplification settings. We assume that you are filtering for a file `foo` in this commit graph: + ----------------------------------------------------------------------- .-A---M---N---O---P---Q / / / / / / @@ -436,6 +437,7 @@ that you are filtering for a file `foo` in this commit graph: \ / / / / / `-------------' X ----------------------------------------------------------------------- + The horizontal line of history A---Q is taken to be the first parent of each merge. The commits are: @@ -640,7 +642,7 @@ commits affected by that topic, we may only want to view the subset of ----------------------------------------------------------------------- E \ - G---H---I---J + C---G---H---I---J \ L--M ----------------------------------------------------------------------- diff --git a/Documentation/technical/multi-pack-index.adoc b/Documentation/technical/multi-pack-index.adoc index cc063b30be..ffda70aa13 100644 --- a/Documentation/technical/multi-pack-index.adoc +++ b/Documentation/technical/multi-pack-index.adoc @@ -164,19 +164,81 @@ objects_nr($H2) + objects_nr($H1) + i (in the C implementation, this is often computed as `i + m->num_objects_in_base`). +=== Pseudo-pack order for incremental MIDXs + +The original implementation of multi-pack reachability bitmaps defined +the pseudo-pack order in linkgit:gitformat-pack[5] (see the section +titled "multi-pack-index reverse indexes") roughly as follows: + +____ +In short, a MIDX's pseudo-pack is the de-duplicated concatenation of +objects in packs stored by the MIDX, laid out in pack order, and the +packs arranged in MIDX order (with the preferred pack coming first). +____ + +In the incremental MIDX design, we extend this definition to include +objects from multiple layers of the MIDX chain. The pseudo-pack order +for incremental MIDXs is determined by concatenating the pseudo-pack +ordering for each layer of the MIDX chain in order. Formally two objects +`o1` and `o2` are compared as follows: + +1. If `o1` appears in an earlier layer of the MIDX chain than `o2`, then + `o1` sorts ahead of `o2`. + +2. Otherwise, if `o1` and `o2` appear in the same MIDX layer, and that + MIDX layer has no base, then if one of `pack(o1)` and `pack(o2)` is + preferred and the other is not, then the preferred one sorts ahead of + the non-preferred one. If there is a base layer (i.e. the MIDX layer + is not the first layer in the chain), then if `pack(o1)` appears + earlier in that MIDX layer's pack order, then `o1` sorts ahead of + `o2`. Likewise if `pack(o2)` appears earlier, then the opposite is + true. + +3. Otherwise, `o1` and `o2` appear in the same pack, and thus in the + same MIDX layer. Sort `o1` and `o2` by their offset within their + containing packfile. + +Note that the preferred pack is a property of the MIDX chain, not the +individual layers themselves. Fundamentally we could introduce a +per-layer preferred pack, but this is less relevant now that we can +perform multi-pack reuse across the set of packs in a MIDX. + +=== Reachability bitmaps and incremental MIDXs + +Each layer of an incremental MIDX chain may have its objects (and the +objects from any previous layer in the same MIDX chain) represented in +its own `*.bitmap` file. + +The structure of a `*.bitmap` file belonging to an incremental MIDX +chain is identical to that of a non-incremental MIDX bitmap, or a +classic single-pack bitmap. Since objects are added to the end of the +incremental MIDX's pseudo-pack order (see above), it is possible to +extend a bitmap when appending to the end of a MIDX chain. + +(Note: it is possible likewise to compress a contiguous sequence of MIDX +incremental layers, and their `*.bitmap` files into a single layer and +`*.bitmap`, but this is not yet implemented.) + +The object positions used are global within the pseudo-pack order, so +subsequent layers will have, for example, `m->num_objects_in_base` +number of `0` bits in each of their four type bitmaps. This follows from +the fact that we only write type bitmap entries for objects present in +the layer immediately corresponding to the bitmap). + +Note also that only the bitmap pertaining to the most recent layer in an +incremental MIDX chain is used to store reachability information about +the interesting and uninteresting objects in a reachability query. +Earlier bitmap layers are only used to look up commit and pseudo-merge +bitmaps from that layer, as well as the type-level bitmaps for objects +in that layer. + +To simplify the implementation, type-level bitmaps are iterated +simultaneously, and their results are OR'd together to avoid recursively +calling internal bitmap functions. + Future Work ----------- -- The multi-pack-index allows many packfiles, especially in a context - where repacking is expensive (such as a very large repo), or - unexpected maintenance time is unacceptable (such as a high-demand - build machine). However, the multi-pack-index needs to be rewritten - in full every time. We can extend the format to be incremental, so - writes are fast. By storing a small "tip" multi-pack-index that - points to large "base" MIDX files, we can keep writes fast while - still reducing the number of binary searches required for object - lookups. - - If the multi-pack-index is extended to store a "stable object order" (a function Order(hash) = integer that is constant for a given hash, even as the multi-pack-index is updated) then MIDX bitmaps could be diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 3abfe7d3d7..b981598298 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,6 +1,6 @@ #!/bin/sh -DEF_VER=v2.49.0 +DEF_VER=v2.49.GIT LF=' ' @@ -955,7 +955,7 @@ FOUND_SOURCE_FILES := $(filter-out $(GENERATED_H),$(shell $(SOURCES_CMD))) FOUND_C_SOURCES = $(filter %.c,$(FOUND_SOURCE_FILES)) FOUND_H_SOURCES = $(filter %.h,$(FOUND_SOURCE_FILES)) -COCCI_SOURCES = $(filter-out $(THIRD_PARTY_SOURCES),$(FOUND_C_SOURCES)) +COCCI_SOURCES = $(filter-out $(THIRD_PARTY_SOURCES) reftable/%,$(FOUND_C_SOURCES)) LIB_H = $(FOUND_H_SOURCES) @@ -995,6 +995,7 @@ LIB_OBJS += common-init.o LIB_OBJS += compat/nonblock.o LIB_OBJS += compat/obstack.o LIB_OBJS += compat/terminal.o +LIB_OBJS += compiler-tricks/not-constant.o LIB_OBJS += config.o LIB_OBJS += connect.o LIB_OBJS += connected.o @@ -1041,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 @@ -1242,6 +1244,7 @@ BUILTIN_OBJS += builtin/describe.o BUILTIN_OBJS += builtin/diagnose.o BUILTIN_OBJS += builtin/diff-files.o BUILTIN_OBJS += builtin/diff-index.o +BUILTIN_OBJS += builtin/diff-pairs.o BUILTIN_OBJS += builtin/diff-tree.o BUILTIN_OBJS += builtin/diff.o BUILTIN_OBJS += builtin/difftool.o @@ -1409,7 +1412,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 @@ -2261,6 +2264,10 @@ ifdef WITH_BREAKING_CHANGES BASIC_CFLAGS += -DWITH_BREAKING_CHANGES endif +ifdef CHECK_ASSERTION_SIDE_EFFECTS + BASIC_CFLAGS += -DCHECK_ASSERTION_SIDE_EFFECTS +endif + ifdef INCLUDE_LIBGIT_RS # Enable symbol hiding in contrib/libgit-sys/libgitpub.a without making # us rebuild the whole tree every time we run a Rust build. @@ -1 +1 @@ -Documentation/RelNotes/2.49.0.adoc
\ No newline at end of file +Documentation/RelNotes/2.50.0.adoc
\ No newline at end of file @@ -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); @@ -153,6 +153,7 @@ int cmd_diagnose(int argc, const char **argv, const char *prefix, struct reposit int cmd_diff_files(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_diff_index(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_diff(int argc, const char **argv, const char *prefix, struct repository *repo); +int cmd_diff_pairs(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_diff_tree(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_difftool(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_env__helper(int argc, const char **argv, const char *prefix, struct repository *repo); diff --git a/builtin/am.c b/builtin/am.c index 2921bb89ef..3b61bd4c33 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -31,7 +31,7 @@ #include "preload-index.h" #include "sequencer.h" #include "revision.h" -#include "merge-recursive.h" +#include "merge-ort-wrappers.h" #include "log-tree.h" #include "notes-utils.h" #include "rerere.h" @@ -1638,12 +1638,13 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa o.branch1 = "HEAD"; their_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg); o.branch2 = their_tree_name; + o.ancestor = "constructed fake ancestor"; o.detect_directory_renames = MERGE_DIRECTORY_RENAMES_NONE; if (state->quiet) o.verbosity = 0; - if (merge_recursive_generic(&o, &our_tree, &their_tree, 1, bases, &result)) { + if (merge_ort_generic(&o, &our_tree, &their_tree, 1, bases, &result)) { repo_rerere(the_repository, state->allow_rerere_autoupdate); free(their_tree_name); return error(_("Failed to merge in the changes.")); diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c index e30086c7d4..7f74bc702f 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -5,7 +5,6 @@ * */ -#define USE_THE_REPOSITORY_VARIABLE #define DISABLE_SIGN_COMPARE_WARNINGS #include "builtin.h" @@ -68,10 +67,10 @@ static void write_tempfile_record(const char *name, const char *prefix) } } -static int checkout_file(const char *name, const char *prefix) +static int checkout_file(struct index_state *index, const char *name, const char *prefix) { int namelen = strlen(name); - int pos = index_name_pos(the_repository->index, name, namelen); + int pos = index_name_pos(index, name, namelen); int has_same_name = 0; int is_file = 0; int is_skipped = 1; @@ -81,8 +80,8 @@ static int checkout_file(const char *name, const char *prefix) if (pos < 0) pos = -pos - 1; - while (pos <the_repository->index->cache_nr) { - struct cache_entry *ce =the_repository->index->cache[pos]; + while (pos < index->cache_nr) { + struct cache_entry *ce = index->cache[pos]; if (ce_namelen(ce) != namelen || memcmp(ce->name, name, namelen)) break; @@ -137,13 +136,13 @@ static int checkout_file(const char *name, const char *prefix) return -1; } -static int checkout_all(const char *prefix, int prefix_length) +static int checkout_all(struct index_state *index, const char *prefix, int prefix_length) { int i, errs = 0; struct cache_entry *last_ce = NULL; - for (i = 0; i < the_repository->index->cache_nr ; i++) { - struct cache_entry *ce = the_repository->index->cache[i]; + for (i = 0; i < index->cache_nr ; i++) { + struct cache_entry *ce = index->cache[i]; if (S_ISSPARSEDIR(ce->ce_mode)) { if (!ce_skip_worktree(ce)) @@ -156,8 +155,8 @@ static int checkout_all(const char *prefix, int prefix_length) * first entry inside the expanded sparse directory). */ if (ignore_skip_worktree) { - ensure_full_index(the_repository->index); - ce = the_repository->index->cache[i]; + ensure_full_index(index); + ce = index->cache[i]; } } @@ -213,7 +212,7 @@ static int option_parse_stage(const struct option *opt, int cmd_checkout_index(int argc, const char **argv, const char *prefix, - struct repository *repo UNUSED) + struct repository *repo) { int i; struct lock_file lock_file = LOCK_INIT; @@ -253,19 +252,19 @@ int cmd_checkout_index(int argc, show_usage_with_options_if_asked(argc, argv, builtin_checkout_index_usage, builtin_checkout_index_options); - git_config(git_default_config, NULL); + repo_config(repo, git_default_config, NULL); prefix_length = prefix ? strlen(prefix) : 0; - prepare_repo_settings(the_repository); - the_repository->settings.command_requires_full_index = 0; + prepare_repo_settings(repo); + repo->settings.command_requires_full_index = 0; - if (repo_read_index(the_repository) < 0) { + if (repo_read_index(repo) < 0) { die("invalid cache"); } argc = parse_options(argc, argv, prefix, builtin_checkout_index_options, builtin_checkout_index_usage, 0); - state.istate = the_repository->index; + state.istate = repo->index; state.force = force; state.quiet = quiet; state.not_new = not_new; @@ -285,8 +284,8 @@ int cmd_checkout_index(int argc, */ if (index_opt && !state.base_dir_len && !to_tempfile) { state.refresh_cache = 1; - state.istate = the_repository->index; - repo_hold_locked_index(the_repository, &lock_file, + state.istate = repo->index; + repo_hold_locked_index(repo, &lock_file, LOCK_DIE_ON_ERROR); } @@ -304,7 +303,7 @@ int cmd_checkout_index(int argc, if (read_from_stdin) die("git checkout-index: don't mix '--stdin' and explicit filenames"); p = prefix_path(prefix, prefix_length, arg); - err |= checkout_file(p, prefix); + err |= checkout_file(repo->index, p, prefix); free(p); } @@ -326,7 +325,7 @@ int cmd_checkout_index(int argc, strbuf_swap(&buf, &unquoted); } p = prefix_path(prefix, prefix_length, buf.buf); - err |= checkout_file(p, prefix); + err |= checkout_file(repo->index, p, prefix); free(p); } strbuf_release(&unquoted); @@ -334,7 +333,7 @@ int cmd_checkout_index(int argc, } if (all) - err |= checkout_all(prefix, prefix_length); + err |= checkout_all(repo->index, prefix, prefix_length); if (pc_workers > 1) err |= run_parallel_checkout(&state, pc_workers, pc_threshold, @@ -344,7 +343,7 @@ int cmd_checkout_index(int argc, return 1; if (is_lock_file_locked(&lock_file) && - write_locked_index(the_repository->index, &lock_file, COMMIT_LOCK)) + write_locked_index(repo->index, &lock_file, COMMIT_LOCK)) die("Unable to write new index file"); return 0; } diff --git a/builtin/checkout.c b/builtin/checkout.c index 01ea9ff8b2..2e7486cf65 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -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; diff --git a/builtin/clone.c b/builtin/clone.c index f14229abf4..7d5b64a6a0 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -342,6 +342,8 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, strbuf_setlen(src, src_len); die(_("failed to iterate over '%s'"), src->buf); } + + dir_iterator_free(iter); } static void clone_local(const char *src_repo, const char *dest_repo) @@ -450,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; @@ -690,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)) { @@ -1523,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-pairs.c b/builtin/diff-pairs.c new file mode 100644 index 0000000000..71c045331a --- /dev/null +++ b/builtin/diff-pairs.c @@ -0,0 +1,207 @@ +#include "builtin.h" +#include "config.h" +#include "diff.h" +#include "diffcore.h" +#include "gettext.h" +#include "hash.h" +#include "hex.h" +#include "object.h" +#include "parse-options.h" +#include "revision.h" +#include "strbuf.h" + +static unsigned parse_mode_or_die(const char *mode, const char **end) +{ + uint16_t ret; + + *end = parse_mode(mode, &ret); + if (!*end) + die(_("unable to parse mode: %s"), mode); + return ret; +} + +static void parse_oid_or_die(const char *hex, struct object_id *oid, + const char **end, const struct git_hash_algo *algop) +{ + if (parse_oid_hex_algop(hex, oid, end, algop) || *(*end)++ != ' ') + die(_("unable to parse object id: %s"), hex); +} + +int cmd_diff_pairs(int argc, const char **argv, const char *prefix, + struct repository *repo) +{ + struct strbuf path_dst = STRBUF_INIT; + struct strbuf path = STRBUF_INIT; + struct strbuf meta = STRBUF_INIT; + struct option *parseopts; + struct rev_info revs; + int line_term = '\0'; + int ret; + + const char * const builtin_diff_pairs_usage[] = { + N_("git diff-pairs -z [<diff-options>]"), + NULL + }; + struct option builtin_diff_pairs_options[] = { + OPT_END() + }; + + repo_init_revisions(repo, &revs, prefix); + + /* + * Diff options are usually parsed implicitly as part of + * setup_revisions(). Explicitly handle parsing to ensure options are + * printed in the usage message. + */ + parseopts = add_diff_options(builtin_diff_pairs_options, &revs.diffopt); + show_usage_with_options_if_asked(argc, argv, builtin_diff_pairs_usage, parseopts); + + repo_config(repo, git_diff_basic_config, NULL); + revs.diffopt.no_free = 1; + revs.disable_stdin = 1; + revs.abbrev = 0; + revs.diff = 1; + + argc = parse_options(argc, argv, prefix, parseopts, builtin_diff_pairs_usage, + PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_DASHDASH); + + if (setup_revisions(argc, argv, &revs, NULL) > 1) + usagef(_("unrecognized argument: %s"), argv[0]); + + /* + * With the -z option, both command input and raw output are + * NUL-delimited (this mode does not affect patch output). At present + * only NUL-delimited raw diff formatted input is supported. + */ + if (revs.diffopt.line_termination) + usage(_("working without -z is not supported")); + + if (revs.prune_data.nr) + usage(_("pathspec arguments not supported")); + + if (revs.pending.nr || revs.max_count != -1 || + revs.min_age != (timestamp_t)-1 || + revs.max_age != (timestamp_t)-1) + usage(_("revision arguments not allowed")); + + if (!revs.diffopt.output_format) + revs.diffopt.output_format = DIFF_FORMAT_PATCH; + + /* + * If rename detection is not requested, use rename information from the + * raw diff formatted input. Setting skip_resolving_statuses ensures + * diffcore_std() does not mess with rename information already present + * in queued filepairs. + */ + if (!revs.diffopt.detect_rename) + revs.diffopt.skip_resolving_statuses = 1; + + while (1) { + struct object_id oid_a, oid_b; + struct diff_filepair *pair; + unsigned mode_a, mode_b; + const char *p; + char status; + + if (strbuf_getwholeline(&meta, stdin, line_term) == EOF) + break; + + p = meta.buf; + if (!*p) { + diffcore_std(&revs.diffopt); + diff_flush(&revs.diffopt); + /* + * When the diff queue is explicitly flushed, append a + * NUL byte to separate batches of diffs. + */ + fputc('\0', revs.diffopt.file); + fflush(revs.diffopt.file); + continue; + } + + if (*p != ':') + die(_("invalid raw diff input")); + p++; + + mode_a = parse_mode_or_die(p, &p); + mode_b = parse_mode_or_die(p, &p); + + if (S_ISDIR(mode_a) || S_ISDIR(mode_b)) + die(_("tree objects not supported")); + + parse_oid_or_die(p, &oid_a, &p, repo->hash_algo); + parse_oid_or_die(p, &oid_b, &p, repo->hash_algo); + + status = *p++; + + if (strbuf_getwholeline(&path, stdin, line_term) == EOF) + die(_("got EOF while reading path")); + + switch (status) { + case DIFF_STATUS_ADDED: + pair = diff_queue_addremove(&diff_queued_diff, + &revs.diffopt, '+', mode_b, + &oid_b, 1, path.buf, 0); + if (pair) + pair->status = status; + break; + + case DIFF_STATUS_DELETED: + pair = diff_queue_addremove(&diff_queued_diff, + &revs.diffopt, '-', mode_a, + &oid_a, 1, path.buf, 0); + if (pair) + pair->status = status; + break; + + case DIFF_STATUS_TYPE_CHANGED: + case DIFF_STATUS_MODIFIED: + pair = diff_queue_change(&diff_queued_diff, &revs.diffopt, + mode_a, mode_b, &oid_a, &oid_b, + 1, 1, path.buf, 0, 0); + if (pair) + pair->status = status; + break; + + case DIFF_STATUS_RENAMED: + case DIFF_STATUS_COPIED: { + struct diff_filespec *a, *b; + unsigned int score; + + if (strbuf_getwholeline(&path_dst, stdin, line_term) == EOF) + die(_("got EOF while reading destination path")); + + a = alloc_filespec(path.buf); + b = alloc_filespec(path_dst.buf); + fill_filespec(a, &oid_a, 1, mode_a); + fill_filespec(b, &oid_b, 1, mode_b); + + pair = diff_queue(&diff_queued_diff, a, b); + + if (strtoul_ui(p, 10, &score)) + die(_("unable to parse rename/copy score: %s"), p); + + pair->score = score * MAX_SCORE / 100; + pair->status = status; + pair->renamed_pair = 1; + } + break; + + default: + die(_("unknown diff status: %c"), status); + } + } + + revs.diffopt.no_free = 0; + diffcore_std(&revs.diffopt); + diff_flush(&revs.diffopt); + ret = diff_result_code(&revs); + + strbuf_release(&path_dst); + strbuf_release(&path); + strbuf_release(&meta); + release_revisions(&revs); + FREE_AND_NULL(parseopts); + + return ret; +} 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 a5c82eef1d..170126d41a 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -35,8 +35,11 @@ static const char *fast_export_usage[] = { NULL }; +enum sign_mode { SIGN_ABORT, SIGN_VERBATIM, SIGN_STRIP, SIGN_WARN_VERBATIM, SIGN_WARN_STRIP }; + static int progress; -static enum signed_tag_mode { SIGNED_TAG_ABORT, VERBATIM, WARN, WARN_STRIP, STRIP } signed_tag_mode = SIGNED_TAG_ABORT; +static enum sign_mode signed_tag_mode = SIGN_ABORT; +static enum sign_mode signed_commit_mode = SIGN_ABORT; static enum tag_of_filtered_mode { TAG_FILTERING_ABORT, DROP, REWRITE } tag_of_filtered_mode = TAG_FILTERING_ABORT; static enum reencode_mode { REENCODE_ABORT, REENCODE_YES, REENCODE_NO } reencode_mode = REENCODE_ABORT; static int fake_missing_tagger; @@ -53,23 +56,24 @@ static int anonymize; static struct hashmap anonymized_seeds; static struct revision_sources revision_sources; -static int parse_opt_signed_tag_mode(const struct option *opt, +static int parse_opt_sign_mode(const struct option *opt, const char *arg, int unset) { - enum signed_tag_mode *val = opt->value; - - if (unset || !strcmp(arg, "abort")) - *val = SIGNED_TAG_ABORT; + enum sign_mode *val = opt->value; + if (unset) + return 0; + else if (!strcmp(arg, "abort")) + *val = SIGN_ABORT; else if (!strcmp(arg, "verbatim") || !strcmp(arg, "ignore")) - *val = VERBATIM; - else if (!strcmp(arg, "warn")) - *val = WARN; + *val = SIGN_VERBATIM; + else if (!strcmp(arg, "warn-verbatim") || !strcmp(arg, "warn")) + *val = SIGN_WARN_VERBATIM; else if (!strcmp(arg, "warn-strip")) - *val = WARN_STRIP; + *val = SIGN_WARN_STRIP; else if (!strcmp(arg, "strip")) - *val = STRIP; + *val = SIGN_STRIP; else - return error("Unknown signed-tags mode: %s", arg); + return error("Unknown %s mode: %s", opt->long_name, arg); return 0; } @@ -510,21 +514,6 @@ static void show_filemodify(struct diff_queue_struct *q, } } -static const char *find_encoding(const char *begin, const char *end) -{ - const char *needle = "\nencoding "; - char *bol, *eol; - - bol = memmem(begin, end ? end - begin : strlen(begin), - needle, strlen(needle)); - if (!bol) - return NULL; - bol += strlen(needle); - eol = strchrnul(bol, '\n'); - *eol = '\0'; - return bol; -} - static char *anonymize_ref_component(void) { static int counter; @@ -626,13 +615,53 @@ static void anonymize_ident_line(const char **beg, const char **end) *end = out->buf + out->len; } +/* + * find_commit_multiline_header is similar to find_commit_header, + * except that it handles multi-line headers, rather than simply + * returning the first line of the header. + * + * The returned string has had the ' ' line continuation markers + * removed, and points to allocated memory that must be free()d (not + * to memory within 'msg'). + * + * If the header is found, then *end is set to point at the '\n' in + * msg that immediately follows the header value. + */ +static const char *find_commit_multiline_header(const char *msg, + const char *key, + const char **end) +{ + struct strbuf val = STRBUF_INIT; + const char *bol, *eol; + size_t len; + + bol = find_commit_header(msg, key, &len); + if (!bol) + return NULL; + eol = bol + len; + strbuf_add(&val, bol, len); + + while (eol[0] == '\n' && eol[1] == ' ') { + bol = eol + 2; + eol = strchrnul(bol, '\n'); + strbuf_addch(&val, '\n'); + strbuf_add(&val, bol, eol - bol); + } + + *end = eol; + return strbuf_detach(&val, NULL); +} + static void handle_commit(struct commit *commit, struct rev_info *rev, struct string_list *paths_of_changed_objects) { int saved_output_format = rev->diffopt.output_format; - const char *commit_buffer; + const char *commit_buffer, *commit_buffer_cursor; const char *author, *author_end, *committer, *committer_end; - const char *encoding, *message; + const char *encoding = NULL; + size_t encoding_len; + const char *signature_alg = NULL, *signature = NULL; + const char *message; char *reencoded = NULL; struct commit_list *p; const char *refname; @@ -641,21 +670,43 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, rev->diffopt.output_format = DIFF_FORMAT_CALLBACK; parse_commit_or_die(commit); - commit_buffer = repo_get_commit_buffer(the_repository, commit, NULL); - author = strstr(commit_buffer, "\nauthor "); + commit_buffer_cursor = commit_buffer = repo_get_commit_buffer(the_repository, commit, NULL); + + author = strstr(commit_buffer_cursor, "\nauthor "); if (!author) die("could not find author in commit %s", oid_to_hex(&commit->object.oid)); author++; - author_end = strchrnul(author, '\n'); - committer = strstr(author_end, "\ncommitter "); + commit_buffer_cursor = author_end = strchrnul(author, '\n'); + + committer = strstr(commit_buffer_cursor, "\ncommitter "); if (!committer) die("could not find committer in commit %s", oid_to_hex(&commit->object.oid)); committer++; - committer_end = strchrnul(committer, '\n'); - message = strstr(committer_end, "\n\n"); - encoding = find_encoding(committer_end, message); + commit_buffer_cursor = committer_end = strchrnul(committer, '\n'); + + /* + * find_commit_header() and find_commit_multiline_header() get + * a `+ 1` because commit_buffer_cursor points at the trailing + * "\n" at the end of the previous line, but they want a + * pointer to the beginning of the next line. + */ + + if (*commit_buffer_cursor == '\n') { + encoding = find_commit_header(commit_buffer_cursor + 1, "encoding", &encoding_len); + if (encoding) + commit_buffer_cursor = encoding + encoding_len; + } + + if (*commit_buffer_cursor == '\n') { + if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig", &commit_buffer_cursor))) + signature_alg = "sha1"; + else if ((signature = find_commit_multiline_header(commit_buffer_cursor + 1, "gpgsig-sha256", &commit_buffer_cursor))) + signature_alg = "sha256"; + } + + message = strstr(commit_buffer_cursor, "\n\n"); if (message) message += 2; @@ -694,16 +745,20 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, if (anonymize) { reencoded = anonymize_commit_message(); } else if (encoding) { - switch(reencode_mode) { + char *buf; + switch (reencode_mode) { case REENCODE_YES: - reencoded = reencode_string(message, "UTF-8", encoding); + buf = xstrfmt("%.*s", (int)encoding_len, encoding); + reencoded = reencode_string(message, "UTF-8", buf); + free(buf); break; case REENCODE_NO: break; case REENCODE_ABORT: - die("Encountered commit-specific encoding %s in commit " + die("Encountered commit-specific encoding %.*s in commit " "%s; use --reencode=[yes|no] to handle it", - encoding, oid_to_hex(&commit->object.oid)); + (int)encoding_len, encoding, + oid_to_hex(&commit->object.oid)); } } if (!commit->parents) @@ -714,8 +769,33 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, printf("%.*s\n%.*s\n", (int)(author_end - author), author, (int)(committer_end - committer), committer); + if (signature) { + switch (signed_commit_mode) { + case SIGN_ABORT: + die("encountered signed commit %s; use " + "--signed-commits=<mode> to handle it", + oid_to_hex(&commit->object.oid)); + case SIGN_WARN_VERBATIM: + warning("exporting signed commit %s", + oid_to_hex(&commit->object.oid)); + /* fallthru */ + case SIGN_VERBATIM: + printf("gpgsig %s\ndata %u\n%s", + signature_alg, + (unsigned)strlen(signature), + signature); + break; + case SIGN_WARN_STRIP: + warning("stripping signature from commit %s", + oid_to_hex(&commit->object.oid)); + /* fallthru */ + case SIGN_STRIP: + break; + } + free((char *)signature); + } if (!reencoded && encoding) - printf("encoding %s\n", encoding); + printf("encoding %.*s\n", (int)encoding_len, encoding); printf("data %u\n%s", (unsigned)(reencoded ? strlen(reencoded) : message @@ -828,22 +908,22 @@ static void handle_tag(const char *name, struct tag *tag) const char *signature = strstr(message, "\n-----BEGIN PGP SIGNATURE-----\n"); if (signature) - switch(signed_tag_mode) { - case SIGNED_TAG_ABORT: + switch (signed_tag_mode) { + case SIGN_ABORT: die("encountered signed tag %s; use " "--signed-tags=<mode> to handle it", oid_to_hex(&tag->object.oid)); - case WARN: + case SIGN_WARN_VERBATIM: warning("exporting signed tag %s", oid_to_hex(&tag->object.oid)); /* fallthru */ - case VERBATIM: + case SIGN_VERBATIM: break; - case WARN_STRIP: + case SIGN_WARN_STRIP: warning("stripping signature from tag %s", oid_to_hex(&tag->object.oid)); /* fallthru */ - case STRIP: + case SIGN_STRIP: message_size = signature + 1 - message; break; } @@ -853,7 +933,7 @@ static void handle_tag(const char *name, struct tag *tag) tagged = tag->tagged; tagged_mark = get_object_mark(tagged); if (!tagged_mark) { - switch(tag_of_filtered_mode) { + switch (tag_of_filtered_mode) { case TAG_FILTERING_ABORT: die("tag %s tags unexported object; use " "--tag-of-filtered-object=<mode> to handle it", @@ -869,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; } @@ -883,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); @@ -965,7 +1045,7 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info) continue; } - switch(commit->object.type) { + switch (commit->object.type) { case OBJ_COMMIT: break; case OBJ_BLOB: @@ -1023,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; } @@ -1042,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 */ @@ -1153,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))); } } @@ -1189,6 +1269,7 @@ int cmd_fast_export(int argc, const char *prefix, struct repository *repo UNUSED) { + const char *env_signed_commits_noabort; struct rev_info revs; struct commit *commit; char *export_filename = NULL, @@ -1202,7 +1283,10 @@ int cmd_fast_export(int argc, N_("show progress after <n> objects")), OPT_CALLBACK(0, "signed-tags", &signed_tag_mode, N_("mode"), N_("select handling of signed tags"), - parse_opt_signed_tag_mode), + parse_opt_sign_mode), + OPT_CALLBACK(0, "signed-commits", &signed_commit_mode, N_("mode"), + N_("select handling of signed commits"), + parse_opt_sign_mode), OPT_CALLBACK(0, "tag-of-filtered-object", &tag_of_filtered_mode, N_("mode"), N_("select handling of tags that tag filtered objects"), parse_opt_tag_of_filtered_mode), @@ -1243,6 +1327,10 @@ int cmd_fast_export(int argc, if (argc == 1) usage_with_options (fast_export_usage, options); + env_signed_commits_noabort = getenv("FAST_EXPORT_SIGNED_COMMITS_NOABORT"); + if (env_signed_commits_noabort && *env_signed_commits_noabort) + signed_commit_mode = SIGN_WARN_STRIP; + /* we handle encodings */ git_config(git_default_config, NULL); diff --git a/builtin/fast-import.c b/builtin/fast-import.c index 397a6f46ad..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) { @@ -2719,10 +2719,13 @@ static struct hash_list *parse_merge(unsigned int *count) static void parse_new_commit(const char *arg) { + static struct strbuf sig = STRBUF_INIT; static struct strbuf msg = STRBUF_INIT; + struct string_list siglines = STRING_LIST_INIT_NODUP; struct branch *b; char *author = NULL; char *committer = NULL; + char *sig_alg = NULL; char *encoding = NULL; struct hash_list *merge_list = NULL; unsigned int merge_count; @@ -2746,6 +2749,13 @@ static void parse_new_commit(const char *arg) } if (!committer) die("Expected committer but didn't get one"); + if (skip_prefix(command_buf.buf, "gpgsig ", &v)) { + sig_alg = xstrdup(v); + read_next_command(); + parse_data(&sig, 0, NULL); + read_next_command(); + } else + strbuf_setlen(&sig, 0); if (skip_prefix(command_buf.buf, "encoding ", &v)) { encoding = xstrdup(v); read_next_command(); @@ -2819,10 +2829,23 @@ static void parse_new_commit(const char *arg) strbuf_addf(&new_data, "encoding %s\n", encoding); + if (sig_alg) { + if (!strcmp(sig_alg, "sha1")) + strbuf_addstr(&new_data, "gpgsig "); + else if (!strcmp(sig_alg, "sha256")) + strbuf_addstr(&new_data, "gpgsig-sha256 "); + else + die("Expected gpgsig algorithm sha1 or sha256, got %s", sig_alg); + string_list_split_in_place(&siglines, sig.buf, "\n", -1); + strbuf_add_separated_string_list(&new_data, "\n ", &siglines); + strbuf_addch(&new_data, '\n'); + } strbuf_addch(&new_data, '\n'); strbuf_addbuf(&new_data, &msg); + string_list_clear(&siglines, 1); free(author); free(committer); + free(sig_alg); free(encoding); if (!store_object(OBJ_COMMIT, &new_data, NULL, &b->oid, next_mark)) @@ -3402,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 95fd0018b9..1c6573345e 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -586,7 +586,7 @@ static struct ref *get_ref_map(struct remote *remote, struct refspec_item tag_refspec; /* also fetch all tags */ - refspec_item_init(&tag_refspec, TAG_REFSPEC, 0); + refspec_item_init_push(&tag_refspec, TAG_REFSPEC); get_fetch_map(remote_refs, &tag_refspec, &tail, 0); refspec_item_clear(&tag_refspec); } else if (tags == TAGS_DEFAULT && *autotags) { @@ -1638,7 +1638,7 @@ 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)); } @@ -1718,7 +1718,6 @@ static int do_fetch(struct transport *transport, const struct ref *remote_refs; struct transport_ls_refs_options transport_ls_refs_options = TRANSPORT_LS_REFS_OPTIONS_INIT; - int must_list_refs = 1; struct fetch_head fetch_head = { 0 }; struct strbuf err = STRBUF_INIT; @@ -1737,21 +1736,7 @@ static int do_fetch(struct transport *transport, } if (rs->nr) { - int i; - refspec_ref_prefixes(rs, &transport_ls_refs_options.ref_prefixes); - - /* - * We can avoid listing refs if all of them are exact - * OIDs - */ - must_list_refs = 0; - for (i = 0; i < rs->nr; i++) { - if (!rs->items[i].exact_sha1) { - must_list_refs = 1; - break; - } - } } else { struct branch *branch = branch_get(NULL); @@ -1766,23 +1751,30 @@ static int do_fetch(struct transport *transport, branch->merge[i]->src); } } - } - if (tags == TAGS_SET || tags == TAGS_DEFAULT) { - must_list_refs = 1; - if (transport_ls_refs_options.ref_prefixes.nr) - strvec_push(&transport_ls_refs_options.ref_prefixes, - "refs/tags/"); - } - - if (uses_remote_tracking(transport, rs)) { - must_list_refs = 1; - if (transport_ls_refs_options.ref_prefixes.nr) + /* + * If there are no refs specified to fetch, then we just + * fetch HEAD; mention that to narrow the advertisement. + */ + if (!transport_ls_refs_options.ref_prefixes.nr) strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD"); } - if (must_list_refs) { + if (tags == TAGS_SET || tags == TAGS_DEFAULT) + strvec_push(&transport_ls_refs_options.ref_prefixes, + "refs/tags/"); + + if (transport_ls_refs_options.ref_prefixes.nr && + uses_remote_tracking(transport, rs)) + strvec_push(&transport_ls_refs_options.ref_prefixes, + "HEAD"); + + /* + * Only initiate ref listing if we have at least one ref we want to + * know about. + */ + if (transport_ls_refs_options.ref_prefixes.nr) { trace2_region_enter("fetch", "remote_refs", the_repository); remote_refs = transport_get_remote_refs(transport, &transport_ls_refs_options); diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 8085ebd8fe..3d2207ec77 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -1,4 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE #include "builtin.h" #include "commit.h" #include "config.h" @@ -20,7 +19,7 @@ static char const * const for_each_ref_usage[] = { int cmd_for_each_ref(int argc, const char **argv, const char *prefix, - struct repository *repo UNUSED) + struct repository *repo) { struct ref_sorting *sorting; struct string_list sorting_options = STRING_LIST_INIT_DUP; @@ -63,7 +62,7 @@ int cmd_for_each_ref(int argc, format.format = "%(objectname) %(objecttype)\t%(refname)"; - git_config(git_default_config, NULL); + repo_config(repo, git_default_config, NULL); /* Set default (refname) sorting */ string_list_append(&sorting_options, "refname"); diff --git a/builtin/fsck.c b/builtin/fsck.c index eea1d43647..9c8a6d6a8d 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -50,6 +50,7 @@ static int verbose; static int show_progress = -1; static int show_dangling = 1; static int name_objects; +static int check_references = 1; #define ERROR_OBJECT 01 #define ERROR_REACHABLE 02 #define ERROR_PACK 04 @@ -399,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); @@ -625,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); @@ -905,11 +906,37 @@ static int check_pack_rev_indexes(struct repository *r, int show_progress) return res; } +static void fsck_refs(struct repository *r) +{ + struct child_process refs_verify = CHILD_PROCESS_INIT; + struct progress *progress = NULL; + + if (show_progress) + progress = start_progress(r, _("Checking ref database"), 1); + + if (verbose) + fprintf_ln(stderr, _("Checking ref database")); + + child_process_init(&refs_verify); + refs_verify.git_cmd = 1; + strvec_pushl(&refs_verify.args, "refs", "verify", NULL); + if (verbose) + strvec_push(&refs_verify.args, "--verbose"); + if (check_strict) + strvec_push(&refs_verify.args, "--strict"); + + if (run_command(&refs_verify)) + errors_found |= ERROR_REFS; + + display_progress(progress, 1); + stop_progress(&progress); +} + static char const * const fsck_usage[] = { N_("git fsck [--tags] [--root] [--unreachable] [--cache] [--no-reflogs]\n" " [--[no-]full] [--strict] [--verbose] [--lost-found]\n" " [--[no-]dangling] [--[no-]progress] [--connectivity-only]\n" - " [--[no-]name-objects] [<object>...]"), + " [--[no-]name-objects] [--[no-]references] [<object>...]"), NULL }; @@ -928,6 +955,7 @@ static struct option fsck_opts[] = { N_("write dangling objects in .git/lost-found")), OPT_BOOL(0, "progress", &show_progress, N_("show progress")), OPT_BOOL(0, "name-objects", &name_objects, N_("show verbose names for reachable objects")), + OPT_BOOL(0, "references", &check_references, N_("check reference database consistency")), OPT_END(), }; @@ -970,6 +998,9 @@ int cmd_fsck(int argc, git_config(git_fsck_config, &fsck_obj_options); prepare_repo_settings(the_repository); + if (check_references) + fsck_refs(the_repository); + if (connectivity_only) { for_each_loose_object(mark_loose_for_connectivity, NULL, 0); for_each_packed_object(the_repository, diff --git a/builtin/gc.c b/builtin/gc.c index 99431fd467..817081e1a5 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -1029,6 +1029,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 +1163,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 +1188,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 +1209,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 +1277,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 +1295,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 +1353,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()); 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 52cc97d52c..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)); @@ -1286,6 +1288,7 @@ static void parse_pack_objects(unsigned char *hash) /* Check pack integrity */ flush(); + the_hash_algo->init_fn(&tmp_ctx); git_hash_clone(&tmp_ctx, &input_ctx); git_hash_final(hash, &tmp_ctx); if (!hasheq(fill(the_hash_algo->rawsz), hash, the_repository->hash_algo)) @@ -1381,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", @@ -2088,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 a4431429b7..be74f0a03b 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -6,7 +6,6 @@ * Copyright (C) Linus Torvalds, 2005 */ -#define USE_THE_REPOSITORY_VARIABLE #define DISABLE_SIGN_COMPARE_WARNINGS #include "builtin.h" @@ -234,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) @@ -245,12 +245,13 @@ static void show_submodule(struct repository *superproject, repo_clear(&subrepo); } -static void expand_objectsize(struct strbuf *line, const struct object_id *oid, +static void expand_objectsize(struct repository *repo, struct strbuf *line, + const struct object_id *oid, const enum object_type type, unsigned int padded) { if (type == OBJ_BLOB) { unsigned long size; - if (oid_object_info(the_repository, oid, &size) < 0) + if (oid_object_info(repo, oid, &size) < 0) die(_("could not get object info about '%s'"), oid_to_hex(oid)); if (padded) @@ -283,10 +284,10 @@ static void show_ce_fmt(struct repository *repo, const struct cache_entry *ce, else if (skip_prefix(format, "(objecttype)", &format)) strbuf_addstr(&sb, type_name(object_type(ce->ce_mode))); else if (skip_prefix(format, "(objectsize:padded)", &format)) - expand_objectsize(&sb, &ce->oid, + expand_objectsize(repo, &sb, &ce->oid, object_type(ce->ce_mode), 1); else if (skip_prefix(format, "(objectsize)", &format)) - expand_objectsize(&sb, &ce->oid, + expand_objectsize(repo, &sb, &ce->oid, object_type(ce->ce_mode), 0); else if (skip_prefix(format, "(stage)", &format)) strbuf_addf(&sb, "%d", ce_stage(ce)); @@ -348,7 +349,7 @@ static void show_ce(struct repository *repo, struct dir_struct *dir, } } -static void show_ru_info(struct index_state *istate) +static void show_ru_info(struct repository *repo, struct index_state *istate) { struct string_list_item *item; @@ -370,7 +371,7 @@ static void show_ru_info(struct index_state *istate) if (!ui->mode[i]) continue; printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i], - repo_find_unique_abbrev(the_repository, &ui->oid[i], abbrev), + repo_find_unique_abbrev(repo, &ui->oid[i], abbrev), i + 1); write_name(path); } @@ -567,7 +568,7 @@ static int option_parse_exclude_standard(const struct option *opt, int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix, - struct repository *repo UNUSED) + struct repository *repo) { int require_work_tree = 0, show_tag = 0, i; char *max_prefix; @@ -647,15 +648,15 @@ int cmd_ls_files(int argc, show_usage_with_options_if_asked(argc, argv, ls_files_usage, builtin_ls_files_options); - prepare_repo_settings(the_repository); - the_repository->settings.command_requires_full_index = 0; + prepare_repo_settings(repo); + repo->settings.command_requires_full_index = 0; prefix = cmd_prefix; if (prefix) prefix_len = strlen(prefix); - git_config(git_default_config, NULL); + repo_config(repo, git_default_config, NULL); - if (repo_read_index(the_repository) < 0) + if (repo_read_index(repo) < 0) die("index file corrupt"); argc = parse_options(argc, argv, prefix, builtin_ls_files_options, @@ -724,7 +725,7 @@ int cmd_ls_files(int argc, max_prefix = common_prefix(&pathspec); max_prefix_len = get_common_prefix_len(max_prefix); - prune_index(the_repository->index, max_prefix, max_prefix_len); + prune_index(repo->index, max_prefix, max_prefix_len); /* Treat unmatching pathspec elements as errors */ if (pathspec.nr && error_unmatch) @@ -748,13 +749,13 @@ int cmd_ls_files(int argc, */ if (show_stage || show_unmerged) die(_("options '%s' and '%s' cannot be used together"), "ls-files --with-tree", "-s/-u"); - overlay_tree_on_index(the_repository->index, with_tree, max_prefix); + overlay_tree_on_index(repo->index, with_tree, max_prefix); } - show_files(the_repository, &dir); + show_files(repo, &dir); if (show_resolve_undo) - show_ru_info(the_repository->index); + show_ru_info(repo, repo->index); if (ps_matched && report_path_error(ps_matched, &pathspec)) { fprintf(stderr, "Did you forget to 'git add'?\n"); diff --git a/builtin/name-rev.c b/builtin/name-rev.c index beac166b5c..ff199638de 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -567,7 +567,11 @@ int cmd_name_rev(int argc, { struct mem_pool string_pool; struct object_array revs = OBJECT_ARRAY_INIT; - int all = 0, annotate_stdin = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0; + +#ifndef WITH_BREAKING_CHANGES + int transform_stdin = 0; +#endif + int all = 0, annotate_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0; struct name_ref_data data = { 0, 0, STRING_LIST_INIT_NODUP, STRING_LIST_INIT_NODUP }; struct option opts[] = { OPT_BOOL(0, "name-only", &data.name_only, N_("print only ref-based names (no object names)")), @@ -578,11 +582,13 @@ int cmd_name_rev(int argc, N_("ignore refs matching <pattern>")), OPT_GROUP(""), OPT_BOOL(0, "all", &all, N_("list all commits reachable from all refs")), +#ifndef WITH_BREAKING_CHANGES OPT_BOOL_F(0, "stdin", &transform_stdin, N_("deprecated: use --annotate-stdin instead"), PARSE_OPT_HIDDEN), +#endif /* WITH_BREAKING_CHANGES */ OPT_BOOL(0, "annotate-stdin", &annotate_stdin, N_("annotate text from stdin")), OPT_BOOL(0, "undefined", &allow_undefined, N_("allow to print `undefined` names (default)")), OPT_BOOL(0, "always", &always, @@ -597,12 +603,14 @@ int cmd_name_rev(int argc, git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, opts, name_rev_usage, 0); +#ifndef WITH_BREAKING_CHANGES if (transform_stdin) { warning("--stdin is deprecated. Please use --annotate-stdin instead, " "which is functionally equivalent.\n" "This option will be removed in a future release."); annotate_stdin = 1; } +#endif if (all + annotate_stdin + !!argc > 1) { error("Specify either a list, or --all, not both!"); @@ -667,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 58a9b16126..4764aa1b8c 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -206,6 +206,7 @@ static int have_non_local_packs; static int incremental; static int ignore_packed_keep_on_disk; static int ignore_packed_keep_in_core; +static int ignore_packed_keep_in_core_has_cruft; static int allow_ofs_delta; static struct pack_idx_option pack_idx_opts; static const char *base_name; @@ -499,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; @@ -1311,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); @@ -1397,7 +1400,8 @@ static void write_pack_file(void) if (write_bitmap_index) { bitmap_writer_init(&bitmap_writer, - the_repository, &to_pack); + the_repository, &to_pack, + NULL); bitmap_writer_set_checksum(&bitmap_writer, hash); bitmap_writer_build_type_index(&bitmap_writer, written_list); @@ -1406,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, @@ -1502,8 +1506,60 @@ static int have_duplicate_entry(const struct object_id *oid, return 1; } +static int want_cruft_object_mtime(struct repository *r, + const struct object_id *oid, + unsigned flags, uint32_t mtime) +{ + struct packed_git **cache; + + for (cache = kept_pack_cache(r, flags); *cache; cache++) { + struct packed_git *p = *cache; + off_t ofs; + uint32_t candidate_mtime; + + ofs = find_pack_entry_one(oid, p); + if (!ofs) + continue; + + /* + * We have a copy of the object 'oid' in a non-cruft + * pack. We can avoid packing an additional copy + * regardless of what the existing copy's mtime is since + * it is outside of a cruft pack. + */ + if (!p->is_cruft) + return 0; + + /* + * If we have a copy of the object 'oid' in a cruft + * pack, then either read the cruft pack's mtime for + * that object, or, if that can't be loaded, assume the + * pack's mtime itself. + */ + if (!load_pack_mtimes(p)) { + uint32_t pos; + if (offset_to_pack_pos(p, ofs, &pos) < 0) + continue; + candidate_mtime = nth_packed_mtime(p, pos); + } else { + candidate_mtime = p->mtime; + } + + /* + * We have a surviving copy of the object in a cruft + * pack whose mtime is greater than or equal to the one + * we are considering. We can thus avoid packing an + * additional copy of that object. + */ + if (mtime <= candidate_mtime) + return 0; + } + + return -1; +} + static int want_found_object(const struct object_id *oid, int exclude, - struct packed_git *p) + struct packed_git *p, uint32_t mtime) { if (exclude) return 1; @@ -1553,12 +1609,29 @@ static int want_found_object(const struct object_id *oid, int exclude, if (ignore_packed_keep_in_core) flags |= IN_CORE_KEEP_PACKS; - if (ignore_packed_keep_on_disk && p->pack_keep) - return 0; - if (ignore_packed_keep_in_core && p->pack_keep_in_core) - return 0; - if (has_object_kept_pack(p->repo, oid, flags)) - return 0; + /* + * If the object is in a pack that we want to ignore, *and* we + * don't have any cruft packs that are being retained, we can + * abort quickly. + */ + if (!ignore_packed_keep_in_core_has_cruft) { + if (ignore_packed_keep_on_disk && p->pack_keep) + return 0; + if (ignore_packed_keep_in_core && p->pack_keep_in_core) + return 0; + if (has_object_kept_pack(p->repo, oid, flags)) + return 0; + } else { + /* + * But if there is at least one cruft pack which + * is being kept, we only want to include the + * provided object if it has a strictly greater + * mtime than any existing cruft copy. + */ + if (!want_cruft_object_mtime(p->repo, oid, flags, + mtime)) + return 0; + } } /* @@ -1577,7 +1650,8 @@ static int want_object_in_pack_one(struct packed_git *p, const struct object_id *oid, int exclude, struct packed_git **found_pack, - off_t *found_offset) + off_t *found_offset, + uint32_t found_mtime) { off_t offset; @@ -1593,7 +1667,7 @@ static int want_object_in_pack_one(struct packed_git *p, *found_offset = offset; *found_pack = p; } - return want_found_object(oid, exclude, p); + return want_found_object(oid, exclude, p, found_mtime); } return -1; } @@ -1607,10 +1681,11 @@ static int want_object_in_pack_one(struct packed_git *p, * function finds if there is any pack that has the object and returns the pack * and its offset in these variables. */ -static int want_object_in_pack(const struct object_id *oid, - int exclude, - struct packed_git **found_pack, - off_t *found_offset) +static int want_object_in_pack_mtime(const struct object_id *oid, + int exclude, + struct packed_git **found_pack, + off_t *found_offset, + uint32_t found_mtime) { int want; struct list_head *pos; @@ -1625,7 +1700,8 @@ static int want_object_in_pack(const struct object_id *oid, * are present we will determine the answer right now. */ if (*found_pack) { - want = want_found_object(oid, exclude, *found_pack); + want = want_found_object(oid, exclude, *found_pack, + found_mtime); if (want != -1) return want; @@ -1636,7 +1712,7 @@ static int want_object_in_pack(const struct object_id *oid, for (m = get_multi_pack_index(the_repository); m; m = m->next) { struct pack_entry e; if (fill_midx_entry(the_repository, oid, &e, m)) { - want = want_object_in_pack_one(e.p, oid, exclude, found_pack, found_offset); + want = want_object_in_pack_one(e.p, oid, exclude, found_pack, found_offset, found_mtime); if (want != -1) return want; } @@ -1644,7 +1720,7 @@ static int want_object_in_pack(const struct object_id *oid, list_for_each(pos, get_packed_git_mru(the_repository)) { struct packed_git *p = list_entry(pos, struct packed_git, mru); - want = want_object_in_pack_one(p, oid, exclude, found_pack, found_offset); + want = want_object_in_pack_one(p, oid, exclude, found_pack, found_offset, found_mtime); if (!exclude && want > 0) list_move(&p->mru, get_packed_git_mru(the_repository)); @@ -1674,6 +1750,15 @@ static int want_object_in_pack(const struct object_id *oid, return 1; } +static inline int want_object_in_pack(const struct object_id *oid, + int exclude, + struct packed_git **found_pack, + off_t *found_offset) +{ + return want_object_in_pack_mtime(oid, exclude, found_pack, found_offset, + 0); +} + static struct object_entry *create_object_entry(const struct object_id *oid, enum object_type type, uint32_t hash, @@ -2453,7 +2538,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); } @@ -3606,7 +3692,7 @@ static void add_cruft_object_entry(const struct object_id *oid, enum object_type entry->no_try_delta = no_try_delta(name); } } else { - if (!want_object_in_pack(oid, 0, &pack, &offset)) + if (!want_object_in_pack_mtime(oid, 0, &pack, &offset, mtime)) return; if (!pack && type == OBJ_BLOB && !has_loose_object(oid)) { /* @@ -3680,6 +3766,8 @@ static void mark_pack_kept_in_core(struct string_list *packs, unsigned keep) struct packed_git *p = item->util; if (!p) die(_("could not find pack '%s'"), item->string); + if (p->is_cruft && keep) + ignore_packed_keep_in_core_has_cruft = 1; p->pack_keep_in_core = keep; } } @@ -3844,7 +3932,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, @@ -4160,7 +4248,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/pack-refs.c b/builtin/pack-refs.c index 4fdd68880e..e47bae1c80 100644 --- a/builtin/pack-refs.c +++ b/builtin/pack-refs.c @@ -1,5 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE - #include "builtin.h" #include "config.h" #include "gettext.h" @@ -15,7 +13,7 @@ static char const * const pack_refs_usage[] = { int cmd_pack_refs(int argc, const char **argv, const char *prefix, - struct repository *repo UNUSED) + struct repository *repo) { struct ref_exclusions excludes = REF_EXCLUSIONS_INIT; struct string_list included_refs = STRING_LIST_INIT_NODUP; @@ -39,7 +37,7 @@ int cmd_pack_refs(int argc, N_("references to exclude")), OPT_END(), }; - git_config(git_default_config, NULL); + repo_config(repo, git_default_config, NULL); if (parse_options(argc, argv, prefix, opts, pack_refs_usage, 0)) usage_with_options(pack_refs_usage, opts); @@ -52,7 +50,7 @@ int cmd_pack_refs(int argc, if (!pack_refs_opts.includes->nr) string_list_append(pack_refs_opts.includes, "refs/tags/*"); - ret = refs_pack_refs(get_main_ref_store(the_repository), &pack_refs_opts); + ret = refs_pack_refs(get_main_ref_store(repo), &pack_refs_opts); clear_ref_exclusions(&excludes); string_list_clear(&included_refs, 0); 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/pull.c b/builtin/pull.c index 9c4a00620a..a1ebc6ad33 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -738,7 +738,8 @@ static const char *get_tracking_branch(const char *remote, const char *refspec) const char *spec_src; const char *merge_branch; - refspec_item_init_or_die(&spec, refspec, REFSPEC_FETCH); + if (!refspec_item_init_fetch(&spec, refspec)) + die(_("invalid refspec '%s'"), refspec); spec_src = spec.src; if (!*spec_src || !strcmp(spec_src, "HEAD")) spec_src = "HEAD"; diff --git a/builtin/rebase.c b/builtin/rebase.c index d4715ed35d..19ad4df74f 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); @@ -1843,7 +1843,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..a3652e69f1 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,11 +57,17 @@ 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 }; @@ -383,7 +392,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix, struct expire_reflog_policy_cb cb = { .cmd = cmd }; 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); @@ -449,10 +458,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 +528,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/refs.c b/builtin/refs.c index 44d592a94c..998d2a2c1c 100644 --- a/builtin/refs.c +++ b/builtin/refs.c @@ -91,7 +91,7 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix, git_config(git_fsck_config, &fsck_refs_options); prepare_repo_settings(the_repository); - worktrees = get_worktrees(); + worktrees = get_worktrees_without_reading_head(); for (size_t i = 0; worktrees[i]; i++) ret |= refs_fsck(get_worktree_ref_store(worktrees[i]), &fsck_refs_options, worktrees[i]); 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/repack.c b/builtin/repack.c index 75e3752353..f3330ade7b 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -1022,29 +1022,13 @@ static int write_filtered_pack(const struct pack_objects_args *args, return finish_pack_objects_cmd(&cmd, names, local); } -static int existing_cruft_pack_cmp(const void *va, const void *vb) +static void combine_small_cruft_packs(FILE *in, size_t combine_cruft_below_size, + struct existing_packs *existing) { - struct packed_git *a = *(struct packed_git **)va; - struct packed_git *b = *(struct packed_git **)vb; - - if (a->pack_size < b->pack_size) - return -1; - if (a->pack_size > b->pack_size) - return 1; - return 0; -} - -static void collapse_small_cruft_packs(FILE *in, size_t max_size, - struct existing_packs *existing) -{ - struct packed_git **existing_cruft, *p; + struct packed_git *p; struct strbuf buf = STRBUF_INIT; - size_t total_size = 0; - size_t existing_cruft_nr = 0; size_t i; - ALLOC_ARRAY(existing_cruft, existing->cruft_packs.nr); - for (p = get_all_packs(the_repository); p; p = p->next) { if (!(p->is_cruft && p->pack_local)) continue; @@ -1056,24 +1040,7 @@ static void collapse_small_cruft_packs(FILE *in, size_t max_size, if (!string_list_has_string(&existing->cruft_packs, buf.buf)) continue; - if (existing_cruft_nr >= existing->cruft_packs.nr) - BUG("too many cruft packs (found %"PRIuMAX", but knew " - "of %"PRIuMAX")", - (uintmax_t)existing_cruft_nr + 1, - (uintmax_t)existing->cruft_packs.nr); - existing_cruft[existing_cruft_nr++] = p; - } - - QSORT(existing_cruft, existing_cruft_nr, existing_cruft_pack_cmp); - - for (i = 0; i < existing_cruft_nr; i++) { - size_t proposed; - - p = existing_cruft[i]; - proposed = st_add(total_size, p->pack_size); - - if (proposed <= max_size) { - total_size = proposed; + if (p->pack_size < combine_cruft_below_size) { fprintf(in, "-%s\n", pack_basename(p)); } else { retain_cruft_pack(existing, p); @@ -1086,13 +1053,13 @@ static void collapse_small_cruft_packs(FILE *in, size_t max_size, existing->non_kept_packs.items[i].string); strbuf_release(&buf); - free(existing_cruft); } static int write_cruft_pack(const struct pack_objects_args *args, const char *destination, const char *pack_prefix, const char *cruft_expiration, + unsigned long combine_cruft_below_size, struct string_list *names, struct existing_packs *existing) { @@ -1135,8 +1102,9 @@ static int write_cruft_pack(const struct pack_objects_args *args, in = xfdopen(cmd.in, "w"); for_each_string_list_item(item, names) fprintf(in, "%s-%s.pack\n", pack_prefix, item->string); - if (args->max_pack_size && !cruft_expiration) { - collapse_small_cruft_packs(in, args->max_pack_size, existing); + if (combine_cruft_below_size && !cruft_expiration) { + combine_small_cruft_packs(in, combine_cruft_below_size, + existing); } else { for_each_string_list_item(item, &existing->non_kept_packs) fprintf(in, "-%s.pack\n", item->string); @@ -1190,6 +1158,7 @@ int cmd_repack(int argc, const char *opt_window_memory = NULL; const char *opt_depth = NULL; const char *opt_threads = NULL; + unsigned long combine_cruft_below_size = 0ul; struct option builtin_repack_options[] = { OPT_BIT('a', NULL, &pack_everything, @@ -1202,6 +1171,9 @@ int cmd_repack(int argc, PACK_CRUFT), OPT_STRING(0, "cruft-expiration", &cruft_expiration, N_("approxidate"), N_("with --cruft, expire objects older than this")), + OPT_MAGNITUDE(0, "combine-cruft-below-size", + &combine_cruft_below_size, + N_("with --cruft, only repack cruft packs smaller than this")), OPT_MAGNITUDE(0, "max-cruft-size", &cruft_po_args.max_pack_size, N_("with --cruft, limit the size of new cruft packs")), OPT_BOOL('d', NULL, &delete_redundant, @@ -1445,7 +1417,8 @@ int cmd_repack(int argc, cruft_po_args.quiet = po_args.quiet; ret = write_cruft_pack(&cruft_po_args, packtmp, pack_prefix, - cruft_expiration, &names, + cruft_expiration, + combine_cruft_below_size, &names, &existing); if (ret) goto cleanup; @@ -1472,10 +1445,17 @@ int cmd_repack(int argc, * generate an empty pack (since every object not in the * cruft pack generated above will have an mtime older * than the expiration). + * + * Pretend we don't have a `--combine-cruft-below-size` + * argument, since we're not selectively combining + * anything based on size to generate the limbo cruft + * pack, but rather removing all cruft packs from the + * main repository regardless of size. */ ret = write_cruft_pack(&cruft_po_args, expire_to, pack_prefix, NULL, + 0ul, &names, &existing); if (ret) diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 8d461008e2..c6e0e9d051 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -1,4 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE #include "builtin.h" #include "config.h" #include "hex.h" @@ -151,7 +150,7 @@ static int send_pack_config(const char *k, const char *v, int cmd_send_pack(int argc, const char **argv, const char *prefix, - struct repository *repo UNUSED) + struct repository *repo) { struct refspec rs = REFSPEC_INIT_PUSH; const char *remote_name = NULL; @@ -212,7 +211,7 @@ int cmd_send_pack(int argc, OPT_END() }; - git_config(send_pack_config, NULL); + repo_config(repo, send_pack_config, NULL); argc = parse_options(argc, argv, prefix, options, send_pack_usage, 0); if (argc > 0) { dest = argv[0]; @@ -317,7 +316,7 @@ int cmd_send_pack(int argc, set_ref_status_for_push(remote_refs, args.send_mirror, args.force_update); - ret = send_pack(the_repository, &args, fd, conn, remote_refs, &extra_have); + ret = send_pack(repo, &args, fd, conn, remote_refs, &extra_have); if (helper_status) print_helper_status(remote_refs); diff --git a/builtin/stash.c b/builtin/stash.c index dbaa999cf1..cfbd92852a 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -13,7 +13,6 @@ #include "lockfile.h" #include "cache-tree.h" #include "unpack-trees.h" -#include "merge-recursive.h" #include "merge-ort-wrappers.h" #include "strvec.h" #include "run-command.h" 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 8383bcf404..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; } @@ -668,6 +669,7 @@ int cmd_unpack_objects(int argc, the_hash_algo->init_fn(&ctx); unpack_all(); git_hash_update(&ctx, buffer, offset); + the_hash_algo->init_fn(&tmp_ctx); git_hash_clone(&tmp_ctx, &ctx); git_hash_final_oid(&oid, &tmp_ctx); if (strict) { diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 4d35bdc4b4..c3903c104f 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -179,7 +179,8 @@ static int parse_next_oid(const char **next, const char *end, (*next)++; *next = parse_arg(*next, &arg); if (arg.len) { - if (repo_get_oid(the_repository, arg.buf, oid)) + if (repo_get_oid_with_flags(the_repository, arg.buf, oid, + GET_OID_SKIP_AMBIGUITY_CHECK)) goto invalid; } else { /* Without -z, an empty value means all zeros: */ @@ -197,7 +198,8 @@ static int parse_next_oid(const char **next, const char *end, *next += arg.len; if (arg.len) { - if (repo_get_oid(the_repository, arg.buf, oid)) + if (repo_get_oid_with_flags(the_repository, arg.buf, oid, + GET_OID_SKIP_AMBIGUITY_CHECK)) goto invalid; } else if (flags & PARSE_SHA1_ALLOW_EMPTY) { /* With -z, treat an empty value as all zeros: */ @@ -299,7 +301,8 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction, die("symref-update %s: expected old value", refname); if (!strcmp(old_arg, "oid")) { - if (repo_get_oid(the_repository, old_target, &old_oid)) + if (repo_get_oid_with_flags(the_repository, old_target, &old_oid, + GET_OID_SKIP_AMBIGUITY_CHECK)) die("symref-update %s: invalid oid: %s", refname, old_target); have_old_oid = 1; @@ -500,7 +503,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); @@ -772,7 +775,8 @@ int cmd_update_ref(int argc, refname = argv[0]; value = argv[1]; oldval = argv[2]; - if (repo_get_oid(the_repository, value, &oid)) + if (repo_get_oid_with_flags(the_repository, value, &oid, + GET_OID_SKIP_AMBIGUITY_CHECK)) die("%s: not a valid SHA1", value); } @@ -783,7 +787,8 @@ int cmd_update_ref(int argc, * must not already exist: */ oidclr(&oldoid, the_repository->hash_algo); - else if (repo_get_oid(the_repository, oldval, &oldoid)) + else if (repo_get_oid_with_flags(the_repository, oldval, &oldoid, + GET_OID_SKIP_AMBIGUITY_CHECK)) die("%s: not a valid old SHA1", oldval); } diff --git a/builtin/verify-commit.c b/builtin/verify-commit.c index 779b7988ca..5f749a30da 100644 --- a/builtin/verify-commit.c +++ b/builtin/verify-commit.c @@ -5,7 +5,6 @@ * * Based on git-verify-tag */ -#define USE_THE_REPOSITORY_VARIABLE #include "builtin.h" #include "config.h" #include "gettext.h" @@ -33,15 +32,15 @@ static int run_gpg_verify(struct commit *commit, unsigned flags) return ret; } -static int verify_commit(const char *name, unsigned flags) +static int verify_commit(struct repository *repo, const char *name, unsigned flags) { struct object_id oid; struct object *obj; - if (repo_get_oid(the_repository, name, &oid)) + if (repo_get_oid(repo, name, &oid)) return error("commit '%s' not found.", name); - obj = parse_object(the_repository, &oid); + obj = parse_object(repo, &oid); if (!obj) return error("%s: unable to read file.", name); if (obj->type != OBJ_COMMIT) @@ -54,7 +53,7 @@ static int verify_commit(const char *name, unsigned flags) int cmd_verify_commit(int argc, const char **argv, const char *prefix, - struct repository *repo UNUSED) + struct repository *repo) { int i = 1, verbose = 0, had_error = 0; unsigned flags = 0; @@ -64,7 +63,7 @@ int cmd_verify_commit(int argc, OPT_END() }; - git_config(git_default_config, NULL); + repo_config(repo, git_default_config, NULL); argc = parse_options(argc, argv, prefix, verify_commit_options, verify_commit_usage, PARSE_OPT_KEEP_ARGV0); @@ -78,7 +77,7 @@ int cmd_verify_commit(int argc, * was received in the process of writing the gpg input: */ signal(SIGPIPE, SIG_IGN); while (i < argc) - if (verify_commit(argv[i++], flags)) + if (verify_commit(repo, argv[i++], flags)) had_error = 1; return had_error; } diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c index f6b97048a5..ed1c40338f 100644 --- a/builtin/verify-tag.c +++ b/builtin/verify-tag.c @@ -5,7 +5,6 @@ * * Based on git-verify-tag.sh */ -#define USE_THE_REPOSITORY_VARIABLE #include "builtin.h" #include "config.h" #include "gettext.h" @@ -23,7 +22,7 @@ static const char * const verify_tag_usage[] = { int cmd_verify_tag(int argc, const char **argv, const char *prefix, - struct repository *repo UNUSED) + struct repository *repo) { int i = 1, verbose = 0, had_error = 0; unsigned flags = 0; @@ -35,7 +34,7 @@ int cmd_verify_tag(int argc, OPT_END() }; - git_config(git_default_config, NULL); + repo_config(repo, git_default_config, NULL); argc = parse_options(argc, argv, prefix, verify_tag_options, verify_tag_usage, PARSE_OPT_KEEP_ARGV0); @@ -56,7 +55,7 @@ int cmd_verify_tag(int argc, struct object_id oid; const char *name = argv[i++]; - if (repo_get_oid(the_repository, name, &oid)) { + if (repo_get_oid(repo, name, &oid)) { had_error = !!error("tag '%s' not found.", name); continue; } 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 20f2da67b9..79696e80f2 100644 --- a/bulk-checkin.c +++ b/bulk-checkin.c @@ -3,7 +3,6 @@ */ #define USE_THE_REPOSITORY_VARIABLE -#define DISABLE_SIGN_COMPARE_WARNINGS #include "git-compat-util.h" #include "bulk-checkin.h" @@ -44,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); @@ -56,7 +55,6 @@ static void flush_bulk_checkin_packfile(struct bulk_checkin_packfile *state) { unsigned char hash[GIT_MAX_RAWSZ]; struct strbuf packname = STRBUF_INIT; - int i; if (!state->f) return; @@ -82,7 +80,7 @@ static void flush_bulk_checkin_packfile(struct bulk_checkin_packfile *state) finish_tmp_packfile(&packname, state->pack_tmp_name, state->written, state->nr_written, &state->pack_idx_opts, hash); - for (i = 0; i < state->nr_written; i++) + for (uint32_t i = 0; i < state->nr_written; i++) free(state->written[i]); clear_exit: @@ -131,14 +129,12 @@ static void flush_batch_fsync(void) static int already_written(struct bulk_checkin_packfile *state, struct object_id *oid) { - int i; - /* The object may already exist in the repository */ if (repo_has_object_file(the_repository, oid)) return 1; /* Might want to keep the list sorted */ - for (i = 0; i < state->nr_written; i++) + for (uint32_t i = 0; i < state->nr_written; i++) if (oideq(&state->written[i]->oid, oid)) return 1; @@ -182,13 +178,13 @@ static int stream_blob_to_pack(struct bulk_checkin_packfile *state, while (status != Z_STREAM_END) { if (size && !s.avail_in) { - ssize_t rsize = size < sizeof(ibuf) ? size : sizeof(ibuf); + size_t rsize = size < sizeof(ibuf) ? size : sizeof(ibuf); ssize_t read_result = read_in_full(fd, ibuf, rsize); if (read_result < 0) die_errno("failed to read from '%s'", path); - if (read_result != rsize) - die("failed to read %d bytes from '%s'", - (int)rsize, path); + if ((size_t)read_result != rsize) + die("failed to read %u bytes from '%s'", + (unsigned)rsize, path); offset += rsize; if (*already_hashed_to < offset) { size_t hsize = offset - *already_hashed_to; @@ -244,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/check-unsafe-assertions.sh b/ci/check-unsafe-assertions.sh new file mode 100755 index 0000000000..233bd9dfbc --- /dev/null +++ b/ci/check-unsafe-assertions.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +make CHECK_ASSERTION_SIDE_EFFECTS=1 >compiler_output 2>compiler_error +if test $? != 0 +then + echo >&2 "ERROR: The compiler could not verify the following assert()" + echo >&2 " calls are free of side-effects. Please replace with" + echo >&2 " ASSERT() calls." + grep undefined.reference.to..not_supposed_to_survive compiler_error | + sed -e s/:[^:]*$// | sort | uniq | tr ':' ' ' | + while read f l + do + printf "${f}:${l}\n " + awk -v start="$l" 'NR >= start { print; if (/\);/) exit }' $f + done + exit 1 +fi +rm compiler_output compiler_error diff --git a/ci/run-static-analysis.sh b/ci/run-static-analysis.sh index 0d51e5ce0e..ae714e020a 100755 --- a/ci/run-static-analysis.sh +++ b/ci/run-static-analysis.sh @@ -31,4 +31,6 @@ exit 1 make check-pot +${0%/*}/check-unsafe-assertions.sh + save_good_tree diff --git a/ci/test-documentation.sh b/ci/test-documentation.sh index 6c018b673e..49f87f50fd 100755 --- a/ci/test-documentation.sh +++ b/ci/test-documentation.sh @@ -15,6 +15,13 @@ filter_log () { "$1" } +check_docs () { + test -s "$1"/Documentation/git.html && + test -s "$1"/Documentation/git.xml && + test -s "$1"/Documentation/git.1 && + grep "<meta name=\"generator\" content=\"$2 " "$1"/Documentation/git.html +} + make check-builtins make check-docs @@ -23,10 +30,7 @@ make doc > >(tee stdout.log) 2> >(tee stderr.raw >&2) cat stderr.raw filter_log stderr.raw >stderr.log test ! -s stderr.log -test -s Documentation/git.html -test -s Documentation/git.xml -test -s Documentation/git.1 -grep '<meta name="generator" content="AsciiDoc ' Documentation/git.html +check_docs . AsciiDoc rm -f stdout.log stderr.log stderr.raw check_unignored_build_artifacts @@ -37,10 +41,21 @@ make USE_ASCIIDOCTOR=1 doc > >(tee stdout.log) 2> >(tee stderr.raw >&2) cat stderr.raw filter_log stderr.raw >stderr.log test ! -s stderr.log -test -s Documentation/git.html -grep '<meta name="generator" content="Asciidoctor ' Documentation/git.html +check_docs . Asciidoctor rm -f stdout.log stderr.log stderr.raw check_unignored_build_artifacts +# Build docs with Meson and AsciiDoc +meson setup build-asciidoc -Ddocs=html,man -Ddocs_backend=asciidoc +meson compile -C build-asciidoc +check_docs build-asciidoc AsciiDoc +rm -rf build-asciidoc + +# Build docs with Meson and AsciiDoctor +meson setup build-asciidoctor -Ddocs=html,man -Ddocs_backend=asciidoctor +meson compile -C build-asciidoctor +check_docs build-asciidoctor Asciidoctor +rm -rf build-asciidoctor + save_good_tree 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/command-list.txt b/command-list.txt index c537114b46..b7ade3ab9f 100644 --- a/command-list.txt +++ b/command-list.txt @@ -96,6 +96,7 @@ git-diagnose ancillaryinterrogators git-diff mainporcelain info git-diff-files plumbinginterrogators git-diff-index plumbinginterrogators +git-diff-pairs plumbinginterrogators git-diff-tree plumbinginterrogators git-difftool ancillaryinterrogators complete git-fast-export ancillarymanipulators 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, @@ -780,19 +780,17 @@ static void clear_commit_marks_1(struct commit_list **plist, void clear_commit_marks_many(size_t nr, struct commit **commit, unsigned int mark) { - for (size_t i = 0; i < nr; i++) { - struct commit_list *list = NULL; - - clear_commit_marks_1(&list, *commit, mark); - while (list) - clear_commit_marks_1(&list, pop_commit(&list), mark); - commit++; - } + for (size_t i = 0; i < nr; i++) + clear_commit_marks(commit[i], mark); } void clear_commit_marks(struct commit *commit, unsigned int mark) { - clear_commit_marks_many(1, &commit, mark); + struct commit_list *list = NULL; + + clear_commit_marks_1(&list, commit, mark); + while (list) + clear_commit_marks_1(&list, pop_commit(&list), mark); } struct commit *pop_commit(struct commit_list **stack) @@ -1380,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 new file mode 100644 index 0000000000..88e0cf9292 --- /dev/null +++ b/compat/mingw-posix.h @@ -0,0 +1,435 @@ +#ifndef COMPAT_MINGW_POSIX_H +#define COMPAT_MINGW_POSIX_H + +#ifdef __MINGW64_VERSION_MAJOR +#include <stdint.h> +#include <wchar.h> +typedef _sigset_t sigset_t; +#endif +#include <winsock2.h> +#include <ws2tcpip.h> + +/* MinGW-w64 reports to have flockfile, but it does not actually have it. */ +#ifdef __MINGW64_VERSION_MAJOR +#undef _POSIX_THREAD_SAFE_FUNCTIONS +#endif + +/* + * things that are not available in header files + */ + +typedef int uid_t; +typedef int socklen_t; +#ifndef __MINGW64_VERSION_MAJOR +typedef int pid_t; +#define hstrerror strerror +#endif + +#define S_IFLNK 0120000 /* Symbolic link */ +#define S_ISLNK(x) (((x) & S_IFMT) == S_IFLNK) +#define S_ISSOCK(x) 0 + +#ifndef S_IRWXG +#define S_IRGRP 0 +#define S_IWGRP 0 +#define S_IXGRP 0 +#define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP) +#endif +#ifndef S_IRWXO +#define S_IROTH 0 +#define S_IWOTH 0 +#define S_IXOTH 0 +#define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH) +#endif + +#define S_ISUID 0004000 +#define S_ISGID 0002000 +#define S_ISVTX 0001000 + +#define WIFEXITED(x) 1 +#define WIFSIGNALED(x) 0 +#define WEXITSTATUS(x) ((x) & 0xff) +#define WTERMSIG(x) SIGTERM + +#ifndef EWOULDBLOCK +#define EWOULDBLOCK EAGAIN +#endif +#ifndef ELOOP +#define ELOOP EMLINK +#endif +#define SHUT_WR SD_SEND + +#define SIGHUP 1 +#define SIGQUIT 3 +#define SIGKILL 9 +#define SIGPIPE 13 +#define SIGALRM 14 +#define SIGCHLD 17 + +#define F_GETFD 1 +#define F_SETFD 2 +#define FD_CLOEXEC 0x1 + +#if !defined O_CLOEXEC && defined O_NOINHERIT +#define O_CLOEXEC O_NOINHERIT +#endif + +#ifndef EAFNOSUPPORT +#define EAFNOSUPPORT WSAEAFNOSUPPORT +#endif +#ifndef ECONNABORTED +#define ECONNABORTED WSAECONNABORTED +#endif +#ifndef ENOTSOCK +#define ENOTSOCK WSAENOTSOCK +#endif + +struct passwd { + char *pw_name; + char *pw_gecos; + char *pw_dir; +}; + +typedef void (__cdecl *sig_handler_t)(int); +struct sigaction { + sig_handler_t sa_handler; + unsigned sa_flags; +}; +#define SA_RESTART 0 + +struct itimerval { + struct timeval it_value, it_interval; +}; +#define ITIMER_REAL 0 + +struct utsname { + char sysname[16]; + char nodename[1]; + char release[16]; + char version[16]; + char machine[1]; +}; + +/* + * sanitize preprocessor namespace polluted by Windows headers defining + * macros which collide with git local versions + */ +#undef HELP_COMMAND /* from winuser.h */ + +/* + * trivial stubs + */ + +static inline int readlink(const char *path UNUSED, char *buf UNUSED, size_t bufsiz UNUSED) +{ errno = ENOSYS; return -1; } +static inline int symlink(const char *oldpath UNUSED, const char *newpath UNUSED) +{ errno = ENOSYS; return -1; } +static inline int fchmod(int fildes UNUSED, mode_t mode UNUSED) +{ errno = ENOSYS; return -1; } +#ifndef __MINGW64_VERSION_MAJOR +static inline pid_t fork(void) +{ errno = ENOSYS; return -1; } +#endif +static inline unsigned int alarm(unsigned int seconds UNUSED) +{ return 0; } +static inline int fsync(int fd) +{ return _commit(fd); } +static inline void sync(void) +{} +static inline uid_t getuid(void) +{ return 1; } +static inline struct passwd *getpwnam(const char *name UNUSED) +{ return NULL; } +static inline int fcntl(int fd UNUSED, int cmd, ...) +{ + if (cmd == F_GETFD || cmd == F_SETFD) + return 0; + errno = EINVAL; + return -1; +} + +#define sigemptyset(x) (void)0 +static inline int sigaddset(sigset_t *set UNUSED, int signum UNUSED) +{ return 0; } +#define SIG_BLOCK 0 +#define SIG_UNBLOCK 0 +static inline int sigprocmask(int how UNUSED, const sigset_t *set UNUSED, sigset_t *oldset UNUSED) +{ return 0; } +static inline pid_t getppid(void) +{ return 1; } +static inline pid_t getpgid(pid_t pid) +{ return pid == 0 ? getpid() : pid; } +static inline pid_t tcgetpgrp(int fd UNUSED) +{ return getpid(); } + +/* + * simple adaptors + */ + +int mingw_mkdir(const char *path, int mode); +#define mkdir mingw_mkdir + +#define WNOHANG 1 +pid_t waitpid(pid_t pid, int *status, int options); + +#define kill mingw_kill +int mingw_kill(pid_t pid, int sig); + +#define locate_in_PATH mingw_locate_in_PATH +char *mingw_locate_in_PATH(const char *cmd); + +/* + * implementations of missing functions + */ + +int pipe(int filedes[2]); +unsigned int sleep (unsigned int seconds); +int mkstemp(char *template); +int gettimeofday(struct timeval *tv, void *tz); +#ifndef __MINGW64_VERSION_MAJOR +struct tm *gmtime_r(const time_t *timep, struct tm *result); +struct tm *localtime_r(const time_t *timep, struct tm *result); +#endif +int getpagesize(void); /* defined in MinGW's libgcc.a */ +struct passwd *getpwuid(uid_t uid); +int setitimer(int type, struct itimerval *in, struct itimerval *out); +int sigaction(int sig, struct sigaction *in, struct sigaction *out); +int link(const char *oldpath, const char *newpath); +int uname(struct utsname *buf); + +/* + * replacements of existing functions + */ + +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 + +int mingw_open (const char *filename, int oflags, ...); +#define open mingw_open +#undef OPEN_RETURNS_EINTR + +int mingw_fgetc(FILE *stream); +#define fgetc mingw_fgetc + +FILE *mingw_fopen (const char *filename, const char *otype); +#define fopen mingw_fopen + +FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream); +#define freopen mingw_freopen + +int mingw_fflush(FILE *stream); +#define fflush mingw_fflush + +ssize_t mingw_write(int fd, const void *buf, size_t len); +#define write mingw_write + +int mingw_access(const char *filename, int mode); +#undef access +#define access mingw_access + +int mingw_chdir(const char *dirname); +#define chdir mingw_chdir + +int mingw_chmod(const char *filename, int mode); +#define chmod mingw_chmod + +char *mingw_mktemp(char *template); +#define mktemp mingw_mktemp + +char *mingw_getcwd(char *pointer, int len); +#define getcwd mingw_getcwd + +#ifdef NO_UNSETENV +#error "NO_UNSETENV is incompatible with the Windows-specific startup code!" +#endif + +/* + * We bind *env() routines (even the mingw_ ones) to private mingw_ versions. + * These talk to the CRT using UNICODE/wchar_t, but maintain the original + * narrow-char API. + * + * Note that the MSCRT maintains both ANSI (getenv()) and UNICODE (_wgetenv()) + * routines and stores both versions of each environment variable in parallel + * (and secretly updates both when you set one or the other), but it uses CP_ACP + * to do the conversion rather than CP_UTF8. + * + * Since everything in the git code base is UTF8, we define the mingw_ routines + * to access the CRT using the UNICODE routines and manually convert them to + * UTF8. This also avoids round-trip problems. + * + * This also helps with our linkage, since "_wenviron" is publicly exported + * from the CRT. But to access "_environ" we would have to statically link + * to the CRT (/MT). + * + * We require NO_SETENV (and let gitsetenv() call our mingw_putenv). + */ +#define getenv mingw_getenv +#define putenv mingw_putenv +#define unsetenv mingw_putenv +char *mingw_getenv(const char *name); +int mingw_putenv(const char *name); + +int mingw_gethostname(char *host, int namelen); +#define gethostname mingw_gethostname + +struct hostent *mingw_gethostbyname(const char *host); +#define gethostbyname mingw_gethostbyname + +int mingw_getaddrinfo(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **res); +#define getaddrinfo mingw_getaddrinfo + +int mingw_socket(int domain, int type, int protocol); +#define socket mingw_socket + +int mingw_connect(int sockfd, struct sockaddr *sa, size_t sz); +#define connect mingw_connect + +int mingw_bind(int sockfd, struct sockaddr *sa, size_t sz); +#define bind mingw_bind + +int mingw_setsockopt(int sockfd, int lvl, int optname, void *optval, int optlen); +#define setsockopt mingw_setsockopt + +int mingw_shutdown(int sockfd, int how); +#define shutdown mingw_shutdown + +int mingw_listen(int sockfd, int backlog); +#define listen mingw_listen + +int mingw_accept(int sockfd, struct sockaddr *sa, socklen_t *sz); +#define accept mingw_accept + +int mingw_rename(const char*, const char*); +#define rename mingw_rename + +#if defined(USE_WIN32_MMAP) || defined(_MSC_VER) +int mingw_getpagesize(void); +#define getpagesize mingw_getpagesize +#endif + +int win32_fsync_no_flush(int fd); +#define fsync_no_flush win32_fsync_no_flush + +#define FSYNC_COMPONENTS_PLATFORM_DEFAULT (FSYNC_COMPONENTS_DEFAULT | FSYNC_COMPONENT_LOOSE_OBJECT) +#define FSYNC_METHOD_DEFAULT (FSYNC_METHOD_BATCH) + +struct rlimit { + unsigned int rlim_cur; +}; +#define RLIMIT_NOFILE 0 + +static inline int getrlimit(int resource, struct rlimit *rlp) +{ + if (resource != RLIMIT_NOFILE) { + errno = EINVAL; + return -1; + } + + rlp->rlim_cur = 2048; + return 0; +} + +/* + * Use mingw specific stat()/lstat()/fstat() implementations on Windows, + * including our own struct stat with 64 bit st_size and nanosecond-precision + * file times. + */ +#ifndef __MINGW64_VERSION_MAJOR +#define off_t off64_t +#define lseek _lseeki64 +#ifndef _MSC_VER +struct timespec { + time_t tv_sec; + long tv_nsec; +}; +#endif +#endif + +struct mingw_stat { + _dev_t st_dev; + _ino_t st_ino; + _mode_t st_mode; + short st_nlink; + short st_uid; + short st_gid; + _dev_t st_rdev; + off64_t st_size; + struct timespec st_atim; + struct timespec st_mtim; + struct timespec st_ctim; +}; + +#define st_atime st_atim.tv_sec +#define st_mtime st_mtim.tv_sec +#define st_ctime st_ctim.tv_sec + +#ifdef stat +#undef stat +#endif +#define stat mingw_stat +int mingw_lstat(const char *file_name, struct stat *buf); +int mingw_stat(const char *file_name, struct stat *buf); +int mingw_fstat(int fd, struct stat *buf); +#ifdef fstat +#undef fstat +#endif +#define fstat mingw_fstat +#ifdef lstat +#undef lstat +#endif +#define lstat mingw_lstat + + +int mingw_utime(const char *file_name, const struct utimbuf *times); +#define utime mingw_utime +size_t mingw_strftime(char *s, size_t max, + const char *format, const struct tm *tm); +#define strftime mingw_strftime + +pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env, + const char *dir, + int fhin, int fhout, int fherr); +int mingw_execvp(const char *cmd, char *const *argv); +#define execvp mingw_execvp +int mingw_execv(const char *cmd, char *const *argv); +#define execv mingw_execv + +static inline unsigned int git_ntohl(unsigned int x) +{ return (unsigned int)ntohl(x); } +#define ntohl git_ntohl + +sig_handler_t mingw_signal(int sig, sig_handler_t handler); +#define signal mingw_signal + +int mingw_raise(int sig); +#define raise mingw_raise + +/* + * ANSI emulation wrappers + */ + +int winansi_isatty(int fd); +#define isatty winansi_isatty + +int winansi_dup2(int oldfd, int newfd); +#define dup2 winansi_dup2 + +void winansi_init(void); +HANDLE winansi_get_osfhandle(int fd); + +#if !defined(__MINGW64_VERSION_MAJOR) && (!defined(_MSC_VER) || _MSC_VER < 1800) +#define PRIuMAX "I64u" +#define PRId64 "I64d" +#else +#include <inttypes.h> +#endif + +#endif /* COMPAT_MINGW_POSIX_H */ diff --git a/compat/mingw.c b/compat/mingw.c index f524c54d06..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)) @@ -2826,31 +2849,44 @@ static void setup_windows_environment(void) } } -static PSID get_current_user_sid(void) +static void get_current_user_sid(PSID *sid, HANDLE *linked_token) { HANDLE token; DWORD len = 0; - PSID result = NULL; + TOKEN_ELEVATION_TYPE elevationType; + DWORD size; + + *sid = NULL; + *linked_token = NULL; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) - return NULL; + return; if (!GetTokenInformation(token, TokenUser, NULL, 0, &len)) { TOKEN_USER *info = xmalloc((size_t)len); if (GetTokenInformation(token, TokenUser, info, len, &len)) { len = GetLengthSid(info->User.Sid); - result = xmalloc(len); - if (!CopySid(len, result, info->User.Sid)) { + *sid = xmalloc(len); + if (!CopySid(len, *sid, info->User.Sid)) { error(_("failed to copy SID (%ld)"), GetLastError()); - FREE_AND_NULL(result); + FREE_AND_NULL(*sid); } } FREE_AND_NULL(info); } - CloseHandle(token); - return result; + if (GetTokenInformation(token, TokenElevationType, &elevationType, sizeof(elevationType), &size) && + elevationType == TokenElevationTypeLimited) { + /* + * The current process is run by a member of the Administrators + * group, but is not running elevated. + */ + if (!GetTokenInformation(token, TokenLinkedToken, linked_token, sizeof(*linked_token), &size)) + linked_token = NULL; /* there is no linked token */ + } + + CloseHandle(token); } static BOOL user_sid_to_user_name(PSID sid, LPSTR *str) @@ -2931,18 +2967,22 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report) else if (sid && IsValidSid(sid)) { /* Now, verify that the SID matches the current user's */ static PSID current_user_sid; + static HANDLE linked_token; BOOL is_member; if (!current_user_sid) - current_user_sid = get_current_user_sid(); + get_current_user_sid(¤t_user_sid, &linked_token); if (current_user_sid && IsValidSid(current_user_sid) && EqualSid(sid, current_user_sid)) result = 1; else if (IsWellKnownSid(sid, WinBuiltinAdministratorsSid) && - CheckTokenMembership(NULL, sid, &is_member) && - is_member) + ((CheckTokenMembership(NULL, sid, &is_member) && + is_member) || + (linked_token && + CheckTokenMembership(linked_token, sid, &is_member) && + is_member))) /* * If owned by the Administrators group, and the * current user is an administrator, we consider that diff --git a/compat/mingw.h b/compat/mingw.h index ebfb8ba423..444daedfa5 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -1,185 +1,10 @@ -#ifdef __MINGW64_VERSION_MAJOR -#include <stdint.h> -#include <wchar.h> -typedef _sigset_t sigset_t; -#endif -#include <winsock2.h> -#include <ws2tcpip.h> - -/* MinGW-w64 reports to have flockfile, but it does not actually have it. */ -#ifdef __MINGW64_VERSION_MAJOR -#undef _POSIX_THREAD_SAFE_FUNCTIONS -#endif +#include "mingw-posix.h" struct config_context; int mingw_core_config(const char *var, const char *value, const struct config_context *ctx, void *cb); #define platform_core_config mingw_core_config -/* - * things that are not available in header files - */ - -typedef int uid_t; -typedef int socklen_t; -#ifndef __MINGW64_VERSION_MAJOR -typedef int pid_t; -#define hstrerror strerror -#endif - -#define S_IFLNK 0120000 /* Symbolic link */ -#define S_ISLNK(x) (((x) & S_IFMT) == S_IFLNK) -#define S_ISSOCK(x) 0 - -#ifndef S_IRWXG -#define S_IRGRP 0 -#define S_IWGRP 0 -#define S_IXGRP 0 -#define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP) -#endif -#ifndef S_IRWXO -#define S_IROTH 0 -#define S_IWOTH 0 -#define S_IXOTH 0 -#define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH) -#endif - -#define S_ISUID 0004000 -#define S_ISGID 0002000 -#define S_ISVTX 0001000 - -#define WIFEXITED(x) 1 -#define WIFSIGNALED(x) 0 -#define WEXITSTATUS(x) ((x) & 0xff) -#define WTERMSIG(x) SIGTERM - -#ifndef EWOULDBLOCK -#define EWOULDBLOCK EAGAIN -#endif -#ifndef ELOOP -#define ELOOP EMLINK -#endif -#define SHUT_WR SD_SEND - -#define SIGHUP 1 -#define SIGQUIT 3 -#define SIGKILL 9 -#define SIGPIPE 13 -#define SIGALRM 14 -#define SIGCHLD 17 - -#define F_GETFD 1 -#define F_SETFD 2 -#define FD_CLOEXEC 0x1 - -#if !defined O_CLOEXEC && defined O_NOINHERIT -#define O_CLOEXEC O_NOINHERIT -#endif - -#ifndef EAFNOSUPPORT -#define EAFNOSUPPORT WSAEAFNOSUPPORT -#endif -#ifndef ECONNABORTED -#define ECONNABORTED WSAECONNABORTED -#endif -#ifndef ENOTSOCK -#define ENOTSOCK WSAENOTSOCK -#endif - -struct passwd { - char *pw_name; - char *pw_gecos; - char *pw_dir; -}; - -typedef void (__cdecl *sig_handler_t)(int); -struct sigaction { - sig_handler_t sa_handler; - unsigned sa_flags; -}; -#define SA_RESTART 0 - -struct itimerval { - struct timeval it_value, it_interval; -}; -#define ITIMER_REAL 0 - -struct utsname { - char sysname[16]; - char nodename[1]; - char release[16]; - char version[16]; - char machine[1]; -}; - -/* - * sanitize preprocessor namespace polluted by Windows headers defining - * macros which collide with git local versions - */ -#undef HELP_COMMAND /* from winuser.h */ - -/* - * trivial stubs - */ - -static inline int readlink(const char *path UNUSED, char *buf UNUSED, size_t bufsiz UNUSED) -{ errno = ENOSYS; return -1; } -static inline int symlink(const char *oldpath UNUSED, const char *newpath UNUSED) -{ errno = ENOSYS; return -1; } -static inline int fchmod(int fildes UNUSED, mode_t mode UNUSED) -{ errno = ENOSYS; return -1; } -#ifndef __MINGW64_VERSION_MAJOR -static inline pid_t fork(void) -{ errno = ENOSYS; return -1; } -#endif -static inline unsigned int alarm(unsigned int seconds UNUSED) -{ return 0; } -static inline int fsync(int fd) -{ return _commit(fd); } -static inline void sync(void) -{} -static inline uid_t getuid(void) -{ return 1; } -static inline struct passwd *getpwnam(const char *name UNUSED) -{ return NULL; } -static inline int fcntl(int fd UNUSED, int cmd, ...) -{ - if (cmd == F_GETFD || cmd == F_SETFD) - return 0; - errno = EINVAL; - return -1; -} - -#define sigemptyset(x) (void)0 -static inline int sigaddset(sigset_t *set UNUSED, int signum UNUSED) -{ return 0; } -#define SIG_BLOCK 0 -#define SIG_UNBLOCK 0 -static inline int sigprocmask(int how UNUSED, const sigset_t *set UNUSED, sigset_t *oldset UNUSED) -{ return 0; } -static inline pid_t getppid(void) -{ return 1; } -static inline pid_t getpgid(pid_t pid) -{ return pid == 0 ? getpid() : pid; } -static inline pid_t tcgetpgrp(int fd UNUSED) -{ return getpid(); } - -/* - * simple adaptors - */ - -int mingw_mkdir(const char *path, int mode); -#define mkdir mingw_mkdir - -#define WNOHANG 1 -pid_t waitpid(pid_t pid, int *status, int options); - -#define kill mingw_kill -int mingw_kill(pid_t pid, int sig); - -#define locate_in_PATH mingw_locate_in_PATH -char *mingw_locate_in_PATH(const char *cmd); - #ifndef NO_OPENSSL #include <openssl/ssl.h> static inline int mingw_SSL_set_fd(SSL *ssl, int fd) @@ -202,249 +27,6 @@ static inline int mingw_SSL_set_wfd(SSL *ssl, int fd) #endif /* - * implementations of missing functions - */ - -int pipe(int filedes[2]); -unsigned int sleep (unsigned int seconds); -int mkstemp(char *template); -int gettimeofday(struct timeval *tv, void *tz); -#ifndef __MINGW64_VERSION_MAJOR -struct tm *gmtime_r(const time_t *timep, struct tm *result); -struct tm *localtime_r(const time_t *timep, struct tm *result); -#endif -int getpagesize(void); /* defined in MinGW's libgcc.a */ -struct passwd *getpwuid(uid_t uid); -int setitimer(int type, struct itimerval *in, struct itimerval *out); -int sigaction(int sig, struct sigaction *in, struct sigaction *out); -int link(const char *oldpath, const char *newpath); -int uname(struct utsname *buf); - -/* - * replacements of existing functions - */ - -int mingw_unlink(const char *pathname); -#define unlink mingw_unlink - -int mingw_rmdir(const char *path); -#define rmdir mingw_rmdir - -int mingw_open (const char *filename, int oflags, ...); -#define open mingw_open -#undef OPEN_RETURNS_EINTR - -int mingw_fgetc(FILE *stream); -#define fgetc mingw_fgetc - -FILE *mingw_fopen (const char *filename, const char *otype); -#define fopen mingw_fopen - -FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream); -#define freopen mingw_freopen - -int mingw_fflush(FILE *stream); -#define fflush mingw_fflush - -ssize_t mingw_write(int fd, const void *buf, size_t len); -#define write mingw_write - -int mingw_access(const char *filename, int mode); -#undef access -#define access mingw_access - -int mingw_chdir(const char *dirname); -#define chdir mingw_chdir - -int mingw_chmod(const char *filename, int mode); -#define chmod mingw_chmod - -char *mingw_mktemp(char *template); -#define mktemp mingw_mktemp - -char *mingw_getcwd(char *pointer, int len); -#define getcwd mingw_getcwd - -#ifdef NO_UNSETENV -#error "NO_UNSETENV is incompatible with the Windows-specific startup code!" -#endif - -/* - * We bind *env() routines (even the mingw_ ones) to private mingw_ versions. - * These talk to the CRT using UNICODE/wchar_t, but maintain the original - * narrow-char API. - * - * Note that the MSCRT maintains both ANSI (getenv()) and UNICODE (_wgetenv()) - * routines and stores both versions of each environment variable in parallel - * (and secretly updates both when you set one or the other), but it uses CP_ACP - * to do the conversion rather than CP_UTF8. - * - * Since everything in the git code base is UTF8, we define the mingw_ routines - * to access the CRT using the UNICODE routines and manually convert them to - * UTF8. This also avoids round-trip problems. - * - * This also helps with our linkage, since "_wenviron" is publicly exported - * from the CRT. But to access "_environ" we would have to statically link - * to the CRT (/MT). - * - * We require NO_SETENV (and let gitsetenv() call our mingw_putenv). - */ -#define getenv mingw_getenv -#define putenv mingw_putenv -#define unsetenv mingw_putenv -char *mingw_getenv(const char *name); -int mingw_putenv(const char *name); - -int mingw_gethostname(char *host, int namelen); -#define gethostname mingw_gethostname - -struct hostent *mingw_gethostbyname(const char *host); -#define gethostbyname mingw_gethostbyname - -int mingw_getaddrinfo(const char *node, const char *service, - const struct addrinfo *hints, struct addrinfo **res); -#define getaddrinfo mingw_getaddrinfo - -int mingw_socket(int domain, int type, int protocol); -#define socket mingw_socket - -int mingw_connect(int sockfd, struct sockaddr *sa, size_t sz); -#define connect mingw_connect - -int mingw_bind(int sockfd, struct sockaddr *sa, size_t sz); -#define bind mingw_bind - -int mingw_setsockopt(int sockfd, int lvl, int optname, void *optval, int optlen); -#define setsockopt mingw_setsockopt - -int mingw_shutdown(int sockfd, int how); -#define shutdown mingw_shutdown - -int mingw_listen(int sockfd, int backlog); -#define listen mingw_listen - -int mingw_accept(int sockfd, struct sockaddr *sa, socklen_t *sz); -#define accept mingw_accept - -int mingw_rename(const char*, const char*); -#define rename mingw_rename - -#if defined(USE_WIN32_MMAP) || defined(_MSC_VER) -int mingw_getpagesize(void); -#define getpagesize mingw_getpagesize -#endif - -int win32_fsync_no_flush(int fd); -#define fsync_no_flush win32_fsync_no_flush - -#define FSYNC_COMPONENTS_PLATFORM_DEFAULT (FSYNC_COMPONENTS_DEFAULT | FSYNC_COMPONENT_LOOSE_OBJECT) -#define FSYNC_METHOD_DEFAULT (FSYNC_METHOD_BATCH) - -struct rlimit { - unsigned int rlim_cur; -}; -#define RLIMIT_NOFILE 0 - -static inline int getrlimit(int resource, struct rlimit *rlp) -{ - if (resource != RLIMIT_NOFILE) { - errno = EINVAL; - return -1; - } - - rlp->rlim_cur = 2048; - return 0; -} - -/* - * Use mingw specific stat()/lstat()/fstat() implementations on Windows, - * including our own struct stat with 64 bit st_size and nanosecond-precision - * file times. - */ -#ifndef __MINGW64_VERSION_MAJOR -#define off_t off64_t -#define lseek _lseeki64 -#ifndef _MSC_VER -struct timespec { - time_t tv_sec; - long tv_nsec; -}; -#endif -#endif - -struct mingw_stat { - _dev_t st_dev; - _ino_t st_ino; - _mode_t st_mode; - short st_nlink; - short st_uid; - short st_gid; - _dev_t st_rdev; - off64_t st_size; - struct timespec st_atim; - struct timespec st_mtim; - struct timespec st_ctim; -}; - -#define st_atime st_atim.tv_sec -#define st_mtime st_mtim.tv_sec -#define st_ctime st_ctim.tv_sec - -#ifdef stat -#undef stat -#endif -#define stat mingw_stat -int mingw_lstat(const char *file_name, struct stat *buf); -int mingw_stat(const char *file_name, struct stat *buf); -int mingw_fstat(int fd, struct stat *buf); -#ifdef fstat -#undef fstat -#endif -#define fstat mingw_fstat -#ifdef lstat -#undef lstat -#endif -#define lstat mingw_lstat - - -int mingw_utime(const char *file_name, const struct utimbuf *times); -#define utime mingw_utime -size_t mingw_strftime(char *s, size_t max, - const char *format, const struct tm *tm); -#define strftime mingw_strftime - -pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env, - const char *dir, - int fhin, int fhout, int fherr); -int mingw_execvp(const char *cmd, char *const *argv); -#define execvp mingw_execvp -int mingw_execv(const char *cmd, char *const *argv); -#define execv mingw_execv - -static inline unsigned int git_ntohl(unsigned int x) -{ return (unsigned int)ntohl(x); } -#define ntohl git_ntohl - -sig_handler_t mingw_signal(int sig, sig_handler_t handler); -#define signal mingw_signal - -int mingw_raise(int sig); -#define raise mingw_raise - -/* - * ANSI emulation wrappers - */ - -int winansi_isatty(int fd); -#define isatty winansi_isatty - -int winansi_dup2(int oldfd, int newfd); -#define dup2 winansi_dup2 - -void winansi_init(void); -HANDLE winansi_get_osfhandle(int fd); - -/* * git specific compatibility */ @@ -457,12 +39,6 @@ static inline void convert_slashes(char *path) #define PATH_SEP ';' char *mingw_query_user_email(void); #define query_user_email mingw_query_user_email -#if !defined(__MINGW64_VERSION_MAJOR) && (!defined(_MSC_VER) || _MSC_VER < 1800) -#define PRIuMAX "I64u" -#define PRId64 "I64d" -#else -#include <inttypes.h> -#endif /** * Verifies that the specified path is owned by the user running the diff --git a/compat/msvc-posix.h b/compat/msvc-posix.h new file mode 100644 index 0000000000..c500b8b4aa --- /dev/null +++ b/compat/msvc-posix.h @@ -0,0 +1,33 @@ +#ifndef COMPAT_MSVC_POSIX_H +#define COMPAT_MSVC_POSIX_H + +#include <direct.h> +#include <process.h> +#include <malloc.h> +#include <io.h> + +#pragma warning(disable: 4018) /* signed/unsigned comparison */ +#pragma warning(disable: 4244) /* type conversion, possible loss of data */ +#pragma warning(disable: 4090) /* 'function' : different 'const' qualifiers (ALLOC_GROW etc.)*/ + +/* porting function */ +#define inline __inline +#define __inline__ __inline +#define __attribute__(x) +#define strcasecmp _stricmp +#define strncasecmp _strnicmp +#define ftruncate _chsize +#define strtoull _strtoui64 +#define strtoll _strtoi64 + +#undef ERROR + +#define ftello _ftelli64 + +typedef int sigset_t; +/* open for reading, writing, or both (not in fcntl.h) */ +#define O_ACCMODE (_O_RDONLY | _O_WRONLY | _O_RDWR) + +#include "mingw-posix.h" + +#endif /* COMPAT_MSVC_POSIX_H */ diff --git a/compat/msvc.h b/compat/msvc.h index 1d7a8c6145..2b87c0a7c7 100644 --- a/compat/msvc.h +++ b/compat/msvc.h @@ -1,33 +1,7 @@ #ifndef __MSVC__HEAD #define __MSVC__HEAD -#include <direct.h> -#include <process.h> -#include <malloc.h> -#include <io.h> - -#pragma warning(disable: 4018) /* signed/unsigned comparison */ -#pragma warning(disable: 4244) /* type conversion, possible loss of data */ -#pragma warning(disable: 4090) /* 'function' : different 'const' qualifiers (ALLOC_GROW etc.)*/ - -/* porting function */ -#define inline __inline -#define __inline__ __inline -#define __attribute__(x) -#define strcasecmp _stricmp -#define strncasecmp _strnicmp -#define ftruncate _chsize -#define strtoull _strtoui64 -#define strtoll _strtoi64 - -#undef ERROR - -#define ftello _ftelli64 - -typedef int sigset_t; -/* open for reading, writing, or both (not in fcntl.h) */ -#define O_ACCMODE (_O_RDONLY | _O_WRONLY | _O_RDWR) - -#include "compat/mingw.h" +#include "msvc-posix.h" +#include "mingw.h" #endif diff --git a/compat/posix.h b/compat/posix.h new file mode 100644 index 0000000000..f4c71f9427 --- /dev/null +++ b/compat/posix.h @@ -0,0 +1,541 @@ +#ifndef COMPAT_POSIX_H +#define COMPAT_POSIX_H + +#define _FILE_OFFSET_BITS 64 + +/* + * Derived from Linux "Features Test Macro" header + * Convenience macros to test the versions of gcc (or + * a compatible compiler). + * Use them like this: + * #if GIT_GNUC_PREREQ (2,8) + * ... code requiring gcc 2.8 or later ... + * #endif + * + * This macro of course is not part of POSIX, but we need it for the UNUSED + * macro which is used by some of our POSIX compatibility wrappers. +*/ +#if defined(__GNUC__) && defined(__GNUC_MINOR__) +# define GIT_GNUC_PREREQ(maj, min) \ + ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min)) +#else + #define GIT_GNUC_PREREQ(maj, min) 0 +#endif + +/* + * UNUSED marks a function parameter that is always unused. It also + * can be used to annotate a function, a variable, or a type that is + * always unused. + * + * A callback interface may dictate that a function accepts a + * parameter at that position, but the implementation of the function + * may not need to use the parameter. In such a case, mark the parameter + * with UNUSED. + * + * When a parameter may be used or unused, depending on conditional + * compilation, consider using MAYBE_UNUSED instead. + */ +#if GIT_GNUC_PREREQ(4, 5) +#define UNUSED __attribute__((unused)) \ + __attribute__((deprecated ("parameter declared as UNUSED"))) +#elif defined(__GNUC__) +#define UNUSED __attribute__((unused)) \ + __attribute__((deprecated)) +#else +#define UNUSED +#endif + +#ifdef __MINGW64__ +#define _POSIX_C_SOURCE 1 +#elif defined(__sun__) + /* + * On Solaris, when _XOPEN_EXTENDED is set, its header file + * forces the programs to be XPG4v2, defeating any _XOPEN_SOURCE + * setting to say we are XPG5 or XPG6. Also on Solaris, + * XPG6 programs must be compiled with a c99 compiler, while + * non XPG6 programs must be compiled with a pre-c99 compiler. + */ +# if __STDC_VERSION__ - 0 >= 199901L +# define _XOPEN_SOURCE 600 +# else +# define _XOPEN_SOURCE 500 +# endif +#elif !defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__USLC__) && \ + !defined(_M_UNIX) && !defined(__sgi) && !defined(__DragonFly__) && \ + !defined(__TANDEM) && !defined(__QNX__) && !defined(__MirBSD__) && \ + !defined(__CYGWIN__) +#define _XOPEN_SOURCE 600 /* glibc2 and AIX 5.3L need 500, OpenBSD needs 600 for S_ISLNK() */ +#define _XOPEN_SOURCE_EXTENDED 1 /* AIX 5.3L needs this */ +#endif +#define _ALL_SOURCE 1 +#define _GNU_SOURCE 1 +#define _BSD_SOURCE 1 +#define _DEFAULT_SOURCE 1 +#define _NETBSD_SOURCE 1 +#define _SGI_SOURCE 1 + +#if defined(WIN32) && !defined(__CYGWIN__) /* Both MinGW and MSVC */ +# if !defined(_WIN32_WINNT) +# define _WIN32_WINNT 0x0600 +# endif +#define WIN32_LEAN_AND_MEAN /* stops windows.h including winsock.h */ +#include <winsock2.h> +#ifndef NO_UNIX_SOCKETS +#include <afunix.h> +#endif +#include <windows.h> +#define GIT_WINDOWS_NATIVE +#endif + +#include <unistd.h> +#include <stdio.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stddef.h> +#include <stdlib.h> +#include <stdarg.h> +#include <stdbool.h> +#include <string.h> +#ifdef HAVE_STRINGS_H +#include <strings.h> /* for strcasecmp() */ +#endif +#include <errno.h> +#include <limits.h> +#include <locale.h> +#ifdef NEEDS_SYS_PARAM_H +#include <sys/param.h> +#endif +#include <sys/types.h> +#include <dirent.h> +#include <sys/time.h> +#include <time.h> +#include <signal.h> +#include <assert.h> +#include <regex.h> +#include <utime.h> +#include <syslog.h> +#if !defined(NO_POLL_H) +#include <poll.h> +#elif !defined(NO_SYS_POLL_H) +#include <sys/poll.h> +#else +/* Pull the compat stuff */ +#include <poll.h> +#endif +#ifdef HAVE_BSD_SYSCTL +#include <sys/sysctl.h> +#endif + +#if defined(__MINGW32__) +#include "mingw-posix.h" +#elif defined(_MSC_VER) +#include "msvc-posix.h" +#else +#include <sys/utsname.h> +#include <sys/wait.h> +#include <sys/resource.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <sys/statvfs.h> +#include <termios.h> +#ifndef NO_SYS_SELECT_H +#include <sys/select.h> +#endif +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <pwd.h> +#include <sys/un.h> +#ifndef NO_INTTYPES_H +#include <inttypes.h> +#else +#include <stdint.h> +#endif +#ifdef HAVE_ARC4RANDOM_LIBBSD +#include <bsd/stdlib.h> +#endif +#ifdef HAVE_GETRANDOM +#include <sys/random.h> +#endif +#ifdef NO_INTPTR_T +/* + * On I16LP32, ILP32 and LP64 "long" is the safe bet, however + * on LLP86, IL33LLP64 and P64 it needs to be "long long", + * while on IP16 and IP16L32 it is "int" (resp. "short") + * Size needs to match (or exceed) 'sizeof(void *)'. + * We can't take "long long" here as not everybody has it. + */ +typedef long intptr_t; +typedef unsigned long uintptr_t; +#endif +#undef _ALL_SOURCE /* AIX 5.3L defines a struct list with _ALL_SOURCE. */ +#include <grp.h> +#define _ALL_SOURCE 1 +#endif + +#ifdef MKDIR_WO_TRAILING_SLASH +#define mkdir(a,b) compat_mkdir_wo_trailing_slash((a),(b)) +int compat_mkdir_wo_trailing_slash(const char*, mode_t); +#endif + +#ifdef time +#undef time +#endif +static inline time_t git_time(time_t *tloc) +{ + struct timeval tv; + + /* + * Avoid time(NULL), which can disagree with gettimeofday(2) + * and filesystem timestamps. + */ + gettimeofday(&tv, NULL); + + if (tloc) + *tloc = tv.tv_sec; + return tv.tv_sec; +} +#define time git_time + +#ifdef NO_STRUCT_ITIMERVAL +struct itimerval { + struct timeval it_interval; + struct timeval it_value; +}; +#endif + +#ifdef NO_SETITIMER +static inline int git_setitimer(int which UNUSED, + const struct itimerval *value UNUSED, + struct itimerval *newvalue UNUSED) { + return 0; /* pretend success */ +} +#undef setitimer +#define setitimer(which,value,ovalue) git_setitimer(which,value,ovalue) +#endif + +#ifndef NO_LIBGEN_H +#include <libgen.h> +#else +#define basename gitbasename +char *gitbasename(char *); +#define dirname gitdirname +char *gitdirname(char *); +#endif + +#ifndef NO_ICONV +#include <iconv.h> +#endif + +/* On most systems <netdb.h> would have given us this, but + * not on some systems (e.g. z/OS). + */ +#ifndef NI_MAXHOST +#define NI_MAXHOST 1025 +#endif + +#ifndef NI_MAXSERV +#define NI_MAXSERV 32 +#endif + +/* On most systems <limits.h> would have given us this, but + * not on some systems (e.g. GNU/Hurd). + */ +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + +#ifndef NAME_MAX +#define NAME_MAX 255 +#endif + +typedef uintmax_t timestamp_t; +#define PRItime PRIuMAX +#define parse_timestamp strtoumax +#define TIME_MAX UINTMAX_MAX +#define TIME_MIN 0 + +int lstat_cache_aware_rmdir(const char *path); +#if !defined(__MINGW32__) && !defined(_MSC_VER) +#define rmdir lstat_cache_aware_rmdir +#endif + +#if defined(NO_MMAP) || defined(USE_WIN32_MMAP) + +#ifndef PROT_READ +#define PROT_READ 1 +#define PROT_WRITE 2 +#define MAP_PRIVATE 1 +#endif + +#define mmap git_mmap +#define munmap git_munmap +void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); +int git_munmap(void *start, size_t length); + +#else /* NO_MMAP || USE_WIN32_MMAP */ + +#include <sys/mman.h> + +#endif /* NO_MMAP || USE_WIN32_MMAP */ + +#ifndef MAP_FAILED +#define MAP_FAILED ((void *)-1) +#endif + +#ifdef NEEDS_MODE_TRANSLATION +#undef S_IFMT +#undef S_IFREG +#undef S_IFDIR +#undef S_IFLNK +#undef S_IFBLK +#undef S_IFCHR +#undef S_IFIFO +#undef S_IFSOCK +#define S_IFMT 0170000 +#define S_IFREG 0100000 +#define S_IFDIR 0040000 +#define S_IFLNK 0120000 +#define S_IFBLK 0060000 +#define S_IFCHR 0020000 +#define S_IFIFO 0010000 +#define S_IFSOCK 0140000 +#ifdef stat +#undef stat +#endif +#define stat(path, buf) git_stat(path, buf) +int git_stat(const char *, struct stat *); +#ifdef fstat +#undef fstat +#endif +#define fstat(fd, buf) git_fstat(fd, buf) +int git_fstat(int, struct stat *); +#ifdef lstat +#undef lstat +#endif +#define lstat(path, buf) git_lstat(path, buf) +int git_lstat(const char *, struct stat *); +#endif + +#ifdef NO_PREAD +#define pread git_pread +ssize_t git_pread(int fd, void *buf, size_t count, off_t offset); +#endif + +#ifdef NO_SETENV +#define setenv gitsetenv +int gitsetenv(const char *, const char *, int); +#endif + +#ifdef NO_MKDTEMP +#define mkdtemp gitmkdtemp +char *gitmkdtemp(char *); +#endif + +#ifdef NO_UNSETENV +#define unsetenv gitunsetenv +int gitunsetenv(const char *); +#endif + +#ifdef NO_STRCASESTR +#define strcasestr gitstrcasestr +char *gitstrcasestr(const char *haystack, const char *needle); +#endif + +#ifdef NO_STRLCPY +#define strlcpy gitstrlcpy +size_t gitstrlcpy(char *, const char *, size_t); +#endif + +#ifdef NO_STRTOUMAX +#define strtoumax gitstrtoumax +uintmax_t gitstrtoumax(const char *, char **, int); +#define strtoimax gitstrtoimax +intmax_t gitstrtoimax(const char *, char **, int); +#endif + +#ifdef NO_HSTRERROR +#define hstrerror githstrerror +const char *githstrerror(int herror); +#endif + +#ifdef NO_MEMMEM +#define memmem gitmemmem +void *gitmemmem(const void *haystack, size_t haystacklen, + const void *needle, size_t needlelen); +#endif + +#ifdef OVERRIDE_STRDUP +#ifdef strdup +#undef strdup +#endif +#define strdup gitstrdup +char *gitstrdup(const char *s); +#endif + +#ifdef NO_GETPAGESIZE +#define getpagesize() sysconf(_SC_PAGESIZE) +#endif + +#ifndef O_CLOEXEC +#define O_CLOEXEC 0 +#endif + +#ifdef FREAD_READS_DIRECTORIES +# if !defined(SUPPRESS_FOPEN_REDEFINITION) +# ifdef fopen +# undef fopen +# endif +# define fopen(a,b) git_fopen(a,b) +# endif +FILE *git_fopen(const char*, const char*); +#endif + +#ifdef SNPRINTF_RETURNS_BOGUS +#ifdef snprintf +#undef snprintf +#endif +#define snprintf git_snprintf +int git_snprintf(char *str, size_t maxsize, + const char *format, ...); +#ifdef vsnprintf +#undef vsnprintf +#endif +#define vsnprintf git_vsnprintf +int git_vsnprintf(char *str, size_t maxsize, + const char *format, va_list ap); +#endif + +#ifdef OPEN_RETURNS_EINTR +#undef open +#define open git_open_with_retry +int git_open_with_retry(const char *path, int flag, ...); +#endif + +#ifdef __GLIBC_PREREQ +#if __GLIBC_PREREQ(2, 1) +#define HAVE_STRCHRNUL +#endif +#endif + +#ifndef HAVE_STRCHRNUL +#define strchrnul gitstrchrnul +static inline char *gitstrchrnul(const char *s, int c) +{ + while (*s && *s != c) + s++; + return (char *)s; +} +#endif + +#ifdef NO_INET_PTON +int inet_pton(int af, const char *src, void *dst); +#endif + +#ifdef NO_INET_NTOP +const char *inet_ntop(int af, const void *src, char *dst, size_t size); +#endif + +#ifdef NO_PTHREADS +#define atexit git_atexit +int git_atexit(void (*handler)(void)); +#endif + +#ifndef HOST_NAME_MAX +#define HOST_NAME_MAX 256 +#endif + +#include "../sane-ctype.h" + +void git_stable_qsort(void *base, size_t nmemb, size_t size, + int(*compar)(const void *, const void *)); +#ifdef INTERNAL_QSORT +#define qsort git_stable_qsort +#endif + +#define QSORT(base, n, compar) sane_qsort((base), (n), sizeof(*(base)), compar) +static inline void sane_qsort(void *base, size_t nmemb, size_t size, + int(*compar)(const void *, const void *)) +{ + if (nmemb > 1) + qsort(base, nmemb, size, compar); +} + +#define STABLE_QSORT(base, n, compar) \ + git_stable_qsort((base), (n), sizeof(*(base)), compar) + +#ifndef HAVE_ISO_QSORT_S +int git_qsort_s(void *base, size_t nmemb, size_t size, + int (*compar)(const void *, const void *, void *), void *ctx); +#define qsort_s git_qsort_s +#endif + +#define QSORT_S(base, n, compar, ctx) do { \ + if (qsort_s((base), (n), sizeof(*(base)), compar, ctx)) \ + BUG("qsort_s() failed"); \ +} while (0) + +#ifdef NO_NSEC +#undef USE_NSEC +#define ST_CTIME_NSEC(st) 0 +#define ST_MTIME_NSEC(st) 0 +#else +#ifdef USE_ST_TIMESPEC +#define ST_CTIME_NSEC(st) ((unsigned int)((st).st_ctimespec.tv_nsec)) +#define ST_MTIME_NSEC(st) ((unsigned int)((st).st_mtimespec.tv_nsec)) +#else +#define ST_CTIME_NSEC(st) ((unsigned int)((st).st_ctim.tv_nsec)) +#define ST_MTIME_NSEC(st) ((unsigned int)((st).st_mtim.tv_nsec)) +#endif +#endif + +#ifndef va_copy +/* + * Since an obvious implementation of va_list would be to make it a + * pointer into the stack frame, a simple assignment will work on + * many systems. But let's try to be more portable. + */ +#ifdef __va_copy +#define va_copy(dst, src) __va_copy(dst, src) +#else +#define va_copy(dst, src) ((dst) = (src)) +#endif +#endif + +#ifndef _POSIX_THREAD_SAFE_FUNCTIONS +static inline void git_flockfile(FILE *fh UNUSED) +{ + ; /* nothing */ +} +static inline void git_funlockfile(FILE *fh UNUSED) +{ + ; /* nothing */ +} +#undef flockfile +#undef funlockfile +#undef getc_unlocked +#define flockfile(fh) git_flockfile(fh) +#define funlockfile(fh) git_funlockfile(fh) +#define getc_unlocked(fh) getc(fh) +#endif + +#ifdef FILENO_IS_A_MACRO +int git_fileno(FILE *stream); +# ifndef COMPAT_CODE_FILENO +# undef fileno +# define fileno(p) git_fileno(p) +# endif +#endif + +#ifdef NEED_ACCESS_ROOT_HANDLER +int git_access(const char *path, int mode); +# ifndef COMPAT_CODE_ACCESS +# ifdef access +# undef access +# endif +# define access(path, mode) git_access(path, mode) +# endif +#endif + +#endif /* COMPAT_POSIX_H */ diff --git a/compat/regex/regex_internal.c b/compat/regex/regex_internal.c index ec5cc5d2dd..4a4f849629 100644 --- a/compat/regex/regex_internal.c +++ b/compat/regex/regex_internal.c @@ -1232,7 +1232,10 @@ re_node_set_merge (re_node_set *dest, const re_node_set *src) is = src->nelem - 1, id = dest->nelem - 1; is >= 0 && id >= 0; ) { if (dest->elems[id] == src->elems[is]) - is--, id--; + { + is--; + id--; + } else if (dest->elems[id] < src->elems[is]) dest->elems[--sbase] = src->elems[is--]; else /* if (dest->elems[id] > src->elems[is]) */ diff --git a/compat/regex/regexec.c b/compat/regex/regexec.c index 2eeec82f40..c08f1bbe1f 100644 --- a/compat/regex/regexec.c +++ b/compat/regex/regexec.c @@ -2210,7 +2210,7 @@ sift_states_bkref (const re_match_context_t *mctx, re_sift_context_t *sctx, /* mctx->bkref_ents may have changed, reload the pointer. */ entry = mctx->bkref_ents + enabled_idx; } - while (enabled_idx++, entry++->more); + while ((void)enabled_idx++, entry++->more); } err = REG_NOERROR; free_return: diff --git a/compiler-tricks/not-constant.c b/compiler-tricks/not-constant.c new file mode 100644 index 0000000000..1da3ffc2f5 --- /dev/null +++ b/compiler-tricks/not-constant.c @@ -0,0 +1,2 @@ +#include <git-compat-util.h> +int false_but_the_compiler_does_not_know_it_; @@ -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; @@ -2521,6 +2516,10 @@ void repo_config_clear(struct repository *repo) void repo_config(struct repository *repo, config_fn_t fn, void *data) { + if (!repo) { + read_very_early_config(fn, data); + return; + } git_config_check_init(repo); configset_iter(repo->config, fn, data); } @@ -219,6 +219,15 @@ void read_very_early_config(config_fn_t cb, void *data); * repo-specific one; by overwriting, the higher-priority repo-specific * value is left at the end). * + * In cases where the repository variable is NULL, repo_config() will + * skip the per-repository config but retain system and global configs + * by calling read_very_early_config() which also ignores one-time + * overrides like "git -c var=val". This is to support handling "git foo -h" + * (which lets git.c:run_builtin() to pass NULL and have the cmd_foo() + * call repo_config() before calling parse_options() to notice "-h", give + * help and exit) for a command that ordinarily require a repository + * so this limitation may be OK (but if needed you are welcome to fix it). + * * Unlike git_config_from_file(), this function respects includes. */ void repo_config(struct repository *r, config_fn_t fn, void *); diff --git a/config.mak.dev b/config.mak.dev index 0fd8cc4d35..e86b6e1b34 100644 --- a/config.mak.dev +++ b/config.mak.dev @@ -39,6 +39,11 @@ DEVELOPER_CFLAGS += -Wunused DEVELOPER_CFLAGS += -Wvla 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 diff --git a/contrib/coccinelle/meson.build b/contrib/coccinelle/meson.build index 5d76a7fee6..ea054c924f 100644 --- a/contrib/coccinelle/meson.build +++ b/contrib/coccinelle/meson.build @@ -1,4 +1,9 @@ -spatch = find_program('spatch', required: get_option('coccinelle')) +coccinelle_opt = get_option('coccinelle').require( + fs.exists(meson.project_source_root() / '.git'), + error_message: 'coccinelle can only be run from a git checkout', +) + +spatch = find_program('spatch', required: coccinelle_opt) if not spatch.found() subdir_done() endif diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 413911be3b..e3d88b0672 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -234,6 +234,17 @@ __git_dequote () done } +# Prints the number of slash-separated components in a path. +# 1: Path to count components of. +__git_count_path_components () +{ + local path="$1" + local relative="${path#/}" + relative="${relative%/}" + local slashes="/${relative//[^\/]}" + echo "${#slashes}" +} + # The following function is based on code from: # # bash_completion - programmable completion functions for bash 3.2+ @@ -779,16 +790,39 @@ __git_tags () __git_dwim_remote_heads () { local pfx="${1-}" cur_="${2-}" sfx="${3-}" - local fer_pfx="${pfx//\%/%%}" # "escape" for-each-ref format specifiers # employ the heuristic used by git checkout and git switch # Try to find a remote branch that cur_es the completion word # but only output if the branch name is unique - __git for-each-ref --format="$fer_pfx%(refname:strip=3)$sfx" \ - --sort="refname:strip=3" \ - ${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \ - "refs/remotes/*/$cur_*" "refs/remotes/*/$cur_*/**" | \ - uniq -u + local awk_script=' + function casemap(s) { + if (ENVIRON["IGNORE_CASE"]) + return tolower(s) + else + return s + } + BEGIN { + split(ENVIRON["REMOTES"], remotes, /\n/) + for (i in remotes) + remotes[i] = "refs/remotes/" casemap(remotes[i]) + cur_ = casemap(ENVIRON["CUR_"]) + } + { + ref_case = casemap($0) + for (i in remotes) { + if (index(ref_case, remotes[i] "/" cur_) == 1) { + branch = substr($0, length(remotes[i] "/") + 1) + print ENVIRON["PFX"] branch ENVIRON["SFX"] + break + } + } + } + ' + __git for-each-ref --format='%(refname)' refs/remotes/ | + PFX="$pfx" SFX="$sfx" CUR_="$cur_" \ + IGNORE_CASE=${GIT_COMPLETION_IGNORE_CASE+1} \ + REMOTES="$(__git_remotes | sort -r)" awk "$awk_script" | + sort | uniq -u } # Lists refs from the local (by default) or from a remote repository. @@ -894,7 +928,8 @@ __git_refs () case "HEAD" in $match*|$umatch*) echo "${pfx}HEAD$sfx" ;; esac - __git for-each-ref --format="$fer_pfx%(refname:strip=3)$sfx" \ + local strip="$(__git_count_path_components "refs/remotes/$remote")" + __git for-each-ref --format="$fer_pfx%(refname:strip=$strip)$sfx" \ ${GIT_COMPLETION_IGNORE_CASE+--ignore-case} \ "refs/remotes/$remote/$match*" \ "refs/remotes/$remote/$match*/**" 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/decorate.c b/decorate.c index e161e13772..9f24925263 100644 --- a/decorate.c +++ b/decorate.c @@ -3,8 +3,6 @@ * data. */ -#define DISABLE_SIGN_COMPARE_WARNINGS - #include "git-compat-util.h" #include "object.h" #include "decorate.h" @@ -16,9 +14,8 @@ static unsigned int hash_obj(const struct object *obj, unsigned int n) static void *insert_decoration(struct decoration *n, const struct object *base, void *decoration) { - int size = n->size; struct decoration_entry *entries = n->entries; - unsigned int j = hash_obj(base, size); + unsigned int j = hash_obj(base, n->size); while (entries[j].base) { if (entries[j].base == base) { @@ -26,7 +23,7 @@ static void *insert_decoration(struct decoration *n, const struct object *base, entries[j].decoration = decoration; return old; } - if (++j >= size) + if (++j >= n->size) j = 0; } entries[j].base = base; @@ -37,8 +34,8 @@ static void *insert_decoration(struct decoration *n, const struct object *base, static void grow_decoration(struct decoration *n) { - int i; - int old_size = n->size; + unsigned int i; + unsigned int old_size = n->size; struct decoration_entry *old_entries = n->entries; n->size = (old_size + 1000) * 3 / 2; @@ -59,9 +56,7 @@ static void grow_decoration(struct decoration *n) void *add_decoration(struct decoration *n, const struct object *obj, void *decoration) { - int nr = n->nr + 1; - - if (nr > n->size * 2 / 3) + if ((n->nr + 1) > n->size * 2 / 3) grow_decoration(n); return insert_decoration(n, obj, decoration); } 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; } @@ -7085,7 +7087,7 @@ void diffcore_std(struct diff_options *options) diffcore_order(options->orderfile); if (options->rotate_to) diffcore_rotate(options); - if (!options->found_follow) + if (!options->found_follow && !options->skip_resolving_statuses) /* See try_to_follow_renames() in tree-diff.c */ diff_resolve_rename_copy(); diffcore_apply_filter(options); @@ -7161,16 +7163,19 @@ void compute_diffstat(struct diff_options *options, options->found_changes = !!diffstat->nr; } -void diff_addremove(struct diff_options *options, - int addremove, unsigned mode, - const struct object_id *oid, - int oid_valid, - const char *concatpath, unsigned dirty_submodule) +struct diff_filepair *diff_queue_addremove(struct diff_queue_struct *queue, + struct diff_options *options, + int addremove, unsigned mode, + const struct object_id *oid, + int oid_valid, + const char *concatpath, + unsigned dirty_submodule) { struct diff_filespec *one, *two; + struct diff_filepair *pair; if (S_ISGITLINK(mode) && is_submodule_ignored(concatpath, options)) - return; + return NULL; /* This may look odd, but it is a preparation for * feeding "there are unchanged files which should @@ -7190,7 +7195,7 @@ void diff_addremove(struct diff_options *options, if (options->prefix && strncmp(concatpath, options->prefix, options->prefix_length)) - return; + return NULL; one = alloc_filespec(concatpath); two = alloc_filespec(concatpath); @@ -7202,25 +7207,29 @@ void diff_addremove(struct diff_options *options, two->dirty_submodule = dirty_submodule; } - diff_queue(&diff_queued_diff, one, two); + pair = diff_queue(queue, one, two); if (!options->flags.diff_from_contents) options->flags.has_changes = 1; + + return pair; } -void diff_change(struct diff_options *options, - unsigned old_mode, unsigned new_mode, - const struct object_id *old_oid, - const struct object_id *new_oid, - int old_oid_valid, int new_oid_valid, - const char *concatpath, - unsigned old_dirty_submodule, unsigned new_dirty_submodule) +struct diff_filepair *diff_queue_change(struct diff_queue_struct *queue, + struct diff_options *options, + unsigned old_mode, unsigned new_mode, + const struct object_id *old_oid, + const struct object_id *new_oid, + int old_oid_valid, int new_oid_valid, + const char *concatpath, + unsigned old_dirty_submodule, + unsigned new_dirty_submodule) { struct diff_filespec *one, *two; struct diff_filepair *p; if (S_ISGITLINK(old_mode) && S_ISGITLINK(new_mode) && is_submodule_ignored(concatpath, options)) - return; + return NULL; if (options->flags.reverse_diff) { SWAP(old_mode, new_mode); @@ -7231,7 +7240,7 @@ void diff_change(struct diff_options *options, if (options->prefix && strncmp(concatpath, options->prefix, options->prefix_length)) - return; + return NULL; one = alloc_filespec(concatpath); two = alloc_filespec(concatpath); @@ -7239,19 +7248,42 @@ void diff_change(struct diff_options *options, fill_filespec(two, new_oid, new_oid_valid, new_mode); one->dirty_submodule = old_dirty_submodule; two->dirty_submodule = new_dirty_submodule; - p = diff_queue(&diff_queued_diff, one, two); + p = diff_queue(queue, one, two); if (options->flags.diff_from_contents) - return; + return p; if (options->flags.quick && options->skip_stat_unmatch && !diff_filespec_check_stat_unmatch(options->repo, p)) { diff_free_filespec_data(p->one); diff_free_filespec_data(p->two); - return; + return p; } options->flags.has_changes = 1; + + return p; +} + +void diff_addremove(struct diff_options *options, int addremove, unsigned mode, + const struct object_id *oid, int oid_valid, + const char *concatpath, unsigned dirty_submodule) +{ + diff_queue_addremove(&diff_queued_diff, options, addremove, mode, oid, + oid_valid, concatpath, dirty_submodule); +} + +void diff_change(struct diff_options *options, + unsigned old_mode, unsigned new_mode, + const struct object_id *old_oid, + const struct object_id *new_oid, + int old_oid_valid, int new_oid_valid, + const char *concatpath, + unsigned old_dirty_submodule, unsigned new_dirty_submodule) +{ + diff_queue_change(&diff_queued_diff, options, old_mode, new_mode, + old_oid, new_oid, old_oid_valid, new_oid_valid, + concatpath, old_dirty_submodule, new_dirty_submodule); } struct diff_filepair *diff_unmerge(struct diff_options *options, const char *path) @@ -353,6 +353,14 @@ struct diff_options { /* to support internal diff recursion by --follow hack*/ int found_follow; + /* + * By default, diffcore_std() resolves the statuses for queued diff file + * pairs by calling diff_resolve_rename_copy(). If status information + * has already been manually set, this option prevents diffcore_std() + * from resetting statuses. + */ + int skip_resolving_statuses; + /* Callback which allows tweaking the options in diff_setup_done(). */ void (*set_default)(struct diff_options *); @@ -508,6 +516,31 @@ void diff_set_default_prefix(struct diff_options *options); int diff_can_quit_early(struct diff_options *); +/* + * Stages changes in the provided diff queue for file additions and deletions. + * If a file pair gets queued, it is returned. + */ +struct diff_filepair *diff_queue_addremove(struct diff_queue_struct *queue, + struct diff_options *, + int addremove, unsigned mode, + const struct object_id *oid, + int oid_valid, const char *fullpath, + unsigned dirty_submodule); + +/* + * Stages changes in the provided diff queue for file modifications. + * If a file pair gets queued, it is returned. + */ +struct diff_filepair *diff_queue_change(struct diff_queue_struct *queue, + struct diff_options *, + unsigned mode1, unsigned mode2, + const struct object_id *old_oid, + const struct object_id *new_oid, + int old_oid_valid, int new_oid_valid, + const char *fullpath, + unsigned dirty_submodule1, + unsigned dirty_submodule2); + void diff_addremove(struct diff_options *, int addremove, unsigned mode, @@ -656,7 +689,7 @@ void flush_one_hunk(struct object_id *result, struct git_hash_ctx *ctx); int diff_result_code(struct rev_info *); -int diff_no_index(struct rev_info *, +int diff_no_index(struct rev_info *, const struct git_hash_algo *algop, int implicit_no_index, int, const char **); int index_differs_from(struct repository *r, const char *def, diff --git a/diffcore-rename.c b/diffcore-rename.c index 91b77993c7..8077283fc7 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -33,7 +33,7 @@ static struct diff_rename_dst *locate_rename_dst(struct diff_filepair *p) { /* Lookup by p->ONE->path */ int idx = break_idx ? strintmap_get(break_idx, p->one->path) : -1; - return (idx == -1) ? NULL : &rename_dst[idx]; + return (idx == -1 || idx == rename_dst_nr) ? NULL : &rename_dst[idx]; } /* @@ -1406,7 +1406,7 @@ void diffcore_rename_extended(struct diff_options *options, trace2_region_enter("diff", "setup", options->repo); info.setup = 0; - assert(!dir_rename_count || strmap_empty(dir_rename_count)); + ASSERT(!dir_rename_count || strmap_empty(dir_rename_count)); want_copies = (detect_rename == DIFF_DETECT_COPY); if (dirs_removed && (break_idx || want_copies)) BUG("dirs_removed incompatible with break/copy detection"); @@ -1669,9 +1669,10 @@ void diffcore_rename_extended(struct diff_options *options, if (DIFF_PAIR_BROKEN(p)) { /* broken delete */ struct diff_rename_dst *dst = locate_rename_dst(p); - if (!dst) - BUG("tracking failed somehow; failed to find associated dst for broken pair"); - if (dst->is_rename) + if (options->single_follow && dst && + strcmp(dst->p->two->path, p->two->path)) + dst = NULL; + if (dst && dst->is_rename) /* counterpart is now rename/copy */ pair_to_free = p; } diff --git a/dir-iterator.c b/dir-iterator.c index de619846f2..857e1d9bda 100644 --- a/dir-iterator.c +++ b/dir-iterator.c @@ -193,9 +193,9 @@ int dir_iterator_advance(struct dir_iterator *dir_iterator) if (S_ISDIR(iter->base.st.st_mode) && push_level(iter)) { if (errno != ENOENT && iter->flags & DIR_ITERATOR_PEDANTIC) - goto error_out; + return ITER_ERROR; if (iter->levels_nr == 0) - goto error_out; + return ITER_ERROR; } /* Loop until we find an entry that we can give back to the caller. */ @@ -211,11 +211,11 @@ int dir_iterator_advance(struct dir_iterator *dir_iterator) int ret = next_directory_entry(level->dir, iter->base.path.buf, &de); if (ret < 0) { if (iter->flags & DIR_ITERATOR_PEDANTIC) - goto error_out; + return ITER_ERROR; continue; } else if (ret > 0) { if (pop_level(iter) == 0) - return dir_iterator_abort(dir_iterator); + return ITER_DONE; continue; } @@ -223,7 +223,7 @@ int dir_iterator_advance(struct dir_iterator *dir_iterator) } else { if (level->entries_idx >= level->entries.nr) { if (pop_level(iter) == 0) - return dir_iterator_abort(dir_iterator); + return ITER_DONE; continue; } @@ -232,22 +232,21 @@ int dir_iterator_advance(struct dir_iterator *dir_iterator) if (prepare_next_entry_data(iter, name)) { if (errno != ENOENT && iter->flags & DIR_ITERATOR_PEDANTIC) - goto error_out; + return ITER_ERROR; continue; } return ITER_OK; } - -error_out: - dir_iterator_abort(dir_iterator); - return ITER_ERROR; } -int dir_iterator_abort(struct dir_iterator *dir_iterator) +void dir_iterator_free(struct dir_iterator *dir_iterator) { struct dir_iterator_int *iter = (struct dir_iterator_int *)dir_iterator; + if (!iter) + return; + for (; iter->levels_nr; iter->levels_nr--) { struct dir_iterator_level *level = &iter->levels[iter->levels_nr - 1]; @@ -266,7 +265,6 @@ int dir_iterator_abort(struct dir_iterator *dir_iterator) free(iter->levels); strbuf_release(&iter->base.path); free(iter); - return ITER_DONE; } struct dir_iterator *dir_iterator_begin(const char *path, unsigned int flags) @@ -301,7 +299,7 @@ struct dir_iterator *dir_iterator_begin(const char *path, unsigned int flags) return dir_iterator; error_out: - dir_iterator_abort(dir_iterator); + dir_iterator_free(dir_iterator); errno = saved_errno; return NULL; } diff --git a/dir-iterator.h b/dir-iterator.h index 6d438809b6..ccd6a19734 100644 --- a/dir-iterator.h +++ b/dir-iterator.h @@ -28,7 +28,7 @@ * * while ((ok = dir_iterator_advance(iter)) == ITER_OK) { * if (want_to_stop_iteration()) { - * ok = dir_iterator_abort(iter); + * ok = ITER_DONE; * break; * } * @@ -39,6 +39,7 @@ * * if (ok != ITER_DONE) * handle_error(); + * dir_iterator_free(iter); * * Callers are allowed to modify iter->path while they are working, * but they must restore it to its original contents before calling @@ -107,11 +108,7 @@ struct dir_iterator *dir_iterator_begin(const char *path, unsigned int flags); */ int dir_iterator_advance(struct dir_iterator *iterator); -/* - * End the iteration before it has been exhausted. Free the - * dir_iterator and any associated resources and return ITER_DONE. On - * error, free the dir_iterator and return ITER_ERROR. - */ -int dir_iterator_abort(struct dir_iterator *iterator); +/* Free the dir_iterator and any associated resources. */ +void dir_iterator_free(struct dir_iterator *iterator); #endif @@ -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; @@ -43,7 +43,6 @@ struct repository; * */ -struct repository; struct dir_entry { unsigned int len; 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..91d854dcb0 100644 --- a/environment.h +++ b/environment.h @@ -154,7 +154,6 @@ 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/ewah/ewah_bitmap.c b/ewah/ewah_bitmap.c index 67f8f588e0..056c410efb 100644 --- a/ewah/ewah_bitmap.c +++ b/ewah/ewah_bitmap.c @@ -371,6 +371,39 @@ void ewah_iterator_init(struct ewah_iterator *it, struct ewah_bitmap *parent) read_new_rlw(it); } +void ewah_or_iterator_init(struct ewah_or_iterator *it, + struct ewah_bitmap **parents, size_t nr) +{ + size_t i; + + memset(it, 0, sizeof(*it)); + + ALLOC_ARRAY(it->its, nr); + for (i = 0; i < nr; i++) + ewah_iterator_init(&it->its[it->nr++], parents[i]); +} + +int ewah_or_iterator_next(eword_t *next, struct ewah_or_iterator *it) +{ + eword_t buf, out = 0; + size_t i; + int ret = 0; + + for (i = 0; i < it->nr; i++) + if (ewah_iterator_next(&buf, &it->its[i])) { + out |= buf; + ret = 1; + } + + *next = out; + return ret; +} + +void ewah_or_iterator_release(struct ewah_or_iterator *it) +{ + free(it->its); +} + void ewah_xor( struct ewah_bitmap *ewah_i, struct ewah_bitmap *ewah_j, diff --git a/ewah/ewok.h b/ewah/ewok.h index 5e357e2493..c29d354236 100644 --- a/ewah/ewok.h +++ b/ewah/ewok.h @@ -148,6 +148,18 @@ void ewah_iterator_init(struct ewah_iterator *it, struct ewah_bitmap *parent); */ int ewah_iterator_next(eword_t *next, struct ewah_iterator *it); +struct ewah_or_iterator { + struct ewah_iterator *its; + size_t nr; +}; + +void ewah_or_iterator_init(struct ewah_or_iterator *it, + struct ewah_bitmap **parents, size_t nr); + +int ewah_or_iterator_next(eword_t *next, struct ewah_or_iterator *it); + +void ewah_or_iterator_release(struct ewah_or_iterator *it); + void ewah_xor( struct ewah_bitmap *ewah_i, struct ewah_bitmap *ewah_j, @@ -30,6 +30,8 @@ enum fsck_msg_type { FUNC(BAD_EMAIL, ERROR) \ FUNC(BAD_NAME, ERROR) \ FUNC(BAD_OBJECT_SHA1, ERROR) \ + FUNC(BAD_PACKED_REF_ENTRY, ERROR) \ + FUNC(BAD_PACKED_REF_HEADER, ERROR) \ FUNC(BAD_PARENT_SHA1, ERROR) \ FUNC(BAD_REF_CONTENT, ERROR) \ FUNC(BAD_REF_FILETYPE, ERROR) \ @@ -53,6 +55,8 @@ enum fsck_msg_type { FUNC(MISSING_TYPE, ERROR) \ FUNC(MISSING_TYPE_ENTRY, ERROR) \ FUNC(MULTIPLE_AUTHORS, ERROR) \ + FUNC(PACKED_REF_ENTRY_NOT_TERMINATED, ERROR) \ + FUNC(PACKED_REF_UNSORTED, ERROR) \ FUNC(TREE_NOT_SORTED, ERROR) \ FUNC(UNKNOWN_TYPE, ERROR) \ FUNC(ZERO_PADDED_DATE, ERROR) \ diff --git a/generate-configlist.sh b/generate-configlist.sh index dffdaada8b..b06da53c89 100755 --- a/generate-configlist.sh +++ b/generate-configlist.sh @@ -13,10 +13,18 @@ print_config_list () { cat <<EOF static const char *config_name_list[] = { EOF - grep -h '^[a-zA-Z].*\..*::$' "$SOURCE_DIR"/Documentation/*config.adoc "$SOURCE_DIR"/Documentation/config/*.adoc | - sed '/deprecated/d; s/::$//; s/, */\n/g' | - sort | - sed 's/^.*$/ "&",/' + sed -E ' +/^`?[a-zA-Z].*\..*`?::$/ { + /deprecated/d; + s/::$//; + s/`//g; + s/^.*$/ "&",/; + s/, */",\n "/g; + p;}; +d' \ + "$SOURCE_DIR"/Documentation/*config.adoc \ + "$SOURCE_DIR"/Documentation/config/*.adoc| + sort cat <<EOF NULL, }; diff --git a/git-compat-util.h b/git-compat-util.h index e123288e8f..afa040086f 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -23,26 +23,9 @@ #include <crtdbg.h> #endif -struct strbuf; - - -#define _FILE_OFFSET_BITS 64 +#include "compat/posix.h" - -/* Derived from Linux "Features Test Macro" header - * Convenience macros to test the versions of gcc (or - * a compatible compiler). - * Use them like this: - * #if GIT_GNUC_PREREQ (2,8) - * ... code requiring gcc 2.8 or later ... - * #endif -*/ -#if defined(__GNUC__) && defined(__GNUC_MINOR__) -# define GIT_GNUC_PREREQ(maj, min) \ - ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min)) -#else - #define GIT_GNUC_PREREQ(maj, min) 0 -#endif +struct strbuf; #if defined(__GNUC__) || defined(__clang__) # define PRAGMA(pragma) _Pragma(#pragma) @@ -176,71 +159,6 @@ DISABLE_WARNING(-Wsign-compare) /* Approximation of the length of the decimal representation of this type. */ #define decimal_length(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1) -#ifdef __MINGW64__ -#define _POSIX_C_SOURCE 1 -#elif defined(__sun__) - /* - * On Solaris, when _XOPEN_EXTENDED is set, its header file - * forces the programs to be XPG4v2, defeating any _XOPEN_SOURCE - * setting to say we are XPG5 or XPG6. Also on Solaris, - * XPG6 programs must be compiled with a c99 compiler, while - * non XPG6 programs must be compiled with a pre-c99 compiler. - */ -# if __STDC_VERSION__ - 0 >= 199901L -# define _XOPEN_SOURCE 600 -# else -# define _XOPEN_SOURCE 500 -# endif -#elif !defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__USLC__) && \ - !defined(_M_UNIX) && !defined(__sgi) && !defined(__DragonFly__) && \ - !defined(__TANDEM) && !defined(__QNX__) && !defined(__MirBSD__) && \ - !defined(__CYGWIN__) -#define _XOPEN_SOURCE 600 /* glibc2 and AIX 5.3L need 500, OpenBSD needs 600 for S_ISLNK() */ -#define _XOPEN_SOURCE_EXTENDED 1 /* AIX 5.3L needs this */ -#endif -#define _ALL_SOURCE 1 -#define _GNU_SOURCE 1 -#define _BSD_SOURCE 1 -#define _DEFAULT_SOURCE 1 -#define _NETBSD_SOURCE 1 -#define _SGI_SOURCE 1 - -/* - * UNUSED marks a function parameter that is always unused. It also - * can be used to annotate a function, a variable, or a type that is - * always unused. - * - * A callback interface may dictate that a function accepts a - * parameter at that position, but the implementation of the function - * may not need to use the parameter. In such a case, mark the parameter - * with UNUSED. - * - * When a parameter may be used or unused, depending on conditional - * compilation, consider using MAYBE_UNUSED instead. - */ -#if GIT_GNUC_PREREQ(4, 5) -#define UNUSED __attribute__((unused)) \ - __attribute__((deprecated ("parameter declared as UNUSED"))) -#elif defined(__GNUC__) -#define UNUSED __attribute__((unused)) \ - __attribute__((deprecated)) -#else -#define UNUSED -#endif - -#if defined(WIN32) && !defined(__CYGWIN__) /* Both MinGW and MSVC */ -# if !defined(_WIN32_WINNT) -# define _WIN32_WINNT 0x0600 -# endif -#define WIN32_LEAN_AND_MEAN /* stops windows.h including winsock.h */ -#include <winsock2.h> -#ifndef NO_UNIX_SOCKETS -#include <afunix.h> -#endif -#include <windows.h> -#define GIT_WINDOWS_NATIVE -#endif - #if defined(NO_UNIX_SOCKETS) || !defined(GIT_WINDOWS_NATIVE) static inline int _have_unix_sockets(void) { @@ -253,45 +171,6 @@ static inline int _have_unix_sockets(void) #define have_unix_sockets _have_unix_sockets #endif -#include <unistd.h> -#include <stdio.h> -#include <sys/stat.h> -#include <fcntl.h> -#include <stddef.h> -#include <stdlib.h> -#include <stdarg.h> -#include <stdbool.h> -#include <string.h> -#ifdef HAVE_STRINGS_H -#include <strings.h> /* for strcasecmp() */ -#endif -#include <errno.h> -#include <limits.h> -#include <locale.h> -#ifdef NEEDS_SYS_PARAM_H -#include <sys/param.h> -#endif -#include <sys/types.h> -#include <dirent.h> -#include <sys/time.h> -#include <time.h> -#include <signal.h> -#include <assert.h> -#include <regex.h> -#include <utime.h> -#include <syslog.h> -#if !defined(NO_POLL_H) -#include <poll.h> -#elif !defined(NO_SYS_POLL_H) -#include <sys/poll.h> -#else -/* Pull the compat stuff */ -#include <poll.h> -#endif -#ifdef HAVE_BSD_SYSCTL -#include <sys/sysctl.h> -#endif - /* Used by compat/win32/path-utils.h, and more */ static inline int is_xplatform_dir_sep(int c) { @@ -308,48 +187,6 @@ static inline int is_xplatform_dir_sep(int c) #elif defined(_MSC_VER) #include "compat/win32/path-utils.h" #include "compat/msvc.h" -#else -#include <sys/utsname.h> -#include <sys/wait.h> -#include <sys/resource.h> -#include <sys/socket.h> -#include <sys/ioctl.h> -#include <sys/statvfs.h> -#include <termios.h> -#ifndef NO_SYS_SELECT_H -#include <sys/select.h> -#endif -#include <netinet/in.h> -#include <netinet/tcp.h> -#include <arpa/inet.h> -#include <netdb.h> -#include <pwd.h> -#include <sys/un.h> -#ifndef NO_INTTYPES_H -#include <inttypes.h> -#else -#include <stdint.h> -#endif -#ifdef HAVE_ARC4RANDOM_LIBBSD -#include <bsd/stdlib.h> -#endif -#ifdef HAVE_GETRANDOM -#include <sys/random.h> -#endif -#ifdef NO_INTPTR_T -/* - * On I16LP32, ILP32 and LP64 "long" is the safe bet, however - * on LLP86, IL33LLP64 and P64 it needs to be "long long", - * while on IP16 and IP16L32 it is "int" (resp. "short") - * Size needs to match (or exceed) 'sizeof(void *)'. - * We can't take "long long" here as not everybody has it. - */ -typedef long intptr_t; -typedef unsigned long uintptr_t; -#endif -#undef _ALL_SOURCE /* AIX 5.3L defines a struct list with _ALL_SOURCE. */ -#include <grp.h> -#define _ALL_SOURCE 1 #endif /* used on Mac OS X */ @@ -370,60 +207,6 @@ static inline const char *precompose_string_if_needed(const char *in) #define probe_utf8_pathname_composition() #endif -#ifdef MKDIR_WO_TRAILING_SLASH -#define mkdir(a,b) compat_mkdir_wo_trailing_slash((a),(b)) -int compat_mkdir_wo_trailing_slash(const char*, mode_t); -#endif - -#ifdef time -#undef time -#endif -static inline time_t git_time(time_t *tloc) -{ - struct timeval tv; - - /* - * Avoid time(NULL), which can disagree with gettimeofday(2) - * and filesystem timestamps. - */ - gettimeofday(&tv, NULL); - - if (tloc) - *tloc = tv.tv_sec; - return tv.tv_sec; -} -#define time git_time - -#ifdef NO_STRUCT_ITIMERVAL -struct itimerval { - struct timeval it_interval; - struct timeval it_value; -}; -#endif - -#ifdef NO_SETITIMER -static inline int git_setitimer(int which UNUSED, - const struct itimerval *value UNUSED, - struct itimerval *newvalue UNUSED) { - return 0; /* pretend success */ -} -#undef setitimer -#define setitimer(which,value,ovalue) git_setitimer(which,value,ovalue) -#endif - -#ifndef NO_LIBGEN_H -#include <libgen.h> -#else -#define basename gitbasename -char *gitbasename(char *); -#define dirname gitdirname -char *gitdirname(char *); -#endif - -#ifndef NO_ICONV -#include <iconv.h> -#endif - #ifndef NO_OPENSSL #ifdef __APPLE__ #undef __AVAILABILITY_MACROS_USES_AVAILABILITY @@ -441,34 +224,6 @@ char *gitdirname(char *); # include <sys/sysinfo.h> #endif -/* On most systems <netdb.h> would have given us this, but - * not on some systems (e.g. z/OS). - */ -#ifndef NI_MAXHOST -#define NI_MAXHOST 1025 -#endif - -#ifndef NI_MAXSERV -#define NI_MAXSERV 32 -#endif - -/* On most systems <limits.h> would have given us this, but - * not on some systems (e.g. GNU/Hurd). - */ -#ifndef PATH_MAX -#define PATH_MAX 4096 -#endif - -#ifndef NAME_MAX -#define NAME_MAX 255 -#endif - -typedef uintmax_t timestamp_t; -#define PRItime PRIuMAX -#define parse_timestamp strtoumax -#define TIME_MAX UINTMAX_MAX -#define TIME_MIN 0 - #ifndef PATH_SEP #define PATH_SEP ':' #endif @@ -492,11 +247,6 @@ static inline int noop_core_config(const char *var UNUSED, #define platform_core_config noop_core_config #endif -int lstat_cache_aware_rmdir(const char *path); -#if !defined(__MINGW32__) && !defined(_MSC_VER) -#define rmdir lstat_cache_aware_rmdir -#endif - #ifndef has_dos_drive_prefix static inline int git_has_dos_drive_prefix(const char *path UNUSED) { @@ -824,25 +574,6 @@ static inline bool strip_suffix(const char *str, const char *suffix, memcpy(_swap_b_ptr, _swap_buffer, sizeof(a)); \ } while (0) -#if defined(NO_MMAP) || defined(USE_WIN32_MMAP) - -#ifndef PROT_READ -#define PROT_READ 1 -#define PROT_WRITE 2 -#define MAP_PRIVATE 1 -#endif - -#define mmap git_mmap -#define munmap git_munmap -void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); -int git_munmap(void *start, size_t length); - -#else /* NO_MMAP || USE_WIN32_MMAP */ - -#include <sys/mman.h> - -#endif /* NO_MMAP || USE_WIN32_MMAP */ - #ifdef NO_MMAP /* This value must be multiple of (pagesize * 2) */ @@ -858,177 +589,15 @@ int git_munmap(void *start, size_t length); #endif /* NO_MMAP */ -#ifndef MAP_FAILED -#define MAP_FAILED ((void *)-1) -#endif - #ifdef NO_ST_BLOCKS_IN_STRUCT_STAT #define on_disk_bytes(st) ((st).st_size) #else #define on_disk_bytes(st) ((st).st_blocks * 512) #endif -#ifdef NEEDS_MODE_TRANSLATION -#undef S_IFMT -#undef S_IFREG -#undef S_IFDIR -#undef S_IFLNK -#undef S_IFBLK -#undef S_IFCHR -#undef S_IFIFO -#undef S_IFSOCK -#define S_IFMT 0170000 -#define S_IFREG 0100000 -#define S_IFDIR 0040000 -#define S_IFLNK 0120000 -#define S_IFBLK 0060000 -#define S_IFCHR 0020000 -#define S_IFIFO 0010000 -#define S_IFSOCK 0140000 -#ifdef stat -#undef stat -#endif -#define stat(path, buf) git_stat(path, buf) -int git_stat(const char *, struct stat *); -#ifdef fstat -#undef fstat -#endif -#define fstat(fd, buf) git_fstat(fd, buf) -int git_fstat(int, struct stat *); -#ifdef lstat -#undef lstat -#endif -#define lstat(path, buf) git_lstat(path, buf) -int git_lstat(const char *, struct stat *); -#endif - #define DEFAULT_PACKED_GIT_LIMIT \ ((1024L * 1024L) * (size_t)(sizeof(void*) >= 8 ? (32 * 1024L * 1024L) : 256)) -#ifdef NO_PREAD -#define pread git_pread -ssize_t git_pread(int fd, void *buf, size_t count, off_t offset); -#endif - -#ifdef NO_SETENV -#define setenv gitsetenv -int gitsetenv(const char *, const char *, int); -#endif - -#ifdef NO_MKDTEMP -#define mkdtemp gitmkdtemp -char *gitmkdtemp(char *); -#endif - -#ifdef NO_UNSETENV -#define unsetenv gitunsetenv -int gitunsetenv(const char *); -#endif - -#ifdef NO_STRCASESTR -#define strcasestr gitstrcasestr -char *gitstrcasestr(const char *haystack, const char *needle); -#endif - -#ifdef NO_STRLCPY -#define strlcpy gitstrlcpy -size_t gitstrlcpy(char *, const char *, size_t); -#endif - -#ifdef NO_STRTOUMAX -#define strtoumax gitstrtoumax -uintmax_t gitstrtoumax(const char *, char **, int); -#define strtoimax gitstrtoimax -intmax_t gitstrtoimax(const char *, char **, int); -#endif - -#ifdef NO_HSTRERROR -#define hstrerror githstrerror -const char *githstrerror(int herror); -#endif - -#ifdef NO_MEMMEM -#define memmem gitmemmem -void *gitmemmem(const void *haystack, size_t haystacklen, - const void *needle, size_t needlelen); -#endif - -#ifdef OVERRIDE_STRDUP -#ifdef strdup -#undef strdup -#endif -#define strdup gitstrdup -char *gitstrdup(const char *s); -#endif - -#ifdef NO_GETPAGESIZE -#define getpagesize() sysconf(_SC_PAGESIZE) -#endif - -#ifndef O_CLOEXEC -#define O_CLOEXEC 0 -#endif - -#ifdef FREAD_READS_DIRECTORIES -# if !defined(SUPPRESS_FOPEN_REDEFINITION) -# ifdef fopen -# undef fopen -# endif -# define fopen(a,b) git_fopen(a,b) -# endif -FILE *git_fopen(const char*, const char*); -#endif - -#ifdef SNPRINTF_RETURNS_BOGUS -#ifdef snprintf -#undef snprintf -#endif -#define snprintf git_snprintf -int git_snprintf(char *str, size_t maxsize, - const char *format, ...); -#ifdef vsnprintf -#undef vsnprintf -#endif -#define vsnprintf git_vsnprintf -int git_vsnprintf(char *str, size_t maxsize, - const char *format, va_list ap); -#endif - -#ifdef OPEN_RETURNS_EINTR -#undef open -#define open git_open_with_retry -int git_open_with_retry(const char *path, int flag, ...); -#endif - -#ifdef __GLIBC_PREREQ -#if __GLIBC_PREREQ(2, 1) -#define HAVE_STRCHRNUL -#endif -#endif - -#ifndef HAVE_STRCHRNUL -#define strchrnul gitstrchrnul -static inline char *gitstrchrnul(const char *s, int c) -{ - while (*s && *s != c) - s++; - return (char *)s; -} -#endif - -#ifdef NO_INET_PTON -int inet_pton(int af, const char *src, void *dst); -#endif - -#ifdef NO_INET_NTOP -const char *inet_ntop(int af, const void *src, char *dst, size_t size); -#endif - -#ifdef NO_PTHREADS -#define atexit git_atexit -int git_atexit(void (*handler)(void)); -#endif - static inline size_t st_add(size_t a, size_t b) { if (unsigned_add_overflows(a, b)) @@ -1295,12 +864,6 @@ static inline size_t xsize_t(off_t len) return (size_t) len; } -#ifndef HOST_NAME_MAX -#define HOST_NAME_MAX 256 -#endif - -#include "sane-ctype.h" - /* * Like skip_prefix, but compare case-insensitively. Note that the comparison * is done via tolower(), so it is strictly ASCII (no multi-byte characters or @@ -1366,34 +929,6 @@ static inline int strtol_i(char const *s, int base, int *result) return 0; } -void git_stable_qsort(void *base, size_t nmemb, size_t size, - int(*compar)(const void *, const void *)); -#ifdef INTERNAL_QSORT -#define qsort git_stable_qsort -#endif - -#define QSORT(base, n, compar) sane_qsort((base), (n), sizeof(*(base)), compar) -static inline void sane_qsort(void *base, size_t nmemb, size_t size, - int(*compar)(const void *, const void *)) -{ - if (nmemb > 1) - qsort(base, nmemb, size, compar); -} - -#define STABLE_QSORT(base, n, compar) \ - git_stable_qsort((base), (n), sizeof(*(base)), compar) - -#ifndef HAVE_ISO_QSORT_S -int git_qsort_s(void *base, size_t nmemb, size_t size, - int (*compar)(const void *, const void *, void *), void *ctx); -#define qsort_s git_qsort_s -#endif - -#define QSORT_S(base, n, compar, ctx) do { \ - if (qsort_s((base), (n), sizeof(*(base)), compar, ctx)) \ - BUG("qsort_s() failed"); \ -} while (0) - #ifndef REG_STARTEND #error "Git requires REG_STARTEND support. Compile with NO_REGEX=NeedsStartEnd" #endif @@ -1418,39 +953,12 @@ int git_regcomp(regex_t *preg, const char *pattern, int cflags); # define FORCE_DIR_SET_GID 0 #endif -#ifdef NO_NSEC -#undef USE_NSEC -#define ST_CTIME_NSEC(st) 0 -#define ST_MTIME_NSEC(st) 0 -#else -#ifdef USE_ST_TIMESPEC -#define ST_CTIME_NSEC(st) ((unsigned int)((st).st_ctimespec.tv_nsec)) -#define ST_MTIME_NSEC(st) ((unsigned int)((st).st_mtimespec.tv_nsec)) -#else -#define ST_CTIME_NSEC(st) ((unsigned int)((st).st_ctim.tv_nsec)) -#define ST_MTIME_NSEC(st) ((unsigned int)((st).st_mtim.tv_nsec)) -#endif -#endif - #ifdef UNRELIABLE_FSTAT #define fstat_is_reliable() 0 #else #define fstat_is_reliable() 1 #endif -#ifndef va_copy -/* - * Since an obvious implementation of va_list would be to make it a - * pointer into the stack frame, a simple assignment will work on - * many systems. But let's try to be more portable. - */ -#ifdef __va_copy -#define va_copy(dst, src) __va_copy(dst, src) -#else -#define va_copy(dst, src) ((dst) = (src)) -#endif -#endif - /* usage.c: only to be used for testing BUG() implementation (see test-tool) */ extern int BUG_exit_code; @@ -1460,6 +968,8 @@ extern int bug_called_must_BUG; __attribute__((format (printf, 3, 4))) NORETURN void BUG_fl(const char *file, int line, const char *fmt, ...); #define BUG(...) BUG_fl(__FILE__, __LINE__, __VA_ARGS__) +/* ASSERT: like assert(), but won't be compiled out with NDEBUG */ +#define ASSERT(a) if (!(a)) BUG("Assertion `" #a "' failed.") __attribute__((format (printf, 3, 4))) void bug_fl(const char *file, int line, const char *fmt, ...); #define bug(...) bug_fl(__FILE__, __LINE__, __VA_ARGS__) @@ -1480,41 +990,6 @@ void bug_fl(const char *file, int line, const char *fmt, ...); # define SHELL_PATH "/bin/sh" #endif -#ifndef _POSIX_THREAD_SAFE_FUNCTIONS -static inline void git_flockfile(FILE *fh UNUSED) -{ - ; /* nothing */ -} -static inline void git_funlockfile(FILE *fh UNUSED) -{ - ; /* nothing */ -} -#undef flockfile -#undef funlockfile -#undef getc_unlocked -#define flockfile(fh) git_flockfile(fh) -#define funlockfile(fh) git_funlockfile(fh) -#define getc_unlocked(fh) getc(fh) -#endif - -#ifdef FILENO_IS_A_MACRO -int git_fileno(FILE *stream); -# ifndef COMPAT_CODE_FILENO -# undef fileno -# define fileno(p) git_fileno(p) -# endif -#endif - -#ifdef NEED_ACCESS_ROOT_HANDLER -int git_access(const char *path, int mode); -# ifndef COMPAT_CODE_ACCESS -# ifdef access -# undef access -# endif -# define access(path, mode) git_access(path, mode) -# endif -#endif - /* * Our code often opens a path to an optional file, to work on its * contents when we can successfully open it. We can ignore a failure @@ -1583,4 +1058,20 @@ static inline void *container_of_or_null_offset(void *ptr, size_t offset) ((uintptr_t)&(ptr)->member - (uintptr_t)(ptr)) #endif /* !__GNUC__ */ +/* + * Prevent an overly clever compiler from optimizing an expression + * out, triggering a false positive when building with the + * -Wunreachable-code option. false_but_the_compiler_does_not_know_it_ + * is defined in a compilation unit separate from where the macro is + * used, initialized to 0, and never modified. + */ +#define NOT_CONSTANT(expr) ((expr) || false_but_the_compiler_does_not_know_it_) +extern int false_but_the_compiler_does_not_know_it_; + +#ifdef CHECK_ASSERTION_SIDE_EFFECTS +#undef assert +extern int not_supposed_to_survive; +#define assert(expr) ((void)(not_supposed_to_survive || (expr))) +#endif /* CHECK_ASSERTION_SIDE_EFFECTS */ + #endif diff --git a/git-curl-compat.h b/git-curl-compat.h index 703756ba85..aa8eed7ed2 100644 --- a/git-curl-compat.h +++ b/git-curl-compat.h @@ -45,4 +45,11 @@ #define GIT_CURL_HAVE_CURLOPT_PROTOCOLS_STR 1 #endif +/** + * CURLOPT_TCP_KEEPCNT was added in 8.9.0, released in July, 2024. + */ +#if LIBCURL_VERSION_NUM >= 0x080900 +#define GIT_CURL_HAVE_CURLOPT_TCP_KEEPCNT +#endif + #endif 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 @@ -541,6 +541,7 @@ static struct cmd_struct commands[] = { { "diff", cmd_diff, NO_PARSEOPT }, { "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT }, { "diff-index", cmd_diff_index, RUN_SETUP | NO_PARSEOPT }, + { "diff-pairs", cmd_diff_pairs, RUN_SETUP | NO_PARSEOPT }, { "diff-tree", cmd_diff_tree, RUN_SETUP | NO_PARSEOPT }, { "difftool", cmd_difftool, RUN_SETUP_GENTLY }, { "fast-export", cmd_fast_export, RUN_SETUP }, @@ -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; +} @@ -193,17 +193,18 @@ struct object_id { int algo; /* XXX requires 4-byte alignment */ }; -#define GET_OID_QUIETLY 01 -#define GET_OID_COMMIT 02 -#define GET_OID_COMMITTISH 04 -#define GET_OID_TREE 010 -#define GET_OID_TREEISH 020 -#define GET_OID_BLOB 040 -#define GET_OID_FOLLOW_SYMLINKS 0100 -#define GET_OID_RECORD_PATH 0200 -#define GET_OID_ONLY_TO_DIE 04000 -#define GET_OID_REQUIRE_PATH 010000 -#define GET_OID_HASH_ANY 020000 +#define GET_OID_QUIETLY 01 +#define GET_OID_COMMIT 02 +#define GET_OID_COMMITTISH 04 +#define GET_OID_TREE 010 +#define GET_OID_TREEISH 020 +#define GET_OID_BLOB 040 +#define GET_OID_FOLLOW_SYMLINKS 0100 +#define GET_OID_RECORD_PATH 0200 +#define GET_OID_ONLY_TO_DIE 04000 +#define GET_OID_REQUIRE_PATH 010000 +#define GET_OID_HASH_ANY 020000 +#define GET_OID_SKIP_AMBIGUITY_CHECK 040000 #define GET_OID_DISAMBIGUATORS \ (GET_OID_COMMIT | GET_OID_COMMITTISH | \ @@ -325,7 +326,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) { @@ -340,7 +341,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) { @@ -104,6 +104,10 @@ static struct { }; #endif +static long curl_tcp_keepidle = -1; +static long curl_tcp_keepintvl = -1; +static long curl_tcp_keepcnt = -1; + enum proactive_auth { PROACTIVE_AUTH_NONE = 0, PROACTIVE_AUTH_IF_CREDENTIALS, @@ -438,11 +442,11 @@ static int http_options(const char *var, const char *value, return 0; } if (!strcmp("http.lowspeedlimit", var)) { - curl_low_speed_limit = (long)git_config_int(var, value, ctx->kvi); + curl_low_speed_limit = git_config_int(var, value, ctx->kvi); return 0; } if (!strcmp("http.lowspeedtime", var)) { - curl_low_speed_time = (long)git_config_int(var, value, ctx->kvi); + curl_low_speed_time = git_config_int(var, value, ctx->kvi); return 0; } @@ -557,6 +561,19 @@ static int http_options(const char *var, const char *value, return 0; } + if (!strcmp("http.keepaliveidle", var)) { + curl_tcp_keepidle = git_config_int(var, value, ctx->kvi); + return 0; + } + if (!strcmp("http.keepaliveinterval", var)) { + curl_tcp_keepintvl = git_config_int(var, value, ctx->kvi); + return 0; + } + if (!strcmp("http.keepalivecount", var)) { + curl_tcp_keepcnt = git_config_int(var, value, ctx->kvi); + return 0; + } + /* Fall back on the default ones */ return git_default_config(var, value, ctx, data); } @@ -704,11 +721,6 @@ static int has_proxy_cert_password(void) return 1; } -static void set_curl_keepalive(CURL *c) -{ - curl_easy_setopt(c, CURLOPT_TCP_KEEPALIVE, 1); -} - /* Return 1 if redactions have been made, 0 otherwise. */ static int redact_sensitive_header(struct strbuf *header, size_t offset) { @@ -1242,7 +1254,18 @@ static CURL *get_curl_handle(void) } init_curl_proxy_auth(result); - set_curl_keepalive(result); + curl_easy_setopt(result, CURLOPT_TCP_KEEPALIVE, 1); + + if (curl_tcp_keepidle > -1) + curl_easy_setopt(result, CURLOPT_TCP_KEEPIDLE, + curl_tcp_keepidle); + if (curl_tcp_keepintvl > -1) + curl_easy_setopt(result, CURLOPT_TCP_KEEPINTVL, + curl_tcp_keepintvl); +#ifdef GIT_CURL_HAVE_CURLOPT_TCP_KEEPCNT + if (curl_tcp_keepcnt > -1) + curl_easy_setopt(result, CURLOPT_TCP_KEEPCNT, curl_tcp_keepcnt); +#endif return result; } @@ -1256,10 +1279,30 @@ static void set_from_env(char **var, const char *envname) } } +static void set_long_from_env(long *var, const char *envname) +{ + const char *val = getenv(envname); + if (val) { + long tmp; + char *endp; + int saved_errno = errno; + + errno = 0; + tmp = strtol(val, &endp, 10); + + if (errno) + warning_errno(_("failed to parse %s"), envname); + else if (*endp || endp == val) + warning(_("failed to parse %s"), envname); + else + *var = tmp; + + errno = saved_errno; + } +} + void http_init(struct remote *remote, const char *url, int proactive_auth) { - char *low_speed_limit; - char *low_speed_time; char *normalized_url; struct urlmatch_config config = URLMATCH_CONFIG_INIT; @@ -1338,12 +1381,8 @@ void http_init(struct remote *remote, const char *url, int proactive_auth) set_from_env(&user_agent, "GIT_HTTP_USER_AGENT"); - low_speed_limit = getenv("GIT_HTTP_LOW_SPEED_LIMIT"); - if (low_speed_limit) - curl_low_speed_limit = strtol(low_speed_limit, NULL, 10); - low_speed_time = getenv("GIT_HTTP_LOW_SPEED_TIME"); - if (low_speed_time) - curl_low_speed_time = strtol(low_speed_time, NULL, 10); + set_long_from_env(&curl_low_speed_limit, "GIT_HTTP_LOW_SPEED_LIMIT"); + set_long_from_env(&curl_low_speed_time, "GIT_HTTP_LOW_SPEED_TIME"); if (curl_ssl_verify == -1) curl_ssl_verify = 1; @@ -1370,6 +1409,10 @@ void http_init(struct remote *remote, const char *url, int proactive_auth) ssl_cert_password_required = 1; } + set_long_from_env(&curl_tcp_keepidle, "GIT_TCP_KEEPIDLE"); + set_long_from_env(&curl_tcp_keepintvl, "GIT_TCP_KEEPINTVL"); + set_long_from_env(&curl_tcp_keepcnt, "GIT_TCP_KEEPCNT"); + curl_default = get_curl_handle(); } diff --git a/imap-send.c b/imap-send.c index 6c8f84e836..27dc033c7f 100644 --- a/imap-send.c +++ b/imap-send.c @@ -324,6 +324,8 @@ static int ssl_socket_connect(struct imap_socket *sock, cert = SSL_get_peer_certificate(sock->ssl); if (!cert) return error("unable to get peer certificate."); + if (SSL_get_verify_result(sock->ssl) != X509_V_OK) + return error("unable to verify peer certificate"); if (verify_hostname(cert, cfg->host) < 0) return -1; } diff --git a/iterator.h b/iterator.h index 0f6900e43a..6b77dcc262 100644 --- a/iterator.h +++ b/iterator.h @@ -12,7 +12,7 @@ #define ITER_OK 0 /* - * The iterator is exhausted and has been freed. + * The iterator is exhausted. */ #define ITER_DONE -1 @@ -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.c b/merge-ort-wrappers.c index d6f6135996..c54d56b344 100644 --- a/merge-ort-wrappers.c +++ b/merge-ort-wrappers.c @@ -1,9 +1,13 @@ #include "git-compat-util.h" #include "gettext.h" #include "hash.h" +#include "hex.h" +#include "lockfile.h" #include "merge-ort.h" #include "merge-ort-wrappers.h" #include "read-cache-ll.h" +#include "repository.h" +#include "tag.h" #include "tree.h" #include "commit.h" @@ -29,6 +33,7 @@ int merge_ort_nonrecursive(struct merge_options *opt, struct tree *merge_base) { struct merge_result result; + int show_msgs; if (unclean(opt, head)) return -1; @@ -38,9 +43,10 @@ int merge_ort_nonrecursive(struct merge_options *opt, return 1; } + show_msgs = !!opt->verbosity; memset(&result, 0, sizeof(result)); merge_incore_nonrecursive(opt, merge_base, head, merge, &result); - merge_switch_to_result(opt, head, &result, 1, 1); + merge_switch_to_result(opt, head, &result, 1, show_msgs); return result.clean; } @@ -53,14 +59,76 @@ int merge_ort_recursive(struct merge_options *opt, { struct tree *head = repo_get_commit_tree(opt->repo, side1); struct merge_result tmp; + int show_msgs; if (unclean(opt, head)) return -1; + show_msgs = !!opt->verbosity; memset(&tmp, 0, sizeof(tmp)); merge_incore_recursive(opt, merge_bases, side1, side2, &tmp); - merge_switch_to_result(opt, head, &tmp, 1, 1); + merge_switch_to_result(opt, head, &tmp, 1, show_msgs); *result = NULL; return tmp.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_ort_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 error(_("Could not parse object '%s'"), + oid_to_hex(&merge_bases[i])); + commit_list_insert(base, &ca); + } + } + + repo_hold_locked_index(opt->repo, &lock, LOCK_DIE_ON_ERROR); + clean = merge_ort_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 error(_("Unable to write index.")); + + return clean ? 0 : 1; +} diff --git a/merge-ort-wrappers.h b/merge-ort-wrappers.h index 90af1f69c5..aeffa1c87b 100644 --- a/merge-ort-wrappers.h +++ b/merge-ort-wrappers.h @@ -22,4 +22,16 @@ int merge_ort_recursive(struct merge_options *opt, const struct commit_list *ancestors, struct commit **result); +/* + * rename-detecting three-way merge. num_merge_bases must be at least 1. + * Recursive ancestor consolidation will be performed if num_merge_bases > 1. + * Wrapper mimicking the old merge_recursive_generic() function. + */ +int merge_ort_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/merge-ort.c b/merge-ort.c index 46e78c3ffa..12cf76fa05 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -791,7 +791,7 @@ static void path_msg(struct merge_options *opt, struct strbuf tmp = STRBUF_INIT; /* Sanity checks */ - assert(omittable_hint == + ASSERT(omittable_hint == (!starts_with(type_short_descriptions[type], "CONFLICT") && !starts_with(type_short_descriptions[type], "ERROR")) || type == CONFLICT_DIR_RENAME_SUGGESTED); @@ -1517,8 +1517,8 @@ static int handle_deferred_entries(struct merge_options *opt, struct strintmap copy; /* Loop over the set of paths we need to know rename info for */ - strset_for_each_entry(&renames->relevant_sources[side], - &iter, entry) { + strintmap_for_each_entry(&renames->relevant_sources[side], + &iter, entry) { char *rename_target, *dir, *dir_marker; struct strmap_entry *e; @@ -1642,7 +1642,7 @@ static int handle_deferred_entries(struct merge_options *opt, ci = strmap_get(&opt->priv->paths, path); VERIFY_CI(ci); - assert(renames->deferred[side].trivial_merges_okay && + ASSERT(renames->deferred[side].trivial_merges_okay && !strset_contains(&renames->deferred[side].target_dirs, path)); resolve_trivial_directory_merge(ci, side); @@ -1817,7 +1817,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 +2199,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 +2231,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 +2739,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 +2752,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. */ @@ -3048,7 +3048,8 @@ static int process_renames(struct merge_options *opt, } } - assert(source_deleted || oldinfo->filemask & old_sidemask); + assert(source_deleted || oldinfo->filemask & old_sidemask || + !strcmp(pair->one->path, pair->two->path)); /* Need to check for special types of rename conflicts... */ if (collision && !source_deleted) { @@ -3122,7 +3123,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; @@ -3404,6 +3405,11 @@ static int collect_renames(struct merge_options *opt, pool_diff_free_filepair(&opt->priv->pool, p); continue; } + if (opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_NONE && + p->status == 'R' && 1) { + possibly_cache_new_pair(renames, p, side_index, NULL); + goto skip_directory_renames; + } new_path = check_for_directory_rename(opt, p->two->path, side_index, @@ -3421,11 +3427,12 @@ static int collect_renames(struct merge_options *opt, if (new_path) apply_directory_rename_modifications(opt, p, new_path); +skip_directory_renames: /* * p->score comes back from diffcore_rename_extended() with - * the similarity of the renamed file. The similarity is - * was used to determine that the two files were related - * and are a rename, which we have already used, but beyond + * the similarity of the renamed file. The similarity was + * used to determine that the two files were related and + * are a rename, which we have already used, but beyond * that we have no use for the similarity. So p->score is * now irrelevant. However, process_renames() will need to * know which side of the merge this rename was associated @@ -3448,6 +3455,11 @@ static int detect_and_process_renames(struct merge_options *opt) if (!possible_renames(renames)) goto cleanup; + if (!opt->detect_renames) { + renames->redo_after_renames = 0; + renames->cached_pairs_valid_side = 0; + goto cleanup; + } trace2_region_enter("merge", "regular renames", opt->repo); detection_run |= detect_regular_renames(opt, MERGE_SIDE1); @@ -3994,7 +4006,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) { /* @@ -4041,7 +4053,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)); } /* @@ -4163,11 +4175,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; } @@ -4175,11 +4187,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; } @@ -4304,7 +4316,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; } @@ -4878,9 +4890,9 @@ 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 *make_virtual_commit(struct repository *repo, + struct tree *tree, + const char *comment) { struct commit *commit = alloc_commit_node(repo); @@ -5020,7 +5032,8 @@ static void merge_start(struct merge_options *opt, struct merge_result *result) trace2_region_leave("merge", "allocate/init", opt->repo); } -static void merge_check_renames_reusable(struct merge_result *result, +static void merge_check_renames_reusable(struct merge_options *opt, + struct merge_result *result, struct tree *merge_base, struct tree *side1, struct tree *side2) @@ -5046,6 +5059,26 @@ static void merge_check_renames_reusable(struct merge_result *result, } /* + * Avoid using cached renames when directory rename detection is + * turned off. Cached renames are far less important in that case, + * and they lead to testcases with an interesting intersection of + * effects from relevant renames optimization, trivial directory + * resolution optimization, and cached renames all converging when + * the target of a cached rename is in a directory that + * collect_merge_info() does not recurse into. To avoid such + * problems, simply disable cached renames for this case (similar + * to the rename/rename(1to1) case; see the "disabling the + * optimization" comment near that case). + * + * This could be revisited in the future; see the commit message + * where this comment was added for some possible pointers. + */ + if (opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_NONE) { + renames->cached_pairs_valid_side = 0; /* neither side valid */ + return; + } + + /* * Handle other cases; note that merge_trees[0..2] will only * be NULL if opti is, or if all three were manually set to * NULL by e.g. rename/rename(1to1) handling. @@ -5186,6 +5219,8 @@ static void merge_ort_internal(struct merge_options *opt, ancestor_name = "empty tree"; } else if (merge_bases) { ancestor_name = "merged common ancestors"; + } else if (opt->ancestor) { + ancestor_name = opt->ancestor; } else { strbuf_add_unique_abbrev(&merge_base_abbrev, &merged_merge_bases->object.oid, @@ -5251,7 +5286,7 @@ void merge_incore_nonrecursive(struct merge_options *opt, trace2_region_enter("merge", "merge_start", opt->repo); assert(opt->ancestor != NULL); - merge_check_renames_reusable(result, merge_base, side1, side2); + merge_check_renames_reusable(opt, result, merge_base, side1, side2); merge_start(opt, result); /* * Record the trees used in this merge, so if there's a next merge in @@ -5275,8 +5310,13 @@ void merge_incore_recursive(struct merge_options *opt, { trace2_region_enter("merge", "incore_recursive", opt->repo); - /* We set the ancestor label based on the merge_bases */ - assert(opt->ancestor == NULL); + /* + * We set the ancestor label based on the merge_bases...but we + * allow one exception through so that builtin/am can override + * with its constructed fake ancestor. + */ + assert(opt->ancestor == NULL || + (merge_bases && !merge_bases->next)); trace2_region_enter("merge", "merge_start", opt->repo); merge_start(opt, result); diff --git a/merge-ort.h b/merge-ort.h index 82f2b3222d..b63bc5424e 100644 --- a/merge-ort.h +++ b/merge-ort.h @@ -44,6 +44,11 @@ struct merge_result { unsigned _properly_initialized; }; +/* Mostly internal function also used by merge-ort-wrappers.c */ +struct commit *make_virtual_commit(struct repository *repo, + struct tree *tree, + const char *comment); + /* * rename-detecting three-way merge with recursive ancestor consolidation. * working tree and index are untouched. diff --git a/merge-recursive.c b/merge-recursive.c index 884ccf99a5..9d1e809bd2 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -502,7 +502,7 @@ static int get_tree_entry_if_blob(struct repository *r, ret = get_tree_entry(r, tree, path, &dfs->oid, &dfs->mode); if (S_ISDIR(dfs->mode)) { - oidcpy(&dfs->oid, null_oid()); + oidcpy(&dfs->oid, null_oid(the_hash_algo)); dfs->mode = 0; } return ret; @@ -1197,7 +1197,7 @@ static void print_commit(struct repository *repo, struct commit *commit) struct pretty_print_context ctx = {0}; ctx.date_mode.type = DATE_NORMAL; /* FIXME: Merge this with output_commit_title() */ - assert(!merge_remote_util(commit)); + 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); @@ -1238,7 +1238,7 @@ static int merge_submodule(struct merge_options *opt, if (is_null_oid(b)) return 0; - if (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) { + if (repo_submodule_init(&subrepo, opt->repo, path, null_oid(the_hash_algo))) { output(opt, 1, _("Failed to merge submodule %s (not checked out)"), path); return 0; } @@ -1698,7 +1698,7 @@ static int handle_file_collision(struct merge_options *opt, /* Store things in diff_filespecs for functions that need it */ null.path = (char *)collide_path; - oidcpy(&null.oid, null_oid()); + oidcpy(&null.oid, null_oid(the_hash_algo)); null.mode = 0; if (merge_mode_and_contents(opt, &null, a, b, collide_path, @@ -2897,14 +2897,14 @@ static int process_renames(struct merge_options *opt, dst_other.mode = ren1->dst_entry->stages[other_stage].mode; try_merge = 0; - if (oideq(&src_other.oid, null_oid()) && + if (oideq(&src_other.oid, null_oid(the_hash_algo)) && 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())) { + } else if (oideq(&src_other.oid, null_oid(the_hash_algo))) { setup_rename_conflict_info(RENAME_DELETE, opt, ren1, NULL); } else if ((dst_other.mode == ren1->pair->two->mode) && @@ -2923,7 +2923,7 @@ static int process_renames(struct merge_options *opt, 1, /* update_cache */ 0 /* update_wd */)) clean_merge = -1; - } else if (!oideq(&dst_other.oid, null_oid())) { + } else if (!oideq(&dst_other.oid, null_oid(the_hash_algo))) { /* * Probably not a clean merge, but it's * premature to set clean_merge to 0 here, diff --git a/mergetools/vimdiff b/mergetools/vimdiff index ffc9be86c8..78710858e8 100644 --- a/mergetools/vimdiff +++ b/mergetools/vimdiff @@ -305,6 +305,9 @@ gen_cmd () { elif echo "$LAYOUT" | grep @BASE >/dev/null then FINAL_TARGET="BASE" + elif echo "$LAYOUT" | grep @REMOTE >/dev/null + then + FINAL_TARGET="REMOTE" else FINAL_TARGET="MERGED" fi @@ -529,7 +532,7 @@ run_unit_tests () { # Function to make sure that we don't break anything when modifying this # script. - NUMBER_OF_TEST_CASES=16 + NUMBER_OF_TEST_CASES=19 TEST_CASE_01="(LOCAL,BASE,REMOTE)/MERGED" # default behaviour TEST_CASE_02="@LOCAL,REMOTE" # when using vimdiff1 @@ -547,6 +550,9 @@ run_unit_tests () { TEST_CASE_14="BASE,REMOTE+BASE,LOCAL" TEST_CASE_15=" (( (LOCAL , BASE , REMOTE) / MERGED)) +(BASE) , LOCAL+ BASE , REMOTE+ (((LOCAL / BASE / REMOTE)) , MERGED ) " TEST_CASE_16="LOCAL,BASE,REMOTE / MERGED + BASE,LOCAL + BASE,REMOTE + (LOCAL / BASE / REMOTE),MERGED" + TEST_CASE_17="(LOCAL,@BASE,REMOTE)/MERGED" + TEST_CASE_18="LOCAL,@REMOTE" + TEST_CASE_19="@REMOTE" EXPECTED_CMD_01="-c \"set hidden diffopt-=hiddenoff | echo | leftabove split | leftabove vertical split | 1b | wincmd l | leftabove vertical split | 2b | wincmd l | 3b | wincmd j | 4b | execute 'tabdo windo diffthis' | tabfirst\"" EXPECTED_CMD_02="-c \"set hidden diffopt-=hiddenoff | echo | leftabove vertical split | 1b | wincmd l | 3b | execute 'tabdo windo diffthis' | tabfirst\"" @@ -564,6 +570,9 @@ run_unit_tests () { EXPECTED_CMD_14="-c \"set hidden diffopt-=hiddenoff | echo | leftabove vertical split | 2b | wincmd l | 3b | tabnew | leftabove vertical split | 2b | wincmd l | 1b | execute 'tabdo windo diffthis' | tabfirst\"" EXPECTED_CMD_15="-c \"set hidden diffopt-=hiddenoff | echo | leftabove split | leftabove vertical split | 1b | wincmd l | leftabove vertical split | 2b | wincmd l | 3b | wincmd j | 4b | tabnew | leftabove vertical split | 2b | wincmd l | 1b | tabnew | leftabove vertical split | 2b | wincmd l | 3b | tabnew | leftabove vertical split | leftabove split | 1b | wincmd j | leftabove split | 2b | wincmd j | 3b | wincmd l | 4b | execute 'tabdo windo diffthis' | tabfirst\"" EXPECTED_CMD_16="-c \"set hidden diffopt-=hiddenoff | echo | leftabove split | leftabove vertical split | 1b | wincmd l | leftabove vertical split | 2b | wincmd l | 3b | wincmd j | 4b | tabnew | leftabove vertical split | 2b | wincmd l | 1b | tabnew | leftabove vertical split | 2b | wincmd l | 3b | tabnew | leftabove vertical split | leftabove split | 1b | wincmd j | leftabove split | 2b | wincmd j | 3b | wincmd l | 4b | execute 'tabdo windo diffthis' | tabfirst\"" + EXPECTED_CMD_17="-c \"set hidden diffopt-=hiddenoff | echo | leftabove split | leftabove vertical split | 1b | wincmd l | leftabove vertical split | 2b | wincmd l | 3b | wincmd j | 4b | execute 'tabdo windo diffthis' | tabfirst\"" + EXPECTED_CMD_18="-c \"set hidden diffopt-=hiddenoff | echo | leftabove vertical split | 1b | wincmd l | 3b | execute 'tabdo windo diffthis' | tabfirst\"" + EXPECTED_CMD_19="-c \"set hidden diffopt-=hiddenoff | echo | silent execute 'bufdo diffthis' | 3b | execute 'tabdo windo diffthis' | tabfirst\"" EXPECTED_TARGET_01="MERGED" EXPECTED_TARGET_02="LOCAL" @@ -581,6 +590,9 @@ run_unit_tests () { EXPECTED_TARGET_14="MERGED" EXPECTED_TARGET_15="MERGED" EXPECTED_TARGET_16="MERGED" + EXPECTED_TARGET_17="BASE" + EXPECTED_TARGET_18="REMOTE" + EXPECTED_TARGET_19="REMOTE" at_least_one_ko="false" diff --git a/meson.build b/meson.build index efe2871c9d..fe930cedc9 100644 --- a/meson.build +++ b/meson.build @@ -264,6 +264,7 @@ libgit_sources = [ 'compat/nonblock.c', 'compat/obstack.c', 'compat/terminal.c', + 'compiler-tricks/not-constant.c', 'config.c', 'connect.c', 'connected.c', @@ -310,6 +311,7 @@ libgit_sources = [ 'graph.c', 'grep.c', 'hash-lookup.c', + 'hash.c', 'hashmap.c', 'help.c', 'hex.c', @@ -540,6 +542,7 @@ builtin_sources = [ 'builtin/diagnose.c', 'builtin/diff-files.c', 'builtin/diff-index.c', + 'builtin/diff-pairs.c', 'builtin/diff-tree.c', 'builtin/diff.c', 'builtin/difftool.c', @@ -581,7 +584,6 @@ builtin_sources = [ 'builtin/name-rev.c', 'builtin/notes.c', 'builtin/pack-objects.c', - 'builtin/pack-redundant.c', 'builtin/pack-refs.c', 'builtin/patch-id.c', 'builtin/prune-packed.c', @@ -632,6 +634,10 @@ builtin_sources = [ 'builtin/write-tree.c', ] +if not get_option('breaking_changes') + builtin_sources += 'builtin/pack-redundant.c' +endif + builtin_sources += custom_target( output: 'config-list.h', command: [ @@ -672,12 +678,6 @@ build_options_config.set_quoted('GIT_TEST_UTF8_LOCALE', get_option('test_utf8_lo build_options_config.set_quoted('LOCALEDIR', fs.as_posix(get_option('prefix') / get_option('localedir'))) build_options_config.set('GITWEBDIR', fs.as_posix(get_option('prefix') / get_option('datadir') / 'gitweb')) -if get_option('breaking_changes') - build_options_config.set('WITH_BREAKING_CHANGES', 'YesPlease') -else - build_options_config.set('WITH_BREAKING_CHANGES', '') -endif - if get_option('sane_tool_path').length() != 0 sane_tool_path = (host_machine.system() == 'windows' ? ';' : ':').join(get_option('sane_tool_path')) build_options_config.set_quoted('BROKEN_PATH_FIX', 's|^\# @BROKEN_PATH_FIX@$|git_broken_path_fix "' + sane_tool_path + '"|') @@ -715,12 +715,14 @@ 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', '-Woverflow', '-Wpointer-arith', '-Wstrict-prototypes', + '-Wunreachable-code', '-Wunused', '-Wvla', '-Wwrite-strings', @@ -739,6 +741,13 @@ if get_option('warning_level') in ['2','3', 'everything'] and compiler.get_argum endforeach endif +if get_option('breaking_changes') + build_options_config.set('WITH_BREAKING_CHANGES', 'YesPlease') + libgit_c_args += '-DWITH_BREAKING_CHANGES' +else + build_options_config.set('WITH_BREAKING_CHANGES', '') +endif + if get_option('b_sanitize').contains('address') build_options_config.set('SANITIZE_ADDRESS', 'YesCompiledWithIt') else @@ -772,7 +781,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') +if get_option('tests') or get_option('gitweb').enabled() or 'netrc' in get_option('credential_helpers') or get_option('docs') != [] perl_required = true endif @@ -1107,7 +1116,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', @@ -1134,6 +1142,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 diff --git a/midx-write.c b/midx-write.c index 48d6558253..48a4dc5e94 100644 --- a/midx-write.c +++ b/midx-write.c @@ -647,18 +647,24 @@ static uint32_t *midx_pack_order(struct write_midx_context *ctx) return pack_order; } -static void write_midx_reverse_index(char *midx_name, unsigned char *midx_hash, - struct write_midx_context *ctx) +static void write_midx_reverse_index(struct write_midx_context *ctx, + const char *object_dir, + unsigned char *midx_hash) { struct strbuf buf = STRBUF_INIT; char *tmp_file; trace2_region_enter("midx", "write_midx_reverse_index", ctx->repo); - strbuf_addf(&buf, "%s-%s.rev", midx_name, hash_to_hex_algop(midx_hash, - ctx->repo->hash_algo)); + if (ctx->incremental) + get_split_midx_filename_ext(ctx->repo->hash_algo, &buf, + object_dir, midx_hash, + MIDX_EXT_REV); + else + 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)) @@ -708,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; @@ -768,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; @@ -829,22 +835,29 @@ static struct commit **find_commits_for_midx_bitmap(uint32_t *indexed_commits_nr return cb.commits; } -static int write_midx_bitmap(struct repository *r, const char *midx_name, +static int write_midx_bitmap(struct write_midx_context *ctx, + const char *object_dir, const unsigned char *midx_hash, struct packing_data *pdata, struct commit **commits, uint32_t commits_nr, - uint32_t *pack_order, unsigned flags) { int ret, i; uint16_t options = 0; struct bitmap_writer writer; struct pack_idx_entry **index; - char *bitmap_name = xstrfmt("%s-%s.bitmap", midx_name, - hash_to_hex_algop(midx_hash, r->hash_algo)); + struct strbuf bitmap_name = STRBUF_INIT; + + trace2_region_enter("midx", "write_midx_bitmap", ctx->repo); - trace2_region_enter("midx", "write_midx_bitmap", r); + if (ctx->incremental) + get_split_midx_filename_ext(ctx->repo->hash_algo, &bitmap_name, + object_dir, midx_hash, + MIDX_EXT_BITMAP); + else + get_midx_filename_ext(ctx->repo->hash_algo, &bitmap_name, + object_dir, midx_hash, MIDX_EXT_BITMAP); if (flags & MIDX_WRITE_BITMAP_HASH_CACHE) options |= BITMAP_OPT_HASH_CACHE; @@ -861,7 +874,8 @@ static int write_midx_bitmap(struct repository *r, const char *midx_name, for (i = 0; i < pdata->nr_objects; i++) index[i] = &pdata->objects[i].idx; - bitmap_writer_init(&writer, r, pdata); + bitmap_writer_init(&writer, ctx->repo, pdata, + ctx->incremental ? ctx->base_midx : NULL); bitmap_writer_show_progress(&writer, flags & MIDX_PROGRESS); bitmap_writer_build_type_index(&writer, index); @@ -879,7 +893,7 @@ static int write_midx_bitmap(struct repository *r, const char *midx_name, * bitmap_writer_finish(). */ for (i = 0; i < pdata->nr_objects; i++) - index[pack_order[i]] = &pdata->objects[i].idx; + index[ctx->pack_order[i]] = &pdata->objects[i].idx; bitmap_writer_select_commits(&writer, commits, commits_nr); ret = bitmap_writer_build(&writer); @@ -887,14 +901,14 @@ static int write_midx_bitmap(struct repository *r, const char *midx_name, goto cleanup; bitmap_writer_set_checksum(&writer, midx_hash); - bitmap_writer_finish(&writer, index, bitmap_name, options); + bitmap_writer_finish(&writer, index, bitmap_name.buf, options); cleanup: free(index); - free(bitmap_name); + strbuf_release(&bitmap_name); bitmap_writer_free(&writer); - trace2_region_leave("midx", "write_midx_bitmap", r); + trace2_region_leave("midx", "write_midx_bitmap", ctx->repo); return ret; } @@ -1077,8 +1091,6 @@ static int write_midx_internal(struct repository *r, const char *object_dir, ctx.repo = r; ctx.incremental = !!(flags & MIDX_WRITE_INCREMENTAL); - if (ctx.incremental && (flags & MIDX_WRITE_BITMAP)) - die(_("cannot write incremental MIDX with bitmap")); if (ctx.incremental) strbuf_addf(&midx_name, @@ -1119,6 +1131,13 @@ static int write_midx_internal(struct repository *r, const char *object_dir, if (ctx.incremental) { struct multi_pack_index *m = ctx.base_midx; while (m) { + if (flags & MIDX_WRITE_BITMAP && load_midx_revindex(m)) { + error(_("could not load reverse index for MIDX %s"), + hash_to_hex_algop(get_midx_checksum(m), + m->repo->hash_algo)); + result = 1; + goto cleanup; + } ctx.num_multi_pack_indexes_before++; m = m->base_midx; } @@ -1342,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); @@ -1387,7 +1408,7 @@ static int write_midx_internal(struct repository *r, const char *object_dir, if (flags & MIDX_WRITE_REV_INDEX && git_env_bool("GIT_TEST_MIDX_WRITE_REV", 0)) - write_midx_reverse_index(midx_name.buf, midx_hash, &ctx); + write_midx_reverse_index(&ctx, object_dir, midx_hash); if (flags & MIDX_WRITE_BITMAP) { struct packing_data pdata; @@ -1410,8 +1431,8 @@ static int write_midx_internal(struct repository *r, const char *object_dir, FREE_AND_NULL(ctx.entries); ctx.entries_nr = 0; - if (write_midx_bitmap(r, midx_name.buf, midx_hash, &pdata, - commits, commits_nr, ctx.pack_order, + if (write_midx_bitmap(&ctx, object_dir, + midx_hash, &pdata, commits, commits_nr, flags) < 0) { error(_("could not write multi-pack bitmap")); result = 1; @@ -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 726e41a047..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); } @@ -2706,7 +2430,7 @@ static int index_stream_convert_blob(struct index_state *istate, struct strbuf sbuf = STRBUF_INIT; assert(path); - assert(would_convert_to_git_filter_fd(istate, path)); + ASSERT(would_convert_to_git_filter_fd(istate, path)); convert_to_git_filter_fd(istate, path, fd, &sbuf, get_conv_flags(flags)); @@ -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); diff --git a/object-name.c b/object-name.c index 76749fbfe6..91f731373a 100644 --- a/object-name.c +++ b/object-name.c @@ -961,7 +961,9 @@ static int get_oid_basic(struct repository *r, const char *str, int len, int fatal = !(flags & GET_OID_QUIETLY); if (len == r->hash_algo->hexsz && !get_oid_hex(str, oid)) { - if (repo_settings_get_warn_ambiguous_refs(r) && warn_on_object_refname_ambiguity) { + if (!(flags & GET_OID_SKIP_AMBIGUITY_CHECK) && + repo_settings_get_warn_ambiguous_refs(r) && + warn_on_object_refname_ambiguity) { refs_found = repo_dwim_ref(r, str, len, &tmp_oid, &real_ref, 0); if (refs_found > 0) { warning(warn_msg, len, str); @@ -1794,18 +1796,20 @@ void object_context_release(struct object_context *ctx) strbuf_release(&ctx->symlink_path); } -/* - * This is like "get_oid_basic()", except it allows "object ID expressions", - * notably "xyz^" for "parent of xyz" - */ -int repo_get_oid(struct repository *r, const char *name, struct object_id *oid) +int repo_get_oid_with_flags(struct repository *r, const char *name, + struct object_id *oid, unsigned flags) { struct object_context unused; - int ret = get_oid_with_context(r, name, 0, oid, &unused); + int ret = get_oid_with_context(r, name, flags, oid, &unused); object_context_release(&unused); return ret; } +int repo_get_oid(struct repository *r, const char *name, struct object_id *oid) +{ + return repo_get_oid_with_flags(r, name, oid, 0); +} + /* * This returns a non-zero value if the string (built using printf * format and the given arguments) is not a valid object. diff --git a/object-name.h b/object-name.h index 8dba4a47a4..cda4934cd5 100644 --- a/object-name.h +++ b/object-name.h @@ -51,6 +51,12 @@ void strbuf_repo_add_unique_abbrev(struct strbuf *sb, struct repository *repo, void strbuf_add_unique_abbrev(struct strbuf *sb, const struct object_id *oid, int abbrev_len); +/* + * This is like "get_oid_basic()", except it allows "object ID expressions", + * notably "xyz^" for "parent of xyz". Accepts GET_OID_* flags. + */ +int repo_get_oid_with_flags(struct repository *r, const char *str, + struct object_id *oid, unsigned flags); int repo_get_oid(struct repository *r, const char *str, struct object_id *oid); __attribute__((format (printf, 2, 3))) int get_oidf(struct object_id *oid, const char *fmt, ...); @@ -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; @@ -524,12 +525,12 @@ void object_array_remove_duplicates(struct object_array *array) } } -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 @@ -336,7 +338,7 @@ void object_array_remove_duplicates(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 34e86d4994..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" @@ -26,6 +25,8 @@ #include "alloc.h" #include "refs.h" #include "strmap.h" +#include "midx.h" +#include "pack-revindex.h" struct bitmapped_commit { struct commit *commit; @@ -43,14 +44,17 @@ static inline int bitmap_writer_nr_selected_commits(struct bitmap_writer *writer } void bitmap_writer_init(struct bitmap_writer *writer, struct repository *r, - struct packing_data *pdata) + struct packing_data *pdata, + struct multi_pack_index *midx) { 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; + writer->midx = midx; string_list_init_dup(&writer->pseudo_merge_groups); @@ -113,6 +117,11 @@ void bitmap_writer_build_type_index(struct bitmap_writer *writer, struct pack_idx_entry **index) { uint32_t i; + uint32_t base_objects = 0; + + if (writer->midx) + base_objects = writer->midx->num_objects + + writer->midx->num_objects_in_base; writer->commits = ewah_new(); writer->trees = ewah_new(); @@ -142,19 +151,19 @@ void bitmap_writer_build_type_index(struct bitmap_writer *writer, switch (real_type) { case OBJ_COMMIT: - ewah_set(writer->commits, i); + ewah_set(writer->commits, i + base_objects); break; case OBJ_TREE: - ewah_set(writer->trees, i); + ewah_set(writer->trees, i + base_objects); break; case OBJ_BLOB: - ewah_set(writer->blobs, i); + ewah_set(writer->blobs, i + base_objects); break; case OBJ_TAG: - ewah_set(writer->tags, i); + ewah_set(writer->tags, i + base_objects); break; default: @@ -207,19 +216,37 @@ void bitmap_writer_push_commit(struct bitmap_writer *writer, static uint32_t find_object_pos(struct bitmap_writer *writer, const struct object_id *oid, int *found) { - struct object_entry *entry = packlist_find(writer->to_pack, oid); + struct object_entry *entry; + + entry = packlist_find(writer->to_pack, oid); + if (entry) { + uint32_t base_objects = 0; + if (writer->midx) + base_objects = writer->midx->num_objects + + writer->midx->num_objects_in_base; - if (!entry) { if (found) - *found = 0; - warning("Failed to write bitmap index. Packfile doesn't have full closure " - "(object %s is missing)", oid_to_hex(oid)); - return 0; + *found = 1; + return oe_in_pack_pos(writer->to_pack, entry) + base_objects; + } else if (writer->midx) { + uint32_t at, pos; + + if (!bsearch_midx(oid, writer->midx, &at)) + goto missing; + if (midx_to_pack_pos(writer->midx, at, &pos) < 0) + goto missing; + + if (found) + *found = 1; + return pos; } +missing: if (found) - *found = 1; - return oe_in_pack_pos(writer->to_pack, entry); + *found = 0; + warning("Failed to write bitmap index. Packfile doesn't have full closure " + "(object %s is missing)", oid_to_hex(oid)); + return 0; } static void compute_xor_offsets(struct bitmap_writer *writer) @@ -415,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); @@ -460,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: @@ -536,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) { @@ -586,15 +613,15 @@ int bitmap_writer_build(struct bitmap_writer *writer) struct prio_queue queue = { compare_commits_by_gen_then_commit_date }; struct prio_queue tree_queue = { NULL }; struct bitmap_index *old_bitmap; - uint32_t *mapping; + uint32_t *mapping = NULL; 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) @@ -645,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); @@ -711,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 (;;) { @@ -960,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; @@ -987,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); @@ -1008,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, @@ -1021,7 +1048,7 @@ void bitmap_writer_finish(struct bitmap_writer *writer, struct strbuf tmp_file = STRBUF_INIT; struct hashfile *f; off_t *offsets = NULL; - uint32_t i; + uint32_t i, base_objects; struct bitmap_disk_header header; @@ -1030,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); @@ -1047,6 +1074,12 @@ void bitmap_writer_finish(struct bitmap_writer *writer, if (options & BITMAP_OPT_LOOKUP_TABLE) CALLOC_ARRAY(offsets, writer->to_pack->nr_objects); + if (writer->midx) + base_objects = writer->midx->num_objects + + writer->midx->num_objects_in_base; + else + base_objects = 0; + for (i = 0; i < bitmap_writer_nr_selected_commits(writer); i++) { struct bitmapped_commit *stored = &writer->selected[i]; int commit_pos = oid_pos(&stored->commit->object.oid, index, @@ -1055,7 +1088,7 @@ void bitmap_writer_finish(struct bitmap_writer *writer, if (commit_pos < 0) BUG(_("trying to write commit not in index")); - stored->commit_pos = commit_pos; + stored->commit_pos = commit_pos + base_objects; } write_selected_commits_v1(writer, f, offsets); @@ -1072,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 6406953d32..aa67cb40ab 100644 --- a/pack-bitmap.c +++ b/pack-bitmap.c @@ -54,6 +54,16 @@ struct bitmap_index { struct packed_git *pack; struct multi_pack_index *midx; + /* + * If using a multi-pack index chain, 'base' points to the + * bitmap index corresponding to this bitmap's midx->base_midx. + * + * base_nr indicates how many layers precede this one, and is + * zero when base is NULL. + */ + struct bitmap_index *base; + uint32_t base_nr; + /* mmapped buffer of the whole bitmap index */ unsigned char *map; size_t map_size; /* size of the mmaped buffer */ @@ -71,6 +81,23 @@ struct bitmap_index { struct ewah_bitmap *blobs; struct ewah_bitmap *tags; + /* + * Type index arrays when this bitmap is associated with an + * incremental multi-pack index chain. + * + * If n is the number of unique layers in the MIDX chain, then + * commits_all[n-1] is this structs 'commits' field, + * commits_all[n-2] is the commits field of this bitmap's + * 'base', and so on. + * + * When associated either with a non-incremental MIDX or a + * single packfile, these arrays each contain a single element. + */ + struct ewah_bitmap **commits_all; + struct ewah_bitmap **trees_all; + struct ewah_bitmap **blobs_all; + struct ewah_bitmap **tags_all; + /* Map from object ID -> `stored_bitmap` for all the bitmapped commits */ kh_oid_map_t *bitmaps; @@ -170,6 +197,15 @@ static struct ewah_bitmap *read_bitmap_1(struct bitmap_index *index) return read_bitmap(index->map, index->map_size, &index->map_pos); } +static uint32_t bitmap_num_objects_total(struct bitmap_index *index) +{ + if (index->midx) { + struct multi_pack_index *m = index->midx; + return m->num_objects + m->num_objects_in_base; + } + return index->pack->num_objects; +} + static uint32_t bitmap_num_objects(struct bitmap_index *index) { if (index->midx) @@ -377,8 +413,15 @@ static int load_bitmap_entries_v1(struct bitmap_index *index) char *midx_bitmap_filename(struct multi_pack_index *midx) { struct strbuf buf = STRBUF_INIT; - get_midx_filename_ext(midx->repo->hash_algo, &buf, midx->object_dir, - get_midx_checksum(midx), MIDX_EXT_BITMAP); + if (midx->has_chain) + get_split_midx_filename_ext(midx->repo->hash_algo, &buf, + midx->object_dir, + get_midx_checksum(midx), + MIDX_EXT_BITMAP); + else + get_midx_filename_ext(midx->repo->hash_algo, &buf, + midx->object_dir, get_midx_checksum(midx), + MIDX_EXT_BITMAP); return strbuf_detach(&buf, NULL); } @@ -445,16 +488,21 @@ static int open_midx_bitmap_1(struct bitmap_index *bitmap_git, goto cleanup; } - for (i = 0; i < bitmap_git->midx->num_packs; i++) { - if (prepare_midx_pack(bitmap_repo(bitmap_git), - bitmap_git->midx, - i)) { + for (i = 0; i < bitmap_git->midx->num_packs + bitmap_git->midx->num_packs_in_base; i++) { + if (prepare_midx_pack(bitmap_repo(bitmap_git), bitmap_git->midx, i)) { warning(_("could not open pack %s"), bitmap_git->midx->pack_names[i]); goto cleanup; } } + if (midx->base_midx) { + bitmap_git->base = prepare_midx_bitmap_git(midx->base_midx); + bitmap_git->base_nr = bitmap_git->base->base_nr + 1; + } else { + bitmap_git->base_nr = 0; + } + return 0; cleanup: @@ -506,6 +554,7 @@ static int open_pack_bitmap_1(struct bitmap_index *bitmap_git, struct packed_git bitmap_git->map_size = xsize_t(st.st_size); bitmap_git->map = xmmap(NULL, bitmap_git->map_size, PROT_READ, MAP_PRIVATE, fd, 0); bitmap_git->map_pos = 0; + bitmap_git->base_nr = 0; close(fd); if (load_bitmap_header(bitmap_git) < 0) { @@ -525,8 +574,7 @@ static int open_pack_bitmap_1(struct bitmap_index *bitmap_git, struct packed_git static int load_reverse_index(struct repository *r, struct bitmap_index *bitmap_git) { if (bitmap_is_midx(bitmap_git)) { - uint32_t i; - int ret; + struct multi_pack_index *m; /* * The multi-pack-index's .rev file is already loaded via @@ -535,17 +583,47 @@ static int load_reverse_index(struct repository *r, struct bitmap_index *bitmap_ * But we still need to open the individual pack .rev files, * since we will need to make use of them in pack-objects. */ - for (i = 0; i < bitmap_git->midx->num_packs; i++) { - ret = load_pack_revindex(r, bitmap_git->midx->packs[i]); - if (ret) - return ret; + for (m = bitmap_git->midx; m; m = m->base_midx) { + uint32_t i; + int ret; + + for (i = 0; i < m->num_packs; i++) { + ret = load_pack_revindex(r, m->packs[i]); + if (ret) + return ret; + } } return 0; } return load_pack_revindex(r, bitmap_git->pack); } -static int load_bitmap(struct repository *r, struct bitmap_index *bitmap_git) +static void load_all_type_bitmaps(struct bitmap_index *bitmap_git) +{ + struct bitmap_index *curr = bitmap_git; + size_t i = bitmap_git->base_nr; + + ALLOC_ARRAY(bitmap_git->commits_all, bitmap_git->base_nr + 1); + ALLOC_ARRAY(bitmap_git->trees_all, bitmap_git->base_nr + 1); + ALLOC_ARRAY(bitmap_git->blobs_all, bitmap_git->base_nr + 1); + ALLOC_ARRAY(bitmap_git->tags_all, bitmap_git->base_nr + 1); + + while (curr) { + bitmap_git->commits_all[i] = curr->commits; + bitmap_git->trees_all[i] = curr->trees; + bitmap_git->blobs_all[i] = curr->blobs; + bitmap_git->tags_all[i] = curr->tags; + + curr = curr->base; + if (curr && !i) + BUG("unexpected number of bitmap layers, expected %"PRIu32, + bitmap_git->base_nr + 1); + i -= 1; + } +} + +static int load_bitmap(struct repository *r, struct bitmap_index *bitmap_git, + int recursing) { assert(bitmap_git->map); @@ -564,6 +642,16 @@ static int load_bitmap(struct repository *r, struct bitmap_index *bitmap_git) if (!bitmap_git->table_lookup && load_bitmap_entries_v1(bitmap_git) < 0) goto failed; + if (bitmap_git->base) { + if (!bitmap_is_midx(bitmap_git)) + BUG("non-MIDX bitmap has non-NULL base bitmap index"); + if (load_bitmap(r, bitmap_git->base, 1) < 0) + goto failed; + } + + if (!recursing) + load_all_type_bitmaps(bitmap_git); + return 0; failed: @@ -639,7 +727,7 @@ struct bitmap_index *prepare_bitmap_git(struct repository *r) { struct bitmap_index *bitmap_git = xcalloc(1, sizeof(*bitmap_git)); - if (!open_bitmap(r, bitmap_git) && !load_bitmap(r, bitmap_git)) + if (!open_bitmap(r, bitmap_git) && !load_bitmap(r, bitmap_git, 0)) return bitmap_git; free_bitmap_index(bitmap_git); @@ -648,10 +736,9 @@ struct bitmap_index *prepare_bitmap_git(struct repository *r) struct bitmap_index *prepare_midx_bitmap_git(struct multi_pack_index *midx) { - struct repository *r = midx->repo; struct bitmap_index *bitmap_git = xcalloc(1, sizeof(*bitmap_git)); - if (!open_midx_bitmap_1(bitmap_git, midx) && !load_bitmap(r, bitmap_git)) + if (!open_midx_bitmap_1(bitmap_git, midx)) return bitmap_git; free_bitmap_index(bitmap_git); @@ -896,26 +983,42 @@ corrupt: return NULL; } -struct ewah_bitmap *bitmap_for_commit(struct bitmap_index *bitmap_git, - struct commit *commit) +static struct ewah_bitmap *find_bitmap_for_commit(struct bitmap_index *bitmap_git, + struct commit *commit, + struct bitmap_index **found) { - khiter_t hash_pos = kh_get_oid_map(bitmap_git->bitmaps, - commit->object.oid); + khiter_t hash_pos; + if (!bitmap_git) + return NULL; + + hash_pos = kh_get_oid_map(bitmap_git->bitmaps, commit->object.oid); if (hash_pos >= kh_end(bitmap_git->bitmaps)) { struct stored_bitmap *bitmap = NULL; if (!bitmap_git->table_lookup) - return NULL; + return find_bitmap_for_commit(bitmap_git->base, commit, + found); /* this is a fairly hot codepath - no trace2_region please */ /* NEEDSWORK: cache misses aren't recorded */ bitmap = lazy_bitmap_for_commit(bitmap_git, commit); if (!bitmap) - return NULL; + return find_bitmap_for_commit(bitmap_git->base, commit, + found); + if (found) + *found = bitmap_git; return lookup_stored_bitmap(bitmap); } + if (found) + *found = bitmap_git; return lookup_stored_bitmap(kh_value(bitmap_git->bitmaps, hash_pos)); } +struct ewah_bitmap *bitmap_for_commit(struct bitmap_index *bitmap_git, + struct commit *commit) +{ + return find_bitmap_for_commit(bitmap_git, commit, NULL); +} + static inline int bitmap_position_extended(struct bitmap_index *bitmap_git, const struct object_id *oid) { @@ -924,7 +1027,7 @@ static inline int bitmap_position_extended(struct bitmap_index *bitmap_git, if (pos < kh_end(positions)) { int bitmap_pos = kh_value(positions, pos); - return bitmap_pos + bitmap_num_objects(bitmap_git); + return bitmap_pos + bitmap_num_objects_total(bitmap_git); } return -1; @@ -992,7 +1095,7 @@ static int ext_index_add_object(struct bitmap_index *bitmap_git, bitmap_pos = kh_value(eindex->positions, hash_pos); } - return bitmap_pos + bitmap_num_objects(bitmap_git); + return bitmap_pos + bitmap_num_objects_total(bitmap_git); } struct bitmap_show_data { @@ -1024,10 +1127,15 @@ static unsigned apply_pseudo_merges_for_commit_1(struct bitmap_index *bitmap_git struct commit *commit, uint32_t commit_pos) { - int ret; + struct bitmap_index *curr = bitmap_git; + int ret = 0; - ret = apply_pseudo_merges_for_commit(&bitmap_git->pseudo_merges, - result, commit, commit_pos); + while (curr) { + ret += apply_pseudo_merges_for_commit(&curr->pseudo_merges, + result, commit, + commit_pos); + curr = curr->base; + } if (ret) pseudo_merges_satisfied_nr += ret; @@ -1301,7 +1409,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. @@ -1342,11 +1450,17 @@ struct ewah_bitmap *pseudo_merge_bitmap_for_commit(struct bitmap_index *bitmap_g if (pos < 0 || pos >= bitmap_num_objects(bitmap_git)) goto done; + /* + * Use bitmap-relative positions instead of offsetting + * by bitmap_git->num_objects_in_base because we use + * this to find a match in pseudo_merge_for_parents(), + * and pseudo-merge groups cannot span multiple bitmap + * layers. + */ bitmap_set(parents, pos); } - match = pseudo_merge_for_parents(&bitmap_git->pseudo_merges, - parents); + match = pseudo_merge_for_parents(&bitmap_git->pseudo_merges, parents); done: bitmap_free(parents); @@ -1500,7 +1614,9 @@ static void show_extended_objects(struct bitmap_index *bitmap_git, for (i = 0; i < eindex->count; ++i) { struct object *obj; - if (!bitmap_get(objects, st_add(bitmap_num_objects(bitmap_git), i))) + if (!bitmap_get(objects, + st_add(bitmap_num_objects_total(bitmap_git), + i))) continue; obj = eindex->objects[i]; @@ -1513,25 +1629,29 @@ static void show_extended_objects(struct bitmap_index *bitmap_git, } } -static void init_type_iterator(struct ewah_iterator *it, +static void init_type_iterator(struct ewah_or_iterator *it, struct bitmap_index *bitmap_git, enum object_type type) { switch (type) { case OBJ_COMMIT: - ewah_iterator_init(it, bitmap_git->commits); + ewah_or_iterator_init(it, bitmap_git->commits_all, + bitmap_git->base_nr + 1); break; case OBJ_TREE: - ewah_iterator_init(it, bitmap_git->trees); + ewah_or_iterator_init(it, bitmap_git->trees_all, + bitmap_git->base_nr + 1); break; case OBJ_BLOB: - ewah_iterator_init(it, bitmap_git->blobs); + ewah_or_iterator_init(it, bitmap_git->blobs_all, + bitmap_git->base_nr + 1); break; case OBJ_TAG: - ewah_iterator_init(it, bitmap_git->tags); + ewah_or_iterator_init(it, bitmap_git->tags_all, + bitmap_git->base_nr + 1); break; default: @@ -1548,7 +1668,7 @@ static void show_objects_for_type( size_t i = 0; uint32_t offset; - struct ewah_iterator it; + struct ewah_or_iterator it; eword_t filter; struct bitmap *objects = bitmap_git->result; @@ -1556,7 +1676,7 @@ static void show_objects_for_type( init_type_iterator(&it, bitmap_git, object_type); for (i = 0; i < objects->word_alloc && - ewah_iterator_next(&filter, &it); i++) { + ewah_or_iterator_next(&filter, &it); i++) { eword_t word = objects->words[i] & filter; size_t pos = (i * BITS_IN_EWORD); @@ -1583,7 +1703,7 @@ static void show_objects_for_type( nth_midxed_object_oid(&oid, m, index_pos); pack_id = nth_midxed_pack_int_id(m, index_pos); - pack = bitmap_git->midx->packs[pack_id]; + pack = nth_midxed_pack(bitmap_git->midx, pack_id); } else { index_pos = pack_pos_to_index(bitmap_git->pack, pos + offset); ofs = pack_pos_to_offset(bitmap_git->pack, pos + offset); @@ -1598,6 +1718,8 @@ static void show_objects_for_type( show_reach(&oid, object_type, 0, hash, pack, ofs); } } + + ewah_or_iterator_release(&it); } static int in_bitmapped_pack(struct bitmap_index *bitmap_git, @@ -1649,7 +1771,7 @@ static void filter_bitmap_exclude_type(struct bitmap_index *bitmap_git, { struct eindex *eindex = &bitmap_git->ext_index; struct bitmap *tips; - struct ewah_iterator it; + struct ewah_or_iterator it; eword_t mask; uint32_t i; @@ -1666,7 +1788,7 @@ static void filter_bitmap_exclude_type(struct bitmap_index *bitmap_git, * packfile. */ for (i = 0, init_type_iterator(&it, bitmap_git, type); - i < to_filter->word_alloc && ewah_iterator_next(&mask, &it); + i < to_filter->word_alloc && ewah_or_iterator_next(&mask, &it); i++) { if (i < tips->word_alloc) mask &= ~tips->words[i]; @@ -1679,13 +1801,14 @@ static void filter_bitmap_exclude_type(struct bitmap_index *bitmap_git, * them individually. */ for (i = 0; i < eindex->count; i++) { - size_t pos = st_add(i, bitmap_num_objects(bitmap_git)); + size_t pos = st_add(i, bitmap_num_objects_total(bitmap_git)); if (eindex->objects[i]->type == type && bitmap_get(to_filter, pos) && !bitmap_get(tips, pos)) bitmap_unset(to_filter, pos); } + ewah_or_iterator_release(&it); bitmap_free(tips); } @@ -1705,7 +1828,7 @@ static unsigned long get_size_by_pos(struct bitmap_index *bitmap_git, oi.sizep = &size; - if (pos < bitmap_num_objects(bitmap_git)) { + if (pos < bitmap_num_objects_total(bitmap_git)) { struct packed_git *pack; off_t ofs; @@ -1713,7 +1836,7 @@ static unsigned long get_size_by_pos(struct bitmap_index *bitmap_git, uint32_t midx_pos = pack_pos_to_midx(bitmap_git->midx, pos); uint32_t pack_id = nth_midxed_pack_int_id(bitmap_git->midx, midx_pos); - pack = bitmap_git->midx->packs[pack_id]; + pack = nth_midxed_pack(bitmap_git->midx, pack_id); ofs = nth_midxed_offset(bitmap_git->midx, midx_pos); } else { pack = bitmap_git->pack; @@ -1728,8 +1851,9 @@ static unsigned long get_size_by_pos(struct bitmap_index *bitmap_git, die(_("unable to get size of %s"), oid_to_hex(&oid)); } } else { + size_t eindex_pos = pos - bitmap_num_objects_total(bitmap_git); struct eindex *eindex = &bitmap_git->ext_index; - struct object *obj = eindex->objects[pos - bitmap_num_objects(bitmap_git)]; + struct object *obj = eindex->objects[eindex_pos]; if (oid_object_info_extended(bitmap_repo(bitmap_git), &obj->oid, &oi, 0) < 0) die(_("unable to get size of %s"), oid_to_hex(&obj->oid)); @@ -1745,14 +1869,14 @@ static void filter_bitmap_blob_limit(struct bitmap_index *bitmap_git, { struct eindex *eindex = &bitmap_git->ext_index; struct bitmap *tips; - struct ewah_iterator it; + struct ewah_or_iterator it; eword_t mask; uint32_t i; tips = find_tip_objects(bitmap_git, tip_objects, OBJ_BLOB); for (i = 0, init_type_iterator(&it, bitmap_git, OBJ_BLOB); - i < to_filter->word_alloc && ewah_iterator_next(&mask, &it); + i < to_filter->word_alloc && ewah_or_iterator_next(&mask, &it); i++) { eword_t word = to_filter->words[i] & mask; unsigned offset; @@ -1780,6 +1904,7 @@ static void filter_bitmap_blob_limit(struct bitmap_index *bitmap_git, bitmap_unset(to_filter, pos); } + ewah_or_iterator_release(&it); bitmap_free(tips); } @@ -1882,7 +2007,7 @@ static void filter_packed_objects_from_bitmap(struct bitmap_index *bitmap_git, uint32_t objects_nr; size_t i, pos; - objects_nr = bitmap_num_objects(bitmap_git); + objects_nr = bitmap_num_objects_total(bitmap_git); pos = objects_nr / BITS_IN_EWORD; if (pos > result->word_alloc) @@ -1935,7 +2060,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; @@ -1945,7 +2070,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); } @@ -1980,7 +2105,7 @@ struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs, * from disk. this is the point of no return; after this the rev_list * becomes invalidated and we must perform the revwalk through bitmaps */ - if (load_bitmap(revs->repo, bitmap_git) < 0) + if (load_bitmap(revs->repo, bitmap_git, 0) < 0) goto cleanup; if (!use_boundary_traversal) @@ -2281,7 +2406,8 @@ void reuse_partial_packfile_from_bitmap(struct bitmap_index *bitmap_git, multi_pack_reuse = 0; if (multi_pack_reuse) { - for (i = 0; i < bitmap_git->midx->num_packs; i++) { + struct multi_pack_index *m = bitmap_git->midx; + for (i = 0; i < m->num_packs + m->num_packs_in_base; i++) { struct bitmapped_pack pack; if (nth_bitmapped_pack(r, bitmap_git->midx, &pack, i) < 0) { warning(_("unable to load pack: '%s', disabling pack-reuse"), @@ -2307,14 +2433,18 @@ void reuse_partial_packfile_from_bitmap(struct bitmap_index *bitmap_git, uint32_t pack_int_id; if (bitmap_is_midx(bitmap_git)) { + struct multi_pack_index *m = bitmap_git->midx; uint32_t preferred_pack_pos; - if (midx_preferred_pack(bitmap_git->midx, &preferred_pack_pos) < 0) { + while (m->base_midx) + m = m->base_midx; + + if (midx_preferred_pack(m, &preferred_pack_pos) < 0) { warning(_("unable to compute preferred pack, disabling pack-reuse")); return; } - pack = bitmap_git->midx->packs[preferred_pack_pos]; + pack = nth_midxed_pack(m, preferred_pack_pos); pack_int_id = preferred_pack_pos; } else { pack = bitmap_git->pack; @@ -2406,12 +2536,12 @@ static uint32_t count_object_type(struct bitmap_index *bitmap_git, struct eindex *eindex = &bitmap_git->ext_index; uint32_t i = 0, count = 0; - struct ewah_iterator it; + struct ewah_or_iterator it; eword_t filter; init_type_iterator(&it, bitmap_git, type); - while (i < objects->word_alloc && ewah_iterator_next(&filter, &it)) { + while (i < objects->word_alloc && ewah_or_iterator_next(&filter, &it)) { eword_t word = objects->words[i++] & filter; count += ewah_bit_popcount64(word); } @@ -2419,10 +2549,12 @@ static uint32_t count_object_type(struct bitmap_index *bitmap_git, for (i = 0; i < eindex->count; ++i) { if (eindex->objects[i]->type == type && bitmap_get(objects, - st_add(bitmap_num_objects(bitmap_git), i))) + st_add(bitmap_num_objects_total(bitmap_git), i))) count++; } + ewah_or_iterator_release(&it); + return count; } @@ -2454,6 +2586,8 @@ struct bitmap_test_data { struct bitmap *tags; struct progress *prg; size_t seen; + + struct bitmap_test_data *base_tdata; }; static void test_bitmap_type(struct bitmap_test_data *tdata, @@ -2462,6 +2596,11 @@ static void test_bitmap_type(struct bitmap_test_data *tdata, enum object_type bitmap_type = OBJ_NONE; int bitmaps_nr = 0; + if (bitmap_is_midx(tdata->bitmap_git)) { + while (pos < tdata->bitmap_git->midx->num_objects_in_base) + tdata = tdata->base_tdata; + } + if (bitmap_get(tdata->commits, pos)) { bitmap_type = OBJ_COMMIT; bitmaps_nr++; @@ -2525,13 +2664,57 @@ static void test_show_commit(struct commit *commit, void *data) display_progress(tdata->prg, ++tdata->seen); } +static uint32_t bitmap_total_entry_count(struct bitmap_index *bitmap_git) +{ + uint32_t total = 0; + do { + total = st_add(total, bitmap_git->entry_count); + bitmap_git = bitmap_git->base; + } while (bitmap_git); + + return total; +} + +static void bitmap_test_data_prepare(struct bitmap_test_data *tdata, + struct bitmap_index *bitmap_git) +{ + memset(tdata, 0, sizeof(struct bitmap_test_data)); + + tdata->bitmap_git = bitmap_git; + tdata->base = bitmap_new(); + tdata->commits = ewah_to_bitmap(bitmap_git->commits); + tdata->trees = ewah_to_bitmap(bitmap_git->trees); + tdata->blobs = ewah_to_bitmap(bitmap_git->blobs); + tdata->tags = ewah_to_bitmap(bitmap_git->tags); + + if (bitmap_git->base) { + tdata->base_tdata = xmalloc(sizeof(struct bitmap_test_data)); + bitmap_test_data_prepare(tdata->base_tdata, bitmap_git->base); + } +} + +static void bitmap_test_data_release(struct bitmap_test_data *tdata) +{ + if (!tdata) + return; + + bitmap_test_data_release(tdata->base_tdata); + free(tdata->base_tdata); + + bitmap_free(tdata->base); + bitmap_free(tdata->commits); + bitmap_free(tdata->trees); + bitmap_free(tdata->blobs); + bitmap_free(tdata->tags); +} + void test_bitmap_walk(struct rev_info *revs) { struct object *root; struct bitmap *result = NULL; size_t result_popcnt; struct bitmap_test_data tdata; - struct bitmap_index *bitmap_git; + struct bitmap_index *bitmap_git, *found; struct ewah_bitmap *bm; if (!(bitmap_git = prepare_bitmap_git(revs->repo))) @@ -2540,17 +2723,28 @@ void test_bitmap_walk(struct rev_info *revs) if (revs->pending.nr != 1) die(_("you must specify exactly one commit to test")); - fprintf_ln(stderr, "Bitmap v%d test (%d entries%s)", + fprintf_ln(stderr, "Bitmap v%d test (%d entries%s, %d total)", bitmap_git->version, bitmap_git->entry_count, - bitmap_git->table_lookup ? "" : " loaded"); + bitmap_git->table_lookup ? "" : " loaded", + bitmap_total_entry_count(bitmap_git)); root = revs->pending.objects[0].item; - bm = bitmap_for_commit(bitmap_git, (struct commit *)root); + bm = find_bitmap_for_commit(bitmap_git, (struct commit *)root, &found); if (bm) { fprintf_ln(stderr, "Found bitmap for '%s'. %d bits / %08x checksum", - oid_to_hex(&root->oid), (int)bm->bit_size, ewah_checksum(bm)); + oid_to_hex(&root->oid), + (int)bm->bit_size, ewah_checksum(bm)); + + if (bitmap_is_midx(found)) + fprintf_ln(stderr, "Located via MIDX '%s'.", + hash_to_hex_algop(get_midx_checksum(found->midx), + revs->repo->hash_algo)); + else + fprintf_ln(stderr, "Located via pack '%s'.", + hash_to_hex_algop(found->pack->hash, + revs->repo->hash_algo)); result = ewah_to_bitmap(bm); } @@ -2567,16 +2761,10 @@ void test_bitmap_walk(struct rev_info *revs) if (prepare_revision_walk(revs)) die(_("revision walk setup failed")); - tdata.bitmap_git = bitmap_git; - tdata.base = bitmap_new(); - tdata.commits = ewah_to_bitmap(bitmap_git->commits); - tdata.trees = ewah_to_bitmap(bitmap_git->trees); - tdata.blobs = ewah_to_bitmap(bitmap_git->blobs); - tdata.tags = ewah_to_bitmap(bitmap_git->tags); + bitmap_test_data_prepare(&tdata, bitmap_git); tdata.prg = start_progress(revs->repo, "Verifying bitmap entries", result_popcnt); - tdata.seen = 0; traverse_commit_list(revs, &test_show_commit, &test_show_object, &tdata); @@ -2588,11 +2776,7 @@ void test_bitmap_walk(struct rev_info *revs) die(_("mismatch in bitmap results")); bitmap_free(result); - bitmap_free(tdata.base); - bitmap_free(tdata.commits); - bitmap_free(tdata.trees); - bitmap_free(tdata.blobs); - bitmap_free(tdata.tags); + bitmap_test_data_release(&tdata); free_bitmap_index(bitmap_git); } @@ -2820,7 +3004,7 @@ uint32_t *create_bitmap_mapping(struct bitmap_index *bitmap_git, BUG("rebuild_existing_bitmaps: missing required rev-cache " "extension"); - num_objects = bitmap_num_objects(bitmap_git); + num_objects = bitmap_num_objects_total(bitmap_git); CALLOC_ARRAY(reposition, num_objects); for (i = 0; i < num_objects; ++i) { @@ -2856,6 +3040,10 @@ void free_bitmap_index(struct bitmap_index *b) ewah_pool_free(b->trees); ewah_pool_free(b->blobs); ewah_pool_free(b->tags); + free(b->commits_all); + free(b->trees_all); + free(b->blobs_all); + free(b->tags_all); if (b->bitmaps) { struct stored_bitmap *sb; kh_foreach_value(b->bitmaps, sb, { @@ -2883,6 +3071,7 @@ void free_bitmap_index(struct bitmap_index *b) close_midx_revindex(b->midx); } free_pseudo_merge_map(&b->pseudo_merges); + free_bitmap_index(b->base); free(b); } @@ -2898,13 +3087,13 @@ static off_t get_disk_usage_for_type(struct bitmap_index *bitmap_git, { struct bitmap *result = bitmap_git->result; off_t total = 0; - struct ewah_iterator it; + struct ewah_or_iterator it; eword_t filter; size_t i; init_type_iterator(&it, bitmap_git, object_type); for (i = 0; i < result->word_alloc && - ewah_iterator_next(&filter, &it); i++) { + ewah_or_iterator_next(&filter, &it); i++) { eword_t word = result->words[i] & filter; size_t base = (i * BITS_IN_EWORD); unsigned offset; @@ -2924,7 +3113,7 @@ static off_t get_disk_usage_for_type(struct bitmap_index *bitmap_git, off_t offset = nth_midxed_offset(bitmap_git->midx, midx_pos); uint32_t pack_id = nth_midxed_pack_int_id(bitmap_git->midx, midx_pos); - struct packed_git *pack = bitmap_git->midx->packs[pack_id]; + struct packed_git *pack = nth_midxed_pack(bitmap_git->midx, pack_id); if (offset_to_pack_pos(pack, offset, &pack_pos) < 0) { struct object_id oid; @@ -2945,6 +3134,8 @@ static off_t get_disk_usage_for_type(struct bitmap_index *bitmap_git, } } + ewah_or_iterator_release(&it); + return total; } @@ -2963,7 +3154,8 @@ static off_t get_disk_usage_for_extended(struct bitmap_index *bitmap_git) struct object *obj = eindex->objects[i]; if (!bitmap_get(result, - st_add(bitmap_num_objects(bitmap_git), i))) + st_add(bitmap_num_objects_total(bitmap_git), + i))) continue; if (oid_object_info_extended(bitmap_repo(bitmap_git), &obj->oid, @@ -3024,7 +3216,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; @@ -3040,7 +3233,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); @@ -3055,14 +3248,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 d7f4b8b8e9..0e9d25e6f2 100644 --- a/pack-bitmap.h +++ b/pack-bitmap.h @@ -104,6 +104,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; @@ -111,6 +112,7 @@ struct bitmap_writer { kh_oid_map_t *bitmaps; struct packing_data *to_pack; + struct multi_pack_index *midx; /* if appending to a MIDX chain */ struct bitmapped_commit *selected; unsigned int selected_nr, selected_alloc; @@ -125,7 +127,8 @@ struct bitmap_writer { }; void bitmap_writer_init(struct bitmap_writer *writer, struct repository *r, - struct packing_data *pdata); + struct packing_data *pdata, + struct multi_pack_index *midx); void bitmap_writer_show_progress(struct bitmap_writer *writer, int show); void bitmap_writer_set_checksum(struct bitmap_writer *writer, const unsigned char *sha1); 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 d3832478d9..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,19 +380,26 @@ 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"); - get_midx_filename_ext(m->repo->hash_algo, &revindex_name, m->object_dir, - get_midx_checksum(m), MIDX_EXT_REV); + if (m->has_chain) + get_split_midx_filename_ext(m->repo->hash_algo, &revindex_name, + m->object_dir, get_midx_checksum(m), + MIDX_EXT_REV); + else + get_midx_filename_ext(m->repo->hash_algo, &revindex_name, + 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); @@ -418,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; @@ -464,18 +477,22 @@ 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)); } uint32_t pack_pos_to_midx(struct multi_pack_index *m, uint32_t pos) { + while (m && pos < m->num_objects_in_base) + m = m->base_midx; + if (!m) + BUG("NULL multi-pack-index for object position: %"PRIu32, pos); if (!m->revindex_data) BUG("pack_pos_to_midx: reverse index not yet loaded"); - if (m->num_objects <= pos) + if (m->num_objects + m->num_objects_in_base <= pos) BUG("pack_pos_to_midx: out-of-bounds object at %"PRIu32, pos); - return get_be32(m->revindex_data + pos); + return get_be32(m->revindex_data + pos - m->num_objects_in_base); } struct midx_pack_key { @@ -491,7 +508,8 @@ static int midx_pack_order_cmp(const void *va, const void *vb) const struct midx_pack_key *key = va; struct multi_pack_index *midx = key->midx; - uint32_t versus = pack_pos_to_midx(midx, (uint32_t*)vb - (const uint32_t *)midx->revindex_data); + size_t pos = (uint32_t *)vb - (const uint32_t *)midx->revindex_data; + uint32_t versus = pack_pos_to_midx(midx, pos + midx->num_objects_in_base); uint32_t versus_pack = nth_midxed_pack_int_id(midx, versus); off_t versus_offset; @@ -529,9 +547,9 @@ static int midx_key_to_pack_pos(struct multi_pack_index *m, { uint32_t *found; - if (key->pack >= m->num_packs) + if (key->pack >= m->num_packs + m->num_packs_in_base) BUG("MIDX pack lookup out of bounds (%"PRIu32" >= %"PRIu32")", - key->pack, m->num_packs); + key->pack, m->num_packs + m->num_packs_in_base); /* * The preferred pack sorts first, so determine its identifier by * looking at the first object in pseudo-pack order. @@ -551,7 +569,8 @@ static int midx_key_to_pack_pos(struct multi_pack_index *m, if (!found) return -1; - *pos = found - m->revindex_data; + *pos = (found - m->revindex_data) + m->num_objects_in_base; + return 0; } @@ -559,9 +578,13 @@ int midx_to_pack_pos(struct multi_pack_index *m, uint32_t at, uint32_t *pos) { struct midx_pack_key key; + while (m && at < m->num_objects_in_base) + m = m->base_midx; + if (!m) + BUG("NULL multi-pack-index for object position: %"PRIu32, at); if (!m->revindex_data) BUG("midx_to_pack_pos: reverse index not yet loaded"); - if (m->num_objects <= at) + if (m->num_objects + m->num_objects_in_base <= at) BUG("midx_to_pack_pos: out-of-bounds object at %"PRIu32, at); key.pack = nth_midxed_pack_int_id(m, at); diff --git a/pack-write.c b/pack-write.c index 823e40b42f..6b06315f80 100644 --- a/pack-write.c +++ b/pack-write.c @@ -1,5 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE - #include "git-compat-util.h" #include "environment.h" #include "gettext.h" @@ -56,7 +54,7 @@ static int need_large_offset(off_t offset, const struct pack_idx_option *opts) * The *sha1 contains the pack content SHA1 hash. * The objects array passed in will be sorted by SHA1 on exit. */ -const char *write_idx_file(const struct git_hash_algo *hash_algo, +const char *write_idx_file(struct repository *repo, const char *index_name, struct pack_idx_entry **objects, int nr_objects, const struct pack_idx_option *opts, const unsigned char *sha1) @@ -82,7 +80,7 @@ const char *write_idx_file(const struct git_hash_algo *hash_algo, if (opts->flags & WRITE_IDX_VERIFY) { assert(index_name); - f = hashfd_check(index_name); + f = hashfd_check(repo->hash_algo, index_name); } else { if (!index_name) { struct strbuf tmp_file = STRBUF_INIT; @@ -92,7 +90,7 @@ const char *write_idx_file(const struct git_hash_algo *hash_algo, unlink(index_name); fd = xopen(index_name, O_CREAT|O_EXCL|O_WRONLY, 0600); } - f = hashfd(fd, index_name); + f = hashfd(repo->hash_algo, fd, index_name); } /* if last object's offset is >= 2^31 we should use index V2 */ @@ -131,7 +129,7 @@ const char *write_idx_file(const struct git_hash_algo *hash_algo, struct pack_idx_entry *obj = *list++; if (index_version < 2) hashwrite_be32(f, obj->offset); - hashwrite(f, obj->oid.hash, hash_algo->rawsz); + hashwrite(f, obj->oid.hash, repo->hash_algo->rawsz); if ((opts->flags & WRITE_IDX_STRICT) && (i && oideq(&list[-2]->oid, &obj->oid))) die("The same object %s appears twice in the pack", @@ -173,7 +171,7 @@ const char *write_idx_file(const struct git_hash_algo *hash_algo, } } - hashwrite(f, sha1, hash_algo->rawsz); + hashwrite(f, sha1, repo->hash_algo->rawsz); finalize_hashfile(f, NULL, FSYNC_COMPONENT_PACK_METADATA, CSUM_HASH_IN_STREAM | CSUM_CLOSE | ((opts->flags & WRITE_IDX_VERIFY) ? 0 : CSUM_FSYNC)); @@ -217,7 +215,7 @@ static void write_rev_trailer(const struct git_hash_algo *hash_algo, hashwrite(f, hash, hash_algo->rawsz); } -char *write_rev_file(const struct git_hash_algo *hash_algo, +char *write_rev_file(struct repository *repo, const char *rev_name, struct pack_idx_entry **objects, uint32_t nr_objects, @@ -236,7 +234,7 @@ char *write_rev_file(const struct git_hash_algo *hash_algo, pack_order[i] = i; QSORT_S(pack_order, nr_objects, pack_order_cmp, objects); - ret = write_rev_file_order(hash_algo, rev_name, pack_order, nr_objects, + ret = write_rev_file_order(repo, rev_name, pack_order, nr_objects, hash, flags); free(pack_order); @@ -244,7 +242,7 @@ char *write_rev_file(const struct git_hash_algo *hash_algo, return ret; } -char *write_rev_file_order(const struct git_hash_algo *hash_algo, +char *write_rev_file_order(struct repository *repo, const char *rev_name, uint32_t *pack_order, uint32_t nr_objects, @@ -268,7 +266,7 @@ char *write_rev_file_order(const struct git_hash_algo *hash_algo, fd = xopen(rev_name, O_CREAT|O_EXCL|O_WRONLY, 0600); path = xstrdup(rev_name); } - f = hashfd(fd, path); + f = hashfd(repo->hash_algo, fd, path); } else if (flags & WRITE_REV_VERIFY) { struct stat statbuf; if (stat(rev_name, &statbuf)) { @@ -278,18 +276,18 @@ char *write_rev_file_order(const struct git_hash_algo *hash_algo, } else die_errno(_("could not stat: %s"), rev_name); } - f = hashfd_check(rev_name); + f = hashfd_check(repo->hash_algo, rev_name); path = xstrdup(rev_name); } else { return NULL; } - write_rev_header(hash_algo, f); + write_rev_header(repo->hash_algo, f); write_rev_index_positions(f, pack_order, nr_objects); - write_rev_trailer(hash_algo, f, hash); + write_rev_trailer(repo->hash_algo, f, hash); - if (adjust_shared_perm(the_repository, path) < 0) + if (adjust_shared_perm(repo, path) < 0) die(_("failed to make %s readable"), path); finalize_hashfile(f, NULL, FSYNC_COMPONENT_PACK_METADATA, @@ -330,7 +328,7 @@ static void write_mtimes_trailer(const struct git_hash_algo *hash_algo, hashwrite(f, hash, hash_algo->rawsz); } -static char *write_mtimes_file(const struct git_hash_algo *hash_algo, +static char *write_mtimes_file(struct repository *repo, struct packing_data *to_pack, struct pack_idx_entry **objects, uint32_t nr_objects, @@ -346,13 +344,13 @@ static char *write_mtimes_file(const struct git_hash_algo *hash_algo, fd = odb_mkstemp(&tmp_file, "pack/tmp_mtimes_XXXXXX"); mtimes_name = strbuf_detach(&tmp_file, NULL); - f = hashfd(fd, mtimes_name); + f = hashfd(repo->hash_algo, fd, mtimes_name); - write_mtimes_header(hash_algo, f); + write_mtimes_header(repo->hash_algo, f); write_mtimes_objects(f, to_pack, objects, nr_objects); - write_mtimes_trailer(hash_algo, f, hash); + write_mtimes_trailer(repo->hash_algo, f, hash); - if (adjust_shared_perm(the_repository, mtimes_name) < 0) + if (adjust_shared_perm(repo, mtimes_name) < 0) die(_("failed to make %s readable"), mtimes_name); finalize_hashfile(f, NULL, FSYNC_COMPONENT_PACK_METADATA, @@ -527,14 +525,15 @@ int encode_in_pack_object_header(unsigned char *hdr, int hdr_len, return n; } -struct hashfile *create_tmp_packfile(char **pack_tmp_name) +struct hashfile *create_tmp_packfile(struct repository *repo, + char **pack_tmp_name) { struct strbuf tmpname = STRBUF_INIT; int fd; fd = odb_mkstemp(&tmpname, "pack/tmp_pack_XXXXXX"); *pack_tmp_name = strbuf_detach(&tmpname, NULL); - return hashfd(fd, *pack_tmp_name); + return hashfd(repo->hash_algo, fd, *pack_tmp_name); } static void rename_tmp_packfile(struct strbuf *name_prefix, const char *source, @@ -555,7 +554,7 @@ void rename_tmp_packfile_idx(struct strbuf *name_buffer, rename_tmp_packfile(name_buffer, *idx_tmp_name, "idx"); } -void stage_tmp_packfiles(const struct git_hash_algo *hash_algo, +void stage_tmp_packfiles(struct repository *repo, struct strbuf *name_buffer, const char *pack_tmp_name, struct pack_idx_entry **written_list, @@ -568,19 +567,19 @@ void stage_tmp_packfiles(const struct git_hash_algo *hash_algo, char *rev_tmp_name = NULL; char *mtimes_tmp_name = NULL; - if (adjust_shared_perm(the_repository, pack_tmp_name)) + if (adjust_shared_perm(repo, pack_tmp_name)) die_errno("unable to make temporary pack file readable"); - *idx_tmp_name = (char *)write_idx_file(hash_algo, NULL, written_list, + *idx_tmp_name = (char *)write_idx_file(repo, NULL, written_list, nr_written, pack_idx_opts, hash); - if (adjust_shared_perm(the_repository, *idx_tmp_name)) + if (adjust_shared_perm(repo, *idx_tmp_name)) die_errno("unable to make temporary index file readable"); - rev_tmp_name = write_rev_file(hash_algo, NULL, written_list, nr_written, + rev_tmp_name = write_rev_file(repo, NULL, written_list, nr_written, hash, pack_idx_opts->flags); if (pack_idx_opts->flags & WRITE_MTIMES) { - mtimes_tmp_name = write_mtimes_file(hash_algo, to_pack, + mtimes_tmp_name = write_mtimes_file(repo, to_pack, written_list, nr_written, hash); } @@ -87,7 +87,7 @@ struct progress; /* Note, the data argument could be NULL if object type is blob */ typedef int (*verify_fn)(const struct object_id *, enum object_type, unsigned long, void*, int*); -const char *write_idx_file(const struct git_hash_algo *hash_algo, +const char *write_idx_file(struct repository *repo, const char *index_name, struct pack_idx_entry **objects, int nr_objects, @@ -106,13 +106,13 @@ struct ref; void write_promisor_file(const char *promisor_name, struct ref **sought, int nr_sought); -char *write_rev_file(const struct git_hash_algo *hash_algo, +char *write_rev_file(struct repository *repo, const char *rev_name, struct pack_idx_entry **objects, uint32_t nr_objects, const unsigned char *hash, unsigned flags); -char *write_rev_file_order(const struct git_hash_algo *hash_algo, +char *write_rev_file_order(struct repository *repo, const char *rev_name, uint32_t *pack_order, uint32_t nr_objects, @@ -134,8 +134,9 @@ int read_pack_header(int fd, struct pack_header *); struct packing_data; -struct hashfile *create_tmp_packfile(char **pack_tmp_name); -void stage_tmp_packfiles(const struct git_hash_algo *hash_algo, +struct hashfile *create_tmp_packfile(struct repository *repo, + char **pack_tmp_name); +void stage_tmp_packfiles(struct repository *repo, struct strbuf *name_buffer, const char *pack_tmp_name, struct pack_idx_entry **written_list, diff --git a/packfile.c b/packfile.c index 2d80d80cb3..9d09f8bc72 100644 --- a/packfile.c +++ b/packfile.c @@ -24,6 +24,7 @@ #include "commit-graph.h" #include "pack-revindex.h" #include "promisor-remote.h" +#include "pack-mtimes.h" char *odb_pack_name(struct repository *r, struct strbuf *buf, const unsigned char *hash, const char *ext) @@ -2107,7 +2108,7 @@ static void maybe_invalidate_kept_pack_cache(struct repository *r, r->objects->kept_pack_cache.flags = 0; } -static struct packed_git **kept_pack_cache(struct repository *r, unsigned flags) +struct packed_git **kept_pack_cache(struct repository *r, unsigned flags) { maybe_invalidate_kept_pack_cache(r, flags); diff --git a/packfile.h b/packfile.h index 00ada7a938..25097213d0 100644 --- a/packfile.h +++ b/packfile.h @@ -197,6 +197,8 @@ int has_object_pack(struct repository *r, const struct object_id *oid); int has_object_kept_pack(struct repository *r, const struct object_id *oid, unsigned flags); +struct packed_git **kept_pack_cache(struct repository *r, unsigned flags); + /* * Return 1 if an object in a promisor packfile is or refers to the given * object, 0 otherwise. diff --git a/parallel-checkout.c b/parallel-checkout.c index 7cc6b30528..57c2dcaa8f 100644 --- a/parallel-checkout.c +++ b/parallel-checkout.c @@ -277,7 +277,7 @@ static int write_pc_item_to_fd(struct parallel_checkout_item *pc_item, int fd, ssize_t wrote; /* Sanity check */ - assert(is_eligible_for_parallel_checkout(pc_item->ce, &pc_item->ca)); + ASSERT(is_eligible_for_parallel_checkout(pc_item->ce, &pc_item->ca)); filter = get_stream_filter_ca(&pc_item->ca, &pc_item->ce->oid); if (filter) { 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/promisor-remote.c b/promisor-remote.c index 6a0a61382f..5801ebfd9b 100644 --- a/promisor-remote.c +++ b/promisor-remote.c @@ -323,13 +323,15 @@ static void promisor_info_vecs(struct repository *repo, promisor_remote_init(repo); for (r = repo->promisor_remote_config->promisors; r; r = r->next) { - char *url; + const char *url; char *url_key = xstrfmt("remote.%s.url", r->name); - strvec_push(names, r->name); - strvec_push(urls, git_config_get_string(url_key, &url) ? NULL : url); + /* Only add remotes with a non empty URL */ + if (!git_config_get_string_tmp(url_key, &url) && *url) { + strvec_push(names, r->name); + strvec_push(urls, url); + } - free(url); free(url_key); } } @@ -356,10 +358,8 @@ char *promisor_remote_info(struct repository *repo) strbuf_addch(&sb, ';'); strbuf_addstr(&sb, "name="); strbuf_addstr_urlencode(&sb, names.v[i], allow_unsanitized); - if (urls.v[i]) { - strbuf_addstr(&sb, ",url="); - strbuf_addstr_urlencode(&sb, urls.v[i], allow_unsanitized); - } + strbuf_addstr(&sb, ",url="); + strbuf_addstr_urlencode(&sb, urls.v[i], allow_unsanitized); } strvec_clear(&names); @@ -370,13 +370,13 @@ char *promisor_remote_info(struct repository *repo) /* * Find first index of 'nicks' where there is 'nick'. 'nick' is - * compared case insensitively to the strings in 'nicks'. If not found + * compared case sensitively to the strings in 'nicks'. If not found * 'nicks->nr' is returned. */ static size_t remote_nick_find(struct strvec *nicks, const char *nick) { for (size_t i = 0; i < nicks->nr; i++) - if (!strcasecmp(nicks->v[i], nick)) + if (!strcmp(nicks->v[i], nick)) return i; return nicks->nr; } @@ -409,10 +409,15 @@ static int should_accept_remote(enum accept_promisor accept, if (accept != ACCEPT_KNOWN_URL) BUG("Unhandled 'enum accept_promisor' value '%d'", accept); + if (!remote_url || !*remote_url) { + warning(_("no or empty URL advertised for remote '%s'"), remote_name); + return 0; + } + if (!strcmp(urls->v[i], remote_url)) return 1; - warning(_("known remote named '%s' but with url '%s' instead of '%s'"), + warning(_("known remote named '%s' but with URL '%s' instead of '%s'"), remote_name, urls->v[i], remote_url); return 0; diff --git a/pseudo-merge.h b/pseudo-merge.h index c9fbe9d312..cf0e62ecd1 100644 --- a/pseudo-merge.h +++ b/pseudo-merge.h @@ -210,7 +210,7 @@ int cascade_pseudo_merges(const struct pseudo_merge_map *pm, /* * Returns a pseudo-merge which contains the exact set of commits - * listed in the "parents" bitamp, or NULL if none could be found. + * listed in the "parents" bitmap, or NULL if none could be found. */ struct pseudo_merge *pseudo_merge_for_parents(const struct pseudo_merge_map *pm, struct bitmap *parents); 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..1b26b9b1d7 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); 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; @@ -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); @@ -1377,7 +1378,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 +1397,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); } @@ -1699,6 +1700,24 @@ struct ref_iterator *refs_ref_iterator_begin( enum do_for_each_ref_flags flags) { struct ref_iterator *iter; + struct strvec normalized_exclude_patterns = STRVEC_INIT; + + if (exclude_patterns) { + for (size_t i = 0; exclude_patterns[i]; i++) { + const char *pattern = exclude_patterns[i]; + size_t len = strlen(pattern); + if (!len) + continue; + + if (pattern[len - 1] == '/') + strvec_push(&normalized_exclude_patterns, pattern); + else + strvec_pushf(&normalized_exclude_patterns, "%s/", + pattern); + } + + exclude_patterns = normalized_exclude_patterns.v; + } if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) { static int ref_paranoia = -1; @@ -1719,6 +1738,8 @@ struct ref_iterator *refs_ref_iterator_begin( if (trim) iter = prefix_ref_iterator_begin(iter, "", trim); + strvec_clear(&normalized_exclude_patterns); + return iter; } @@ -2160,7 +2181,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; } @@ -2345,14 +2366,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 @@ -2475,19 +2496,18 @@ int ref_transaction_commit(struct ref_transaction *transaction, 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) +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) { - const char *slash; - const char *extra_refname; struct strbuf dirname = STRBUF_INIT; struct strbuf referent = STRBUF_INIT; - struct object_id oid; - unsigned int type; + struct string_list_item *item; + struct ref_iterator *iter = NULL; + struct strset dirnames; int ret = -1; /* @@ -2497,86 +2517,130 @@ int refs_verify_refname_available(struct ref_store *refs, assert(err); - strbuf_grow(&dirname, strlen(refname) + 1); - for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) { - /* - * Just saying "Is a directory" when we e.g. can't - * lock some multi-level ref isn't very informative, - * the user won't be told *what* is a directory, so - * let's not use strerror() below. - */ - int ignore_errno; - /* Expand dirname to the new prefix, not including the trailing slash: */ - strbuf_add(&dirname, refname + dirname.len, slash - refname - dirname.len); + strset_init(&dirnames); + + for_each_string_list_item(item, refnames) { + const char *refname = item->string; + const char *extra_refname; + struct object_id oid; + unsigned int type; + const char *slash; + + strbuf_reset(&dirname); + + for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) { + /* + * Just saying "Is a directory" when we e.g. can't + * lock some multi-level ref isn't very informative, + * the user won't be told *what* is a directory, so + * let's not use strerror() below. + */ + int ignore_errno; + + /* Expand dirname to the new prefix, not including the trailing slash: */ + strbuf_add(&dirname, refname + dirname.len, slash - refname - dirname.len); + + /* + * We are still at a leading dir of the refname (e.g., + * "refs/foo"; if there is a reference with that name, + * it is a conflict, *unless* it is in skip. + */ + if (skip && string_list_has_string(skip, dirname.buf)) + continue; + + /* + * 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. + */ + if (!strset_add(&dirnames, dirname.buf)) + continue; + + if (!initial_transaction && + !refs_read_raw_ref(refs, dirname.buf, &oid, &referent, + &type, &ignore_errno)) { + strbuf_addf(err, _("'%s' exists; cannot create '%s'"), + dirname.buf, refname); + goto cleanup; + } + + if (extras && string_list_has_string(extras, dirname.buf)) { + strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"), + refname, dirname.buf); + goto cleanup; + } + } /* - * We are still at a leading dir of the refname (e.g., - * "refs/foo"; if there is a reference with that name, - * it is a conflict, *unless* it is in skip. + * We are at the leaf of our refname (e.g., "refs/foo/bar"). + * There is no point in searching for a reference with that + * name, because a refname isn't considered to conflict with + * itself. But we still need to check for references whose + * names are in the "refs/foo/bar/" namespace, because they + * *do* conflict. */ - if (skip && string_list_has_string(skip, dirname.buf)) - continue; + strbuf_addstr(&dirname, refname + dirname.len); + strbuf_addch(&dirname, '/'); - if (!initial_transaction && - !refs_read_raw_ref(refs, dirname.buf, &oid, &referent, - &type, &ignore_errno)) { - strbuf_addf(err, _("'%s' exists; cannot create '%s'"), - dirname.buf, refname); - goto cleanup; - } + if (!initial_transaction) { + int ok; - if (extras && string_list_has_string(extras, dirname.buf)) { - strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"), - refname, dirname.buf); - goto cleanup; - } - } + if (!iter) { + iter = refs_ref_iterator_begin(refs, dirname.buf, NULL, 0, + DO_FOR_EACH_INCLUDE_BROKEN); + } else if (ref_iterator_seek(iter, dirname.buf) < 0) { + goto cleanup; + } - /* - * We are at the leaf of our refname (e.g., "refs/foo/bar"). - * There is no point in searching for a reference with that - * name, because a refname isn't considered to conflict with - * itself. But we still need to check for references whose - * names are in the "refs/foo/bar/" namespace, because they - * *do* conflict. - */ - strbuf_addstr(&dirname, refname + dirname.len); - strbuf_addch(&dirname, '/'); - - if (!initial_transaction) { - struct ref_iterator *iter; - int ok; - - iter = refs_ref_iterator_begin(refs, dirname.buf, NULL, 0, - DO_FOR_EACH_INCLUDE_BROKEN); - while ((ok = ref_iterator_advance(iter)) == ITER_OK) { - if (skip && - string_list_has_string(skip, iter->refname)) - continue; + while ((ok = ref_iterator_advance(iter)) == ITER_OK) { + if (skip && + string_list_has_string(skip, iter->refname)) + continue; - strbuf_addf(err, _("'%s' exists; cannot create '%s'"), - iter->refname, refname); - ref_iterator_abort(iter); - goto cleanup; + strbuf_addf(err, _("'%s' exists; cannot create '%s'"), + iter->refname, refname); + goto cleanup; + } + + if (ok != ITER_DONE) + BUG("error while iterating over references"); } - if (ok != ITER_DONE) - BUG("error while iterating over references"); + extra_refname = find_descendant_ref(dirname.buf, extras, skip); + if (extra_refname) { + strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"), + refname, extra_refname); + goto cleanup; + } } - extra_refname = find_descendant_ref(dirname.buf, extras, skip); - if (extra_refname) - strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"), - refname, extra_refname); - else - ret = 0; + ret = 0; cleanup: strbuf_release(&referent); strbuf_release(&dirname); + 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) +{ + struct string_list_item item = { .string = (char *) refname }; + struct string_list refnames = { + .items = &item, + .nr = 1, + }; + + return refs_verify_refnames_available(refs, &refnames, extras, skip, + initial_transaction, err); +} + struct do_for_each_reflog_help { each_reflog_fn *fn; void *cb_data; @@ -2794,7 +2858,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) @@ -124,6 +124,18 @@ int refs_verify_refname_available(struct ref_store *refs, 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); + int refs_ref_exists(struct ref_store *refs, const char *refname); int should_autocreate_reflog(enum log_refs_config log_all_ref_updates, diff --git a/refs/debug.c b/refs/debug.c index fbc4df08b4..485e3079d7 100644 --- a/refs/debug.c +++ b/refs/debug.c @@ -169,6 +169,16 @@ static int debug_ref_iterator_advance(struct ref_iterator *ref_iterator) return res; } +static int debug_ref_iterator_seek(struct ref_iterator *ref_iterator, + const char *prefix) +{ + struct debug_ref_iterator *diter = + (struct debug_ref_iterator *)ref_iterator; + int res = diter->iter->vtable->seek(diter->iter, prefix); + trace_printf_key(&trace_refs, "iterator_seek: %s: %d\n", prefix ? prefix : "", res); + return res; +} + static int debug_ref_iterator_peel(struct ref_iterator *ref_iterator, struct object_id *peeled) { @@ -179,19 +189,19 @@ static int debug_ref_iterator_peel(struct ref_iterator *ref_iterator, return res; } -static int debug_ref_iterator_abort(struct ref_iterator *ref_iterator) +static void debug_ref_iterator_release(struct ref_iterator *ref_iterator) { struct debug_ref_iterator *diter = (struct debug_ref_iterator *)ref_iterator; - int res = diter->iter->vtable->abort(diter->iter); - trace_printf_key(&trace_refs, "iterator_abort: %d\n", res); - return res; + diter->iter->vtable->release(diter->iter); + trace_printf_key(&trace_refs, "iterator_abort\n"); } static struct ref_iterator_vtable debug_ref_iterator_vtable = { .advance = debug_ref_iterator_advance, + .seek = debug_ref_iterator_seek, .peel = debug_ref_iterator_peel, - .abort = debug_ref_iterator_abort, + .release = debug_ref_iterator_release, }; static struct ref_iterator * @@ -217,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 6c6e67dc1c..91d3aca70a 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -678,6 +678,7 @@ static void unlock_ref(struct ref_lock *lock) */ 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, @@ -855,16 +856,11 @@ retry: } /* - * If the ref did not exist and we are creating it, - * make sure there is no existing packed ref that - * conflicts with refname: + * If the ref did not exist and we are creating it, we have to + * make sure there is no existing packed ref that conflicts + * with refname. This check is deferred so that we can batch it. */ - if (refs_verify_refname_available( - refs->packed_ref_store, refname, - extras, NULL, 0, err)) { - ret = TRANSACTION_NAME_CONFLICT; - goto error_return; - } + string_list_append(refnames_to_check, refname); } ret = 0; @@ -919,13 +915,17 @@ static int files_ref_iterator_advance(struct ref_iterator *ref_iterator) return ITER_OK; } - iter->iter0 = NULL; - if (ref_iterator_abort(ref_iterator) != ITER_DONE) - ok = ITER_ERROR; - return ok; } +static int files_ref_iterator_seek(struct ref_iterator *ref_iterator, + const char *prefix) +{ + struct files_ref_iterator *iter = + (struct files_ref_iterator *)ref_iterator; + return ref_iterator_seek(iter->iter0, prefix); +} + static int files_ref_iterator_peel(struct ref_iterator *ref_iterator, struct object_id *peeled) { @@ -935,23 +935,18 @@ static int files_ref_iterator_peel(struct ref_iterator *ref_iterator, return ref_iterator_peel(iter->iter0, peeled); } -static int files_ref_iterator_abort(struct ref_iterator *ref_iterator) +static void files_ref_iterator_release(struct ref_iterator *ref_iterator) { struct files_ref_iterator *iter = (struct files_ref_iterator *)ref_iterator; - int ok = ITER_DONE; - - if (iter->iter0) - ok = ref_iterator_abort(iter->iter0); - - base_ref_iterator_free(ref_iterator); - return ok; + ref_iterator_free(iter->iter0); } static struct ref_iterator_vtable files_ref_iterator_vtable = { .advance = files_ref_iterator_advance, + .seek = files_ref_iterator_seek, .peel = files_ref_iterator_peel, - .abort = files_ref_iterator_abort, + .release = files_ref_iterator_release, }; static struct ref_iterator *files_ref_iterator_begin( @@ -1270,7 +1265,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; @@ -1382,7 +1377,7 @@ static int should_pack_refs(struct files_ref_store *refs, iter->flags, opts)) refcount++; if (refcount >= limit) { - ref_iterator_abort(iter); + ref_iterator_free(iter); return 1; } } @@ -1390,6 +1385,7 @@ static int should_pack_refs(struct files_ref_store *refs, if (ret != ITER_DONE) die("error while iterating over references"); + ref_iterator_free(iter); return 0; } @@ -1456,6 +1452,7 @@ static int files_pack_refs(struct ref_store *ref_store, packed_refs_unlock(refs->packed_ref_store); prune_refs(refs, &refs_to_prune); + ref_iterator_free(iter); strbuf_release(&err); return 0; } @@ -2303,35 +2300,33 @@ static int files_reflog_iterator_advance(struct ref_iterator *ref_iterator) return ITER_OK; } - iter->dir_iterator = NULL; - if (ref_iterator_abort(ref_iterator) == ITER_ERROR) - ok = ITER_ERROR; return ok; } +static int files_reflog_iterator_seek(struct ref_iterator *ref_iterator UNUSED, + const char *prefix UNUSED) +{ + BUG("ref_iterator_seek() called for reflog_iterator"); +} + static int files_reflog_iterator_peel(struct ref_iterator *ref_iterator UNUSED, struct object_id *peeled UNUSED) { BUG("ref_iterator_peel() called for reflog_iterator"); } -static int files_reflog_iterator_abort(struct ref_iterator *ref_iterator) +static void files_reflog_iterator_release(struct ref_iterator *ref_iterator) { struct files_reflog_iterator *iter = (struct files_reflog_iterator *)ref_iterator; - int ok = ITER_DONE; - - if (iter->dir_iterator) - ok = dir_iterator_abort(iter->dir_iterator); - - base_ref_iterator_free(ref_iterator); - return ok; + dir_iterator_free(iter->dir_iterator); } static struct ref_iterator_vtable files_reflog_iterator_vtable = { .advance = files_reflog_iterator_advance, + .seek = files_reflog_iterator_seek, .peel = files_reflog_iterator_peel, - .abort = files_reflog_iterator_abort, + .release = files_reflog_iterator_release, }; static struct ref_iterator *reflog_iterator_begin(struct ref_store *ref_store, @@ -2569,6 +2564,7 @@ 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) { @@ -2597,7 +2593,7 @@ static int lock_ref_for_update(struct files_ref_store *refs, lock->count++; } else { ret = lock_raw_ref(refs, update->refname, mustexist, - affected_refnames, + refnames_to_check, affected_refnames, &lock, &referent, &update->type, err); if (ret) { @@ -2811,6 +2807,7 @@ static int files_transaction_prepare(struct ref_store *ref_store, 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; struct files_transaction_backend_data *backend_data; @@ -2898,7 +2895,8 @@ static int files_transaction_prepare(struct ref_store *ref_store, struct ref_update *update = transaction->updates[i]; ret = lock_ref_for_update(refs, update, transaction, - head_ref, &affected_refnames, err); + head_ref, &refnames_to_check, + &affected_refnames, err); if (ret) goto cleanup; @@ -2930,6 +2928,26 @@ static int files_transaction_prepare(struct ref_store *ref_store, } } + /* + * Verify that none of the loose reference that we're about to write + * conflict with any existing packed references. Ideally, we'd do this + * check after the packed-refs are locked so that the file cannot + * change underneath our feet. But introducing such a lock now would + * probably do more harm than good as users rely on there not being a + * global lock with the "files" backend. + * + * Another alternative would be to do the check after the (optional) + * lock, but that would extend the time we spend in the globally-locked + * state. + * + * 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; + goto cleanup; + } + if (packed_transaction) { if (packed_refs_lock(refs->packed_ref_store, 0, err)) { ret = TRANSACTION_GENERIC_ERROR; @@ -2972,6 +2990,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); if (ret) files_transaction_cleanup(refs, transaction); @@ -3036,6 +3055,7 @@ static int files_transaction_finish_initial(struct files_ref_store *refs, 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; struct ref_transaction *packed_transaction = NULL; struct ref_transaction *loose_transaction = NULL; @@ -3085,11 +3105,7 @@ static int files_transaction_finish_initial(struct files_ref_store *refs, !is_null_oid(&update->old_oid)) BUG("initial ref transaction with old_sha1 set"); - if (refs_verify_refname_available(&refs->base, update->refname, - &affected_refnames, NULL, 1, err)) { - ret = TRANSACTION_NAME_CONFLICT; - goto cleanup; - } + string_list_append(&refnames_to_check, update->refname); /* * packed-refs don't support symbolic refs, root refs and reflogs, @@ -3125,8 +3141,19 @@ static int files_transaction_finish_initial(struct files_ref_store *refs, } } - if (packed_refs_lock(refs->packed_ref_store, 0, err) || - ref_transaction_commit(packed_transaction, err)) { + if (packed_refs_lock(refs->packed_ref_store, 0, err)) { + ret = TRANSACTION_GENERIC_ERROR; + goto cleanup; + } + + if (refs_verify_refnames_available(&refs->base, &refnames_to_check, + &affected_refnames, NULL, 1, err)) { + packed_refs_unlock(refs->packed_ref_store); + ret = TRANSACTION_NAME_CONFLICT; + goto cleanup; + } + + if (ref_transaction_commit(packed_transaction, err)) { ret = TRANSACTION_GENERIC_ERROR; goto cleanup; } @@ -3147,6 +3174,7 @@ cleanup: ref_transaction_free(packed_transaction); transaction->state = REF_TRANSACTION_CLOSED; string_list_clear(&affected_refnames, 0); + string_list_clear(&refnames_to_check, 0); return ret; } @@ -3808,6 +3836,7 @@ static int files_fsck_refs_dir(struct ref_store *ref_store, ret = error(_("failed to iterate over '%s'"), sb.buf); out: + dir_iterator_free(iter); strbuf_release(&sb); strbuf_release(&refname); return ret; diff --git a/refs/iterator.c b/refs/iterator.c index d25e568bf0..766d96e795 100644 --- a/refs/iterator.c +++ b/refs/iterator.c @@ -15,15 +15,26 @@ int ref_iterator_advance(struct ref_iterator *ref_iterator) return ref_iterator->vtable->advance(ref_iterator); } +int ref_iterator_seek(struct ref_iterator *ref_iterator, + const char *prefix) +{ + return ref_iterator->vtable->seek(ref_iterator, prefix); +} + int ref_iterator_peel(struct ref_iterator *ref_iterator, struct object_id *peeled) { return ref_iterator->vtable->peel(ref_iterator, peeled); } -int ref_iterator_abort(struct ref_iterator *ref_iterator) +void ref_iterator_free(struct ref_iterator *ref_iterator) { - return ref_iterator->vtable->abort(ref_iterator); + if (ref_iterator) { + ref_iterator->vtable->release(ref_iterator); + /* Help make use-after-free bugs fail quickly: */ + ref_iterator->vtable = NULL; + free(ref_iterator); + } } void base_ref_iterator_init(struct ref_iterator *iter, @@ -36,20 +47,19 @@ void base_ref_iterator_init(struct ref_iterator *iter, iter->flags = 0; } -void base_ref_iterator_free(struct ref_iterator *iter) -{ - /* Help make use-after-free bugs fail quickly: */ - iter->vtable = NULL; - free(iter); -} - struct empty_ref_iterator { struct ref_iterator base; }; -static int empty_ref_iterator_advance(struct ref_iterator *ref_iterator) +static int empty_ref_iterator_advance(struct ref_iterator *ref_iterator UNUSED) +{ + return ITER_DONE; +} + +static int empty_ref_iterator_seek(struct ref_iterator *ref_iterator UNUSED, + const char *prefix UNUSED) { - return ref_iterator_abort(ref_iterator); + return 0; } static int empty_ref_iterator_peel(struct ref_iterator *ref_iterator UNUSED, @@ -58,16 +68,15 @@ static int empty_ref_iterator_peel(struct ref_iterator *ref_iterator UNUSED, BUG("peel called for empty iterator"); } -static int empty_ref_iterator_abort(struct ref_iterator *ref_iterator) +static void empty_ref_iterator_release(struct ref_iterator *ref_iterator UNUSED) { - base_ref_iterator_free(ref_iterator); - return ITER_DONE; } static struct ref_iterator_vtable empty_ref_iterator_vtable = { .advance = empty_ref_iterator_advance, + .seek = empty_ref_iterator_seek, .peel = empty_ref_iterator_peel, - .abort = empty_ref_iterator_abort, + .release = empty_ref_iterator_release, }; struct ref_iterator *empty_ref_iterator_begin(void) @@ -87,7 +96,8 @@ int is_empty_ref_iterator(struct ref_iterator *ref_iterator) struct merge_ref_iterator { struct ref_iterator base; - struct ref_iterator *iter0, *iter1; + struct ref_iterator *iter0, *iter0_owned; + struct ref_iterator *iter1, *iter1_owned; ref_iterator_select_fn *select; void *cb_data; @@ -179,9 +189,8 @@ static int merge_ref_iterator_advance(struct ref_iterator *ref_iterator) iter->select(iter->iter0, iter->iter1, iter->cb_data); if (selection == ITER_SELECT_DONE) { - return ref_iterator_abort(ref_iterator); + return ITER_DONE; } else if (selection == ITER_SELECT_ERROR) { - ref_iterator_abort(ref_iterator); return ITER_ERROR; } @@ -211,10 +220,31 @@ static int merge_ref_iterator_advance(struct ref_iterator *ref_iterator) } error: - ref_iterator_abort(ref_iterator); return ITER_ERROR; } +static int merge_ref_iterator_seek(struct ref_iterator *ref_iterator, + const char *prefix) +{ + struct merge_ref_iterator *iter = + (struct merge_ref_iterator *)ref_iterator; + int ret; + + iter->current = NULL; + iter->iter0 = iter->iter0_owned; + iter->iter1 = iter->iter1_owned; + + ret = ref_iterator_seek(iter->iter0, prefix); + if (ret < 0) + return ret; + + ret = ref_iterator_seek(iter->iter1, prefix); + if (ret < 0) + return ret; + + return 0; +} + static int merge_ref_iterator_peel(struct ref_iterator *ref_iterator, struct object_id *peeled) { @@ -227,28 +257,19 @@ static int merge_ref_iterator_peel(struct ref_iterator *ref_iterator, return ref_iterator_peel(*iter->current, peeled); } -static int merge_ref_iterator_abort(struct ref_iterator *ref_iterator) +static void merge_ref_iterator_release(struct ref_iterator *ref_iterator) { struct merge_ref_iterator *iter = (struct merge_ref_iterator *)ref_iterator; - int ok = ITER_DONE; - - if (iter->iter0) { - if (ref_iterator_abort(iter->iter0) != ITER_DONE) - ok = ITER_ERROR; - } - if (iter->iter1) { - if (ref_iterator_abort(iter->iter1) != ITER_DONE) - ok = ITER_ERROR; - } - base_ref_iterator_free(ref_iterator); - return ok; + ref_iterator_free(iter->iter0_owned); + ref_iterator_free(iter->iter1_owned); } static struct ref_iterator_vtable merge_ref_iterator_vtable = { .advance = merge_ref_iterator_advance, + .seek = merge_ref_iterator_seek, .peel = merge_ref_iterator_peel, - .abort = merge_ref_iterator_abort, + .release = merge_ref_iterator_release, }; struct ref_iterator *merge_ref_iterator_begin( @@ -267,8 +288,8 @@ struct ref_iterator *merge_ref_iterator_begin( */ base_ref_iterator_init(ref_iterator, &merge_ref_iterator_vtable); - iter->iter0 = iter0; - iter->iter1 = iter1; + iter->iter0 = iter->iter0_owned = iter0; + iter->iter1 = iter->iter1_owned = iter1; iter->select = select; iter->cb_data = cb_data; iter->current = NULL; @@ -310,10 +331,10 @@ struct ref_iterator *overlay_ref_iterator_begin( * them. */ if (is_empty_ref_iterator(front)) { - ref_iterator_abort(front); + ref_iterator_free(front); return back; } else if (is_empty_ref_iterator(back)) { - ref_iterator_abort(back); + ref_iterator_free(back); return front; } @@ -350,19 +371,15 @@ static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator) while ((ok = ref_iterator_advance(iter->iter0)) == ITER_OK) { int cmp = compare_prefix(iter->iter0->refname, iter->prefix); - if (cmp < 0) continue; - - if (cmp > 0) { - /* - * As the source iterator is ordered, we - * can stop the iteration as soon as we see a - * refname that comes after the prefix: - */ - ok = ref_iterator_abort(iter->iter0); - break; - } + /* + * As the source iterator is ordered, we + * can stop the iteration as soon as we see a + * refname that comes after the prefix: + */ + if (cmp > 0) + return ITER_DONE; if (iter->trim) { /* @@ -386,12 +403,19 @@ static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator) return ITER_OK; } - iter->iter0 = NULL; - if (ref_iterator_abort(ref_iterator) != ITER_DONE) - return ITER_ERROR; return ok; } +static int prefix_ref_iterator_seek(struct ref_iterator *ref_iterator, + const char *prefix) +{ + struct prefix_ref_iterator *iter = + (struct prefix_ref_iterator *)ref_iterator; + free(iter->prefix); + iter->prefix = xstrdup_or_null(prefix); + return ref_iterator_seek(iter->iter0, prefix); +} + static int prefix_ref_iterator_peel(struct ref_iterator *ref_iterator, struct object_id *peeled) { @@ -401,23 +425,19 @@ static int prefix_ref_iterator_peel(struct ref_iterator *ref_iterator, return ref_iterator_peel(iter->iter0, peeled); } -static int prefix_ref_iterator_abort(struct ref_iterator *ref_iterator) +static void prefix_ref_iterator_release(struct ref_iterator *ref_iterator) { struct prefix_ref_iterator *iter = (struct prefix_ref_iterator *)ref_iterator; - int ok = ITER_DONE; - - if (iter->iter0) - ok = ref_iterator_abort(iter->iter0); + ref_iterator_free(iter->iter0); free(iter->prefix); - base_ref_iterator_free(ref_iterator); - return ok; } static struct ref_iterator_vtable prefix_ref_iterator_vtable = { .advance = prefix_ref_iterator_advance, + .seek = prefix_ref_iterator_seek, .peel = prefix_ref_iterator_peel, - .abort = prefix_ref_iterator_abort, + .release = prefix_ref_iterator_release, }; struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0, @@ -453,20 +473,14 @@ int do_for_each_ref_iterator(struct ref_iterator *iter, current_ref_iter = iter; while ((ok = ref_iterator_advance(iter)) == ITER_OK) { retval = fn(iter->refname, iter->referent, iter->oid, iter->flags, cb_data); - if (retval) { - /* - * If ref_iterator_abort() returns ITER_ERROR, - * we ignore that error in deference to the - * callback function's return value. - */ - ref_iterator_abort(iter); + if (retval) goto out; - } } out: current_ref_iter = old_ref_iter; if (ok == ITER_ERROR) - return -1; + retval = -1; + ref_iterator_free(iter); return retval; } diff --git a/refs/packed-backend.c b/refs/packed-backend.c index a7b6f74b6e..b4289a7d9c 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -4,6 +4,7 @@ #include "../git-compat-util.h" #include "../config.h" #include "../dir.h" +#include "../fsck.h" #include "../gettext.h" #include "../hash.h" #include "../hex.h" @@ -299,14 +300,9 @@ struct snapshot_record { size_t len; }; -static int cmp_packed_ref_records(const void *v1, const void *v2, - void *cb_data) -{ - const struct snapshot *snapshot = cb_data; - const struct snapshot_record *e1 = v1, *e2 = v2; - const char *r1 = e1->start + snapshot_hexsz(snapshot) + 1; - const char *r2 = e2->start + snapshot_hexsz(snapshot) + 1; +static int cmp_packed_refname(const char *r1, const char *r2) +{ while (1) { if (*r1 == '\n') return *r2 == '\n' ? 0 : -1; @@ -321,6 +317,17 @@ static int cmp_packed_ref_records(const void *v1, const void *v2, } } +static int cmp_packed_ref_records(const void *v1, const void *v2, + void *cb_data) +{ + const struct snapshot *snapshot = cb_data; + const struct snapshot_record *e1 = v1, *e2 = v2; + const char *r1 = e1->start + snapshot_hexsz(snapshot) + 1; + const char *r2 = e2->start + snapshot_hexsz(snapshot) + 1; + + return cmp_packed_refname(r1, r2); +} + /* * Compare a snapshot record at `rec` to the specified NUL-terminated * refname. @@ -493,6 +500,21 @@ static void verify_buffer_safe(struct snapshot *snapshot) last_line, eof - last_line); } +/* + * When parsing the "packed-refs" file, we will parse it line by line. + * Because we know the start pointer of the refname and the next + * newline pointer, we could calculate the length of the refname by + * subtracting the two pointers. However, there is a corner case where + * the refname contains corrupted embedded NUL characters. And + * `check_refname_format()` will not catch this when the truncated + * refname is still a valid refname. To prevent this, we need to check + * whether the refname contains the NUL characters. + */ +static int refname_contains_nul(struct strbuf *refname) +{ + return !!memchr(refname->buf, '\0', refname->len); +} + #define SMALL_FILE_SIZE (32*1024) /* @@ -693,7 +715,7 @@ static struct snapshot *create_snapshot(struct packed_ref_store *refs) tmp = xmemdupz(snapshot->buf, eol - snapshot->buf); - if (!skip_prefix(tmp, "# pack-refs with:", (const char **)&p)) + if (!skip_prefix(tmp, "# pack-refs with: ", (const char **)&p)) die_invalid_line(refs->path, snapshot->buf, snapshot->eof - snapshot->buf); @@ -819,6 +841,8 @@ struct packed_ref_iterator { struct snapshot *snapshot; + char *prefix; + /* The current position in the snapshot's buffer: */ const char *pos; @@ -841,11 +865,9 @@ struct packed_ref_iterator { }; /* - * Move the iterator to the next record in the snapshot, without - * respect for whether the record is actually required by the current - * iteration. Adjust the fields in `iter` and return `ITER_OK` or - * `ITER_DONE`. This function does not free the iterator in the case - * of `ITER_DONE`. + * Move the iterator to the next record in the snapshot. Adjust the fields in + * `iter` and return `ITER_OK` or `ITER_DONE`. This function does not free the + * iterator in the case of `ITER_DONE`. */ static int next_record(struct packed_ref_iterator *iter) { @@ -894,6 +916,9 @@ static int next_record(struct packed_ref_iterator *iter) strbuf_add(&iter->refname_buf, p, eol - p); iter->base.refname = iter->refname_buf.buf; + if (refname_contains_nul(&iter->refname_buf)) + die("packed refname contains embedded NULL: %s", iter->base.refname); + if (check_refname_format(iter->base.refname, REFNAME_ALLOW_ONELEVEL)) { if (!refname_is_safe(iter->base.refname)) die("packed refname is dangerous: %s", @@ -942,6 +967,9 @@ static int packed_ref_iterator_advance(struct ref_iterator *ref_iterator) int ok; while ((ok = next_record(iter)) == ITER_OK) { + const char *refname = iter->base.refname; + const char *prefix = iter->prefix; + if (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY && !is_per_worktree_ref(iter->base.refname)) continue; @@ -951,15 +979,41 @@ static int packed_ref_iterator_advance(struct ref_iterator *ref_iterator) &iter->oid, iter->flags)) continue; + while (prefix && *prefix) { + if (*refname < *prefix) + BUG("packed-refs backend yielded reference preceding its prefix"); + else if (*refname > *prefix) + return ITER_DONE; + prefix++; + refname++; + } + return ITER_OK; } - if (ref_iterator_abort(ref_iterator) != ITER_DONE) - ok = ITER_ERROR; - return ok; } +static int packed_ref_iterator_seek(struct ref_iterator *ref_iterator, + const char *prefix) +{ + struct packed_ref_iterator *iter = + (struct packed_ref_iterator *)ref_iterator; + const char *start; + + if (prefix && *prefix) + start = find_reference_location(iter->snapshot, prefix, 0); + else + start = iter->snapshot->start; + + free(iter->prefix); + iter->prefix = xstrdup_or_null(prefix); + iter->pos = start; + iter->eof = iter->snapshot->eof; + + return 0; +} + static int packed_ref_iterator_peel(struct ref_iterator *ref_iterator, struct object_id *peeled) { @@ -976,23 +1030,21 @@ static int packed_ref_iterator_peel(struct ref_iterator *ref_iterator, } } -static int packed_ref_iterator_abort(struct ref_iterator *ref_iterator) +static void packed_ref_iterator_release(struct ref_iterator *ref_iterator) { struct packed_ref_iterator *iter = (struct packed_ref_iterator *)ref_iterator; - int ok = ITER_DONE; - strbuf_release(&iter->refname_buf); free(iter->jump); + free(iter->prefix); release_snapshot(iter->snapshot); - base_ref_iterator_free(ref_iterator); - return ok; } static struct ref_iterator_vtable packed_ref_iterator_vtable = { .advance = packed_ref_iterator_advance, + .seek = packed_ref_iterator_seek, .peel = packed_ref_iterator_peel, - .abort = packed_ref_iterator_abort + .release = packed_ref_iterator_release, }; static int jump_list_entry_cmp(const void *va, const void *vb) @@ -1104,7 +1156,6 @@ static struct ref_iterator *packed_ref_iterator_begin( { struct packed_ref_store *refs; struct snapshot *snapshot; - const char *start; struct packed_ref_iterator *iter; struct ref_iterator *ref_iterator; unsigned int required_flags = REF_STORE_READ; @@ -1120,14 +1171,6 @@ static struct ref_iterator *packed_ref_iterator_begin( */ snapshot = get_snapshot(refs); - if (prefix && *prefix) - start = find_reference_location(snapshot, prefix, 0); - else - start = snapshot->start; - - if (start == snapshot->eof) - return empty_ref_iterator_begin(); - CALLOC_ARRAY(iter, 1); ref_iterator = &iter->base; base_ref_iterator_init(ref_iterator, &packed_ref_iterator_vtable); @@ -1137,19 +1180,15 @@ static struct ref_iterator *packed_ref_iterator_begin( iter->snapshot = snapshot; acquire_snapshot(snapshot); - - iter->pos = start; - iter->eof = snapshot->eof; strbuf_init(&iter->refname_buf, 0); - iter->base.oid = &iter->oid; - iter->repo = ref_store->repo; iter->flags = flags; - if (prefix && *prefix) - /* Stop iteration after we've gone *past* prefix: */ - ref_iterator = prefix_ref_iterator_begin(ref_iterator, prefix, 0); + if (packed_ref_iterator_seek(&iter->base, prefix) < 0) { + ref_iterator_free(&iter->base); + return NULL; + } return ref_iterator; } @@ -1362,8 +1401,10 @@ static int write_with_updates(struct packed_ref_store *refs, */ iter = packed_ref_iterator_begin(&refs->base, "", NULL, DO_FOR_EACH_INCLUDE_BROKEN); - if ((ok = ref_iterator_advance(iter)) != ITER_OK) + if ((ok = ref_iterator_advance(iter)) != ITER_OK) { + ref_iterator_free(iter); iter = NULL; + } i = 0; @@ -1411,8 +1452,10 @@ static int write_with_updates(struct packed_ref_store *refs, * the iterator over the unneeded * value. */ - if ((ok = ref_iterator_advance(iter)) != ITER_OK) + if ((ok = ref_iterator_advance(iter)) != ITER_OK) { + ref_iterator_free(iter); iter = NULL; + } cmp = +1; } else { /* @@ -1449,8 +1492,10 @@ static int write_with_updates(struct packed_ref_store *refs, peel_error ? NULL : &peeled)) goto write_error; - if ((ok = ref_iterator_advance(iter)) != ITER_OK) + if ((ok = ref_iterator_advance(iter)) != ITER_OK) { + ref_iterator_free(iter); iter = NULL; + } } else if (is_null_oid(&update->new_oid)) { /* * The update wants to delete the reference, @@ -1499,9 +1544,7 @@ write_error: get_tempfile_path(refs->tempfile), strerror(errno)); error: - if (iter) - ref_iterator_abort(iter); - + ref_iterator_free(iter); delete_tempfile(&refs->tempfile); return -1; } @@ -1748,15 +1791,329 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s return empty_ref_iterator_begin(); } -static int packed_fsck(struct ref_store *ref_store UNUSED, - struct fsck_options *o UNUSED, +static int packed_fsck_ref_next_line(struct fsck_options *o, + unsigned long line_number, const char *start, + const char *eof, const char **eol) +{ + int ret = 0; + + *eol = memchr(start, '\n', eof - start); + if (!*eol) { + struct strbuf packed_entry = STRBUF_INIT; + struct fsck_ref_report report = { 0 }; + + strbuf_addf(&packed_entry, "packed-refs line %lu", line_number); + report.path = packed_entry.buf; + ret = fsck_report_ref(o, &report, + FSCK_MSG_PACKED_REF_ENTRY_NOT_TERMINATED, + "'%.*s' is not terminated with a newline", + (int)(eof - start), start); + + /* + * There is no newline but we still want to parse it to the end of + * the buffer. + */ + *eol = eof; + strbuf_release(&packed_entry); + } + + return ret; +} + +static int packed_fsck_ref_header(struct fsck_options *o, + const char *start, const char *eol, + unsigned int *sorted) +{ + struct string_list traits = STRING_LIST_INIT_NODUP; + char *tmp_line; + int ret = 0; + char *p; + + tmp_line = xmemdupz(start, eol - start); + if (!skip_prefix(tmp_line, "# pack-refs with: ", (const char **)&p)) { + struct fsck_ref_report report = { 0 }; + report.path = "packed-refs.header"; + + ret = fsck_report_ref(o, &report, + FSCK_MSG_BAD_PACKED_REF_HEADER, + "'%.*s' does not start with '# pack-refs with: '", + (int)(eol - start), start); + goto cleanup; + } + + string_list_split_in_place(&traits, p, " ", -1); + *sorted = unsorted_string_list_has_string(&traits, "sorted"); + +cleanup: + free(tmp_line); + string_list_clear(&traits, 0); + return ret; +} + +static int packed_fsck_ref_peeled_line(struct fsck_options *o, + struct ref_store *ref_store, + unsigned long line_number, + const char *start, const char *eol) +{ + struct strbuf packed_entry = STRBUF_INIT; + struct fsck_ref_report report = { 0 }; + struct object_id peeled; + const char *p; + int ret = 0; + + /* + * Skip the '^' and parse the peeled oid. + */ + start++; + if (parse_oid_hex_algop(start, &peeled, &p, ref_store->repo->hash_algo)) { + strbuf_addf(&packed_entry, "packed-refs line %lu", line_number); + report.path = packed_entry.buf; + + ret = fsck_report_ref(o, &report, + FSCK_MSG_BAD_PACKED_REF_ENTRY, + "'%.*s' has invalid peeled oid", + (int)(eol - start), start); + goto cleanup; + } + + if (p != eol) { + strbuf_addf(&packed_entry, "packed-refs line %lu", line_number); + report.path = packed_entry.buf; + + ret = fsck_report_ref(o, &report, + FSCK_MSG_BAD_PACKED_REF_ENTRY, + "has trailing garbage after peeled oid '%.*s'", + (int)(eol - p), p); + goto cleanup; + } + +cleanup: + strbuf_release(&packed_entry); + return ret; +} + +static int packed_fsck_ref_main_line(struct fsck_options *o, + struct ref_store *ref_store, + unsigned long line_number, + struct strbuf *refname, + const char *start, const char *eol) +{ + struct strbuf packed_entry = STRBUF_INIT; + struct fsck_ref_report report = { 0 }; + struct object_id oid; + const char *p; + int ret = 0; + + if (parse_oid_hex_algop(start, &oid, &p, ref_store->repo->hash_algo)) { + strbuf_addf(&packed_entry, "packed-refs line %lu", line_number); + report.path = packed_entry.buf; + + ret = fsck_report_ref(o, &report, + FSCK_MSG_BAD_PACKED_REF_ENTRY, + "'%.*s' has invalid oid", + (int)(eol - start), start); + goto cleanup; + } + + if (p == eol || !isspace(*p)) { + strbuf_addf(&packed_entry, "packed-refs line %lu", line_number); + report.path = packed_entry.buf; + + ret = fsck_report_ref(o, &report, + FSCK_MSG_BAD_PACKED_REF_ENTRY, + "has no space after oid '%s' but with '%.*s'", + oid_to_hex(&oid), (int)(eol - p), p); + goto cleanup; + } + + p++; + strbuf_reset(refname); + strbuf_add(refname, p, eol - p); + if (refname_contains_nul(refname)) { + strbuf_addf(&packed_entry, "packed-refs line %lu", line_number); + report.path = packed_entry.buf; + + ret = fsck_report_ref(o, &report, + FSCK_MSG_BAD_PACKED_REF_ENTRY, + "refname '%s' contains NULL binaries", + refname->buf); + } + + if (check_refname_format(refname->buf, 0)) { + strbuf_addf(&packed_entry, "packed-refs line %lu", line_number); + report.path = packed_entry.buf; + + ret = fsck_report_ref(o, &report, + FSCK_MSG_BAD_REF_NAME, + "has bad refname '%s'", refname->buf); + } + +cleanup: + strbuf_release(&packed_entry); + return ret; +} + +static int packed_fsck_ref_sorted(struct fsck_options *o, + struct ref_store *ref_store, + const char *start, const char *eof) +{ + size_t hexsz = ref_store->repo->hash_algo->hexsz; + struct strbuf packed_entry = STRBUF_INIT; + struct fsck_ref_report report = { 0 }; + struct strbuf refname1 = STRBUF_INIT; + struct strbuf refname2 = STRBUF_INIT; + unsigned long line_number = 1; + const char *former = NULL; + const char *current; + const char *eol; + int ret = 0; + + if (*start == '#') { + eol = memchr(start, '\n', eof - start); + start = eol + 1; + line_number++; + } + + for (; start < eof; line_number++, start = eol + 1) { + eol = memchr(start, '\n', eof - start); + + if (*start == '^') + continue; + + if (!former) { + former = start + hexsz + 1; + continue; + } + + current = start + hexsz + 1; + if (cmp_packed_refname(former, current) >= 0) { + const char *err_fmt = + "refname '%s' is less than previous refname '%s'"; + + eol = memchr(former, '\n', eof - former); + strbuf_add(&refname1, former, eol - former); + eol = memchr(current, '\n', eof - current); + strbuf_add(&refname2, current, eol - current); + + strbuf_addf(&packed_entry, "packed-refs line %lu", line_number); + report.path = packed_entry.buf; + ret = fsck_report_ref(o, &report, + FSCK_MSG_PACKED_REF_UNSORTED, + err_fmt, refname2.buf, refname1.buf); + goto cleanup; + } + former = current; + } + +cleanup: + strbuf_release(&packed_entry); + strbuf_release(&refname1); + strbuf_release(&refname2); + return ret; +} + +static int packed_fsck_ref_content(struct fsck_options *o, + struct ref_store *ref_store, + unsigned int *sorted, + const char *start, const char *eof) +{ + struct strbuf refname = STRBUF_INIT; + unsigned long line_number = 1; + const char *eol; + int ret = 0; + + ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol); + if (*start == '#') { + ret |= packed_fsck_ref_header(o, start, eol, sorted); + + start = eol + 1; + line_number++; + } + + while (start < eof) { + ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol); + ret |= packed_fsck_ref_main_line(o, ref_store, line_number, &refname, start, eol); + start = eol + 1; + line_number++; + if (start < eof && *start == '^') { + ret |= packed_fsck_ref_next_line(o, line_number, start, eof, &eol); + ret |= packed_fsck_ref_peeled_line(o, ref_store, line_number, + start, eol); + start = eol + 1; + line_number++; + } + } + + strbuf_release(&refname); + return ret; +} + +static int packed_fsck(struct ref_store *ref_store, + struct fsck_options *o, struct worktree *wt) { + struct packed_ref_store *refs = packed_downcast(ref_store, + REF_STORE_READ, "fsck"); + struct strbuf packed_ref_content = STRBUF_INIT; + unsigned int sorted = 0; + struct stat st; + int ret = 0; + int fd = -1; if (!is_main_worktree(wt)) - return 0; + goto cleanup; - return 0; + if (o->verbose) + fprintf_ln(stderr, "Checking packed-refs file %s", refs->path); + + fd = open_nofollow(refs->path, O_RDONLY); + if (fd < 0) { + /* + * If the packed-refs file doesn't exist, there's nothing + * to check. + */ + if (errno == ENOENT) + goto cleanup; + + if (errno == ELOOP) { + struct fsck_ref_report report = { 0 }; + report.path = "packed-refs"; + ret = fsck_report_ref(o, &report, + FSCK_MSG_BAD_REF_FILETYPE, + "not a regular file but a symlink"); + goto cleanup; + } + + ret = error_errno(_("unable to open '%s'"), refs->path); + goto cleanup; + } else if (fstat(fd, &st) < 0) { + ret = error_errno(_("unable to stat '%s'"), refs->path); + goto cleanup; + } else if (!S_ISREG(st.st_mode)) { + struct fsck_ref_report report = { 0 }; + report.path = "packed-refs"; + ret = fsck_report_ref(o, &report, + FSCK_MSG_BAD_REF_FILETYPE, + "not a regular file"); + goto cleanup; + } + + if (strbuf_read(&packed_ref_content, fd, 0) < 0) { + ret = error_errno(_("unable to read '%s'"), refs->path); + goto cleanup; + } + + ret = packed_fsck_ref_content(o, ref_store, &sorted, packed_ref_content.buf, + packed_ref_content.buf + packed_ref_content.len); + if (!ret && sorted) + ret = packed_fsck_ref_sorted(o, ref_store, packed_ref_content.buf, + packed_ref_content.buf + packed_ref_content.len); + +cleanup: + if (fd >= 0) + close(fd); + strbuf_release(&packed_ref_content); + return ret; } struct ref_storage_be refs_be_packed = { diff --git a/refs/ref-cache.c b/refs/ref-cache.c index 02f09e4df8..c1f1bab1d5 100644 --- a/refs/ref-cache.c +++ b/refs/ref-cache.c @@ -362,9 +362,7 @@ struct cache_ref_iterator { struct ref_iterator base; /* - * The number of levels currently on the stack. This is always - * at least 1, because when it becomes zero the iteration is - * ended and this struct is freed. + * The number of levels currently on the stack. */ size_t levels_nr; @@ -376,7 +374,7 @@ struct cache_ref_iterator { * The prefix is matched textually, without regard for path * component boundaries. */ - const char *prefix; + char *prefix; /* * A stack of levels. levels[0] is the uppermost level that is @@ -389,6 +387,9 @@ struct cache_ref_iterator { struct cache_ref_iterator_level *levels; struct repository *repo; + struct ref_cache *cache; + + int prime_dir; }; static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator) @@ -396,6 +397,9 @@ static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator) struct cache_ref_iterator *iter = (struct cache_ref_iterator *)ref_iterator; + if (!iter->levels_nr) + return ITER_DONE; + while (1) { struct cache_ref_iterator_level *level = &iter->levels[iter->levels_nr - 1]; @@ -409,7 +413,7 @@ static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator) if (++level->index == level->dir->nr) { /* This level is exhausted; pop up a level */ if (--iter->levels_nr == 0) - return ref_iterator_abort(ref_iterator); + return ITER_DONE; continue; } @@ -444,6 +448,41 @@ static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator) } } +static int cache_ref_iterator_seek(struct ref_iterator *ref_iterator, + const char *prefix) +{ + struct cache_ref_iterator *iter = + (struct cache_ref_iterator *)ref_iterator; + struct cache_ref_iterator_level *level; + struct ref_dir *dir; + + dir = get_ref_dir(iter->cache->root); + if (prefix && *prefix) + dir = find_containing_dir(dir, prefix); + if (!dir) { + iter->levels_nr = 0; + return 0; + } + + if (iter->prime_dir) + prime_ref_dir(dir, prefix); + iter->levels_nr = 1; + level = &iter->levels[0]; + level->index = -1; + level->dir = dir; + + if (prefix && *prefix) { + free(iter->prefix); + iter->prefix = xstrdup(prefix); + level->prefix_state = PREFIX_WITHIN_DIR; + } else { + FREE_AND_NULL(iter->prefix); + level->prefix_state = PREFIX_CONTAINS_DIR; + } + + return 0; +} + static int cache_ref_iterator_peel(struct ref_iterator *ref_iterator, struct object_id *peeled) { @@ -452,21 +491,19 @@ static int cache_ref_iterator_peel(struct ref_iterator *ref_iterator, return peel_object(iter->repo, ref_iterator->oid, peeled) ? -1 : 0; } -static int cache_ref_iterator_abort(struct ref_iterator *ref_iterator) +static void cache_ref_iterator_release(struct ref_iterator *ref_iterator) { struct cache_ref_iterator *iter = (struct cache_ref_iterator *)ref_iterator; - - free((char *)iter->prefix); + free(iter->prefix); free(iter->levels); - base_ref_iterator_free(ref_iterator); - return ITER_DONE; } static struct ref_iterator_vtable cache_ref_iterator_vtable = { .advance = cache_ref_iterator_advance, + .seek = cache_ref_iterator_seek, .peel = cache_ref_iterator_peel, - .abort = cache_ref_iterator_abort + .release = cache_ref_iterator_release, }; struct ref_iterator *cache_ref_iterator_begin(struct ref_cache *cache, @@ -474,39 +511,22 @@ struct ref_iterator *cache_ref_iterator_begin(struct ref_cache *cache, struct repository *repo, int prime_dir) { - struct ref_dir *dir; struct cache_ref_iterator *iter; struct ref_iterator *ref_iterator; - struct cache_ref_iterator_level *level; - - dir = get_ref_dir(cache->root); - if (prefix && *prefix) - dir = find_containing_dir(dir, prefix); - if (!dir) - /* There's nothing to iterate over. */ - return empty_ref_iterator_begin(); - - if (prime_dir) - prime_ref_dir(dir, prefix); CALLOC_ARRAY(iter, 1); ref_iterator = &iter->base; base_ref_iterator_init(ref_iterator, &cache_ref_iterator_vtable); ALLOC_GROW(iter->levels, 10, iter->levels_alloc); - iter->levels_nr = 1; - level = &iter->levels[0]; - level->index = -1; - level->dir = dir; + iter->repo = repo; + iter->cache = cache; + iter->prime_dir = prime_dir; - if (prefix && *prefix) { - iter->prefix = xstrdup(prefix); - level->prefix_state = PREFIX_WITHIN_DIR; - } else { - level->prefix_state = PREFIX_CONTAINS_DIR; + if (cache_ref_iterator_seek(&iter->base, prefix) < 0) { + ref_iterator_free(&iter->base); + return NULL; } - iter->repo = repo; - return ref_iterator; } diff --git a/refs/refs-internal.h b/refs/refs-internal.h index 8894b43d1d..e5862757a7 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -273,11 +273,11 @@ enum do_for_each_ref_flags { * the next reference and returns ITER_OK. The data pointed at by * refname and oid belong to the iterator; if you want to retain them * after calling ref_iterator_advance() again or calling - * ref_iterator_abort(), you must make a copy. When the iteration has + * ref_iterator_free(), you must make a copy. When the iteration has * been exhausted, ref_iterator_advance() releases any resources * associated with the iteration, frees the ref_iterator object, and * returns ITER_DONE. If you want to abort the iteration early, call - * ref_iterator_abort(), which also frees the ref_iterator object and + * ref_iterator_free(), which also frees the ref_iterator object and * any associated resources. If there was an internal error advancing * to the next entry, ref_iterator_advance() aborts the iteration, * frees the ref_iterator, and returns ITER_ERROR. @@ -293,7 +293,7 @@ enum do_for_each_ref_flags { * * while ((ok = ref_iterator_advance(iter)) == ITER_OK) { * if (want_to_stop_iteration()) { - * ok = ref_iterator_abort(iter); + * ok = ITER_DONE; * break; * } * @@ -307,6 +307,7 @@ enum do_for_each_ref_flags { * * if (ok != ITER_DONE) * handle_error(); + * ref_iterator_free(iter); */ struct ref_iterator { struct ref_iterator_vtable *vtable; @@ -327,18 +328,30 @@ struct ref_iterator { int ref_iterator_advance(struct ref_iterator *ref_iterator); /* + * Seek the iterator to the first reference with the given prefix. + * The prefix is matched as a literal string, without regard for path + * separators. If prefix is NULL or the empty string, seek the iterator to the + * first reference again. + * + * This function is expected to behave as if a new ref iterator with the same + * prefix had been created, but allows reuse of iterators and thus may allow + * the backend to optimize. Parameters other than the prefix that have been + * passed when creating the iterator will remain unchanged. + * + * Returns 0 on success, a negative error code otherwise. + */ +int ref_iterator_seek(struct ref_iterator *ref_iterator, + const char *prefix); + +/* * If possible, peel the reference currently being viewed by the * iterator. Return 0 on success. */ int ref_iterator_peel(struct ref_iterator *ref_iterator, struct object_id *peeled); -/* - * End the iteration before it has been exhausted, freeing the - * reference iterator and any associated resources and returning - * ITER_DONE. If the abort itself failed, return ITER_ERROR. - */ -int ref_iterator_abort(struct ref_iterator *ref_iterator); +/* Free the reference iterator and any associated resources. */ +void ref_iterator_free(struct ref_iterator *ref_iterator); /* * An iterator over nothing (its first ref_iterator_advance() call @@ -438,13 +451,6 @@ struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0, void base_ref_iterator_init(struct ref_iterator *iter, struct ref_iterator_vtable *vtable); -/* - * Base class destructor for ref_iterators. Destroy the ref_iterator - * part of iter and shallow-free the object. This is meant to be - * called only by the destructors of derived classes. - */ -void base_ref_iterator_free(struct ref_iterator *iter); - /* Virtual function declarations for ref_iterators: */ /* @@ -456,6 +462,13 @@ void base_ref_iterator_free(struct ref_iterator *iter); typedef int ref_iterator_advance_fn(struct ref_iterator *ref_iterator); /* + * Seek the iterator to the first reference matching the given prefix. Should + * behave the same as if a new iterator was created with the same prefix. + */ +typedef int ref_iterator_seek_fn(struct ref_iterator *ref_iterator, + const char *prefix); + +/* * Peels the current ref, returning 0 for success or -1 for failure. */ typedef int ref_iterator_peel_fn(struct ref_iterator *ref_iterator, @@ -463,15 +476,15 @@ typedef int ref_iterator_peel_fn(struct ref_iterator *ref_iterator, /* * Implementations of this function should free any resources specific - * to the derived class, then call base_ref_iterator_free() to clean - * up and free the ref_iterator object. + * to the derived class. */ -typedef int ref_iterator_abort_fn(struct ref_iterator *ref_iterator); +typedef void ref_iterator_release_fn(struct ref_iterator *ref_iterator); struct ref_iterator_vtable { ref_iterator_advance_fn *advance; + ref_iterator_seek_fn *seek; ref_iterator_peel_fn *peel; - ref_iterator_abort_fn *abort; + ref_iterator_release_fn *release; }; /* diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c index 771f50df38..ae434cd248 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -547,7 +547,7 @@ struct reftable_ref_iterator { struct reftable_ref_record ref; struct object_id oid; - const char *prefix; + char *prefix; size_t prefix_len; char **exclude_patterns; size_t exclude_patterns_index; @@ -711,20 +711,27 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator) break; } - if (iter->err > 0) { - if (ref_iterator_abort(ref_iterator) != ITER_DONE) - return ITER_ERROR; + if (iter->err > 0) return ITER_DONE; - } - - if (iter->err < 0) { - ref_iterator_abort(ref_iterator); + if (iter->err < 0) return ITER_ERROR; - } - return ITER_OK; } +static int reftable_ref_iterator_seek(struct ref_iterator *ref_iterator, + const char *prefix) +{ + struct reftable_ref_iterator *iter = + (struct reftable_ref_iterator *)ref_iterator; + + free(iter->prefix); + iter->prefix = xstrdup_or_null(prefix); + iter->prefix_len = prefix ? strlen(prefix) : 0; + iter->err = reftable_iterator_seek_ref(&iter->iter, prefix); + + return iter->err; +} + static int reftable_ref_iterator_peel(struct ref_iterator *ref_iterator, struct object_id *peeled) { @@ -740,7 +747,7 @@ static int reftable_ref_iterator_peel(struct ref_iterator *ref_iterator, return -1; } -static int reftable_ref_iterator_abort(struct ref_iterator *ref_iterator) +static void reftable_ref_iterator_release(struct ref_iterator *ref_iterator) { struct reftable_ref_iterator *iter = (struct reftable_ref_iterator *)ref_iterator; @@ -751,14 +758,14 @@ static int reftable_ref_iterator_abort(struct ref_iterator *ref_iterator) free(iter->exclude_patterns[i]); free(iter->exclude_patterns); } - free(iter); - return ITER_DONE; + free(iter->prefix); } static struct ref_iterator_vtable reftable_ref_iterator_vtable = { .advance = reftable_ref_iterator_advance, + .seek = reftable_ref_iterator_seek, .peel = reftable_ref_iterator_peel, - .abort = reftable_ref_iterator_abort + .release = reftable_ref_iterator_release, }; static int qsort_strcmp(const void *va, const void *vb) @@ -815,8 +822,6 @@ static struct reftable_ref_iterator *ref_iterator_for_stack(struct reftable_ref_ iter = xcalloc(1, sizeof(*iter)); base_ref_iterator_init(&iter->base, &reftable_ref_iterator_vtable); - iter->prefix = prefix; - iter->prefix_len = prefix ? strlen(prefix) : 0; iter->base.oid = &iter->oid; iter->flags = flags; iter->refs = refs; @@ -830,8 +835,11 @@ static struct reftable_ref_iterator *ref_iterator_for_stack(struct reftable_ref_ if (ret) goto done; - reftable_stack_init_ref_iterator(stack, &iter->iter); - ret = reftable_iterator_seek_ref(&iter->iter, prefix); + ret = reftable_stack_init_ref_iterator(stack, &iter->iter); + if (ret) + goto done; + + ret = reftable_ref_iterator_seek(&iter->base, prefix); if (ret) goto done; @@ -1069,6 +1077,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store, 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; struct object_id head_oid; @@ -1224,12 +1233,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store, * can output a proper error message instead of failing * at a later point. */ - ret = refs_verify_refname_available(ref_store, u->refname, - &affected_refnames, NULL, - transaction->flags & REF_TRANSACTION_FLAG_INITIAL, - err); - if (ret < 0) - goto done; + string_list_append(&refnames_to_check, u->refname); /* * There is no need to write the reference deletion @@ -1379,6 +1383,12 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store, } } + ret = refs_verify_refnames_available(ref_store, &refnames_to_check, &affected_refnames, NULL, + transaction->flags & REF_TRANSACTION_FLAG_INITIAL, + err); + if (ret < 0) + goto done; + transaction->backend_data = tx_data; transaction->state = REF_TRANSACTION_PREPARED; @@ -1394,6 +1404,7 @@ done: string_list_clear(&affected_refnames, 0); strbuf_release(&referent); strbuf_release(&head_referent); + string_list_clear(&refnames_to_check, 0); return ret; } @@ -2017,20 +2028,20 @@ static int reftable_reflog_iterator_advance(struct ref_iterator *ref_iterator) break; } - if (iter->err > 0) { - if (ref_iterator_abort(ref_iterator) != ITER_DONE) - return ITER_ERROR; + if (iter->err > 0) return ITER_DONE; - } - - if (iter->err < 0) { - ref_iterator_abort(ref_iterator); + if (iter->err < 0) return ITER_ERROR; - } - return ITER_OK; } +static int reftable_reflog_iterator_seek(struct ref_iterator *ref_iterator UNUSED, + const char *prefix UNUSED) +{ + BUG("reftable reflog iterator cannot be seeked"); + return -1; +} + static int reftable_reflog_iterator_peel(struct ref_iterator *ref_iterator UNUSED, struct object_id *peeled UNUSED) { @@ -2038,21 +2049,20 @@ static int reftable_reflog_iterator_peel(struct ref_iterator *ref_iterator UNUSE return -1; } -static int reftable_reflog_iterator_abort(struct ref_iterator *ref_iterator) +static void reftable_reflog_iterator_release(struct ref_iterator *ref_iterator) { struct reftable_reflog_iterator *iter = (struct reftable_reflog_iterator *)ref_iterator; reftable_log_record_release(&iter->log); reftable_iterator_destroy(&iter->iter); strbuf_release(&iter->last_name); - free(iter); - return ITER_DONE; } static struct ref_iterator_vtable reftable_reflog_iterator_vtable = { .advance = reftable_reflog_iterator_advance, + .seek = reftable_reflog_iterator_seek, .peel = reftable_reflog_iterator_peel, - .abort = reftable_reflog_iterator_abort + .release = reftable_reflog_iterator_release, }; static struct reftable_reflog_iterator *reflog_iterator_for_stack(struct reftable_ref_store *refs, @@ -153,18 +153,22 @@ static int parse_refspec(struct refspec_item *item, const char *refspec, int fet return 1; } -int refspec_item_init(struct refspec_item *item, const char *refspec, int fetch) +static int refspec_item_init(struct refspec_item *item, const char *refspec, + int fetch) { memset(item, 0, sizeof(*item)); item->raw = xstrdup(refspec); return parse_refspec(item, refspec, fetch); } -void refspec_item_init_or_die(struct refspec_item *item, const char *refspec, - int fetch) +int refspec_item_init_fetch(struct refspec_item *item, const char *refspec) { - if (!refspec_item_init(item, refspec, fetch)) - die(_("invalid refspec '%s'"), refspec); + return refspec_item_init(item, refspec, 1); +} + +int refspec_item_init_push(struct refspec_item *item, const char *refspec) +{ + return refspec_item_init(item, refspec, 0); } void refspec_item_clear(struct refspec_item *item) @@ -178,17 +182,29 @@ void refspec_item_clear(struct refspec_item *item) item->exact_sha1 = 0; } -void refspec_init(struct refspec *rs, int fetch) +void refspec_init_fetch(struct refspec *rs) +{ + struct refspec blank = REFSPEC_INIT_FETCH; + memcpy(rs, &blank, sizeof(*rs)); +} + +void refspec_init_push(struct refspec *rs) { - memset(rs, 0, sizeof(*rs)); - rs->fetch = fetch; + struct refspec blank = REFSPEC_INIT_PUSH; + memcpy(rs, &blank, sizeof(*rs)); } void refspec_append(struct refspec *rs, const char *refspec) { struct refspec_item item; + int ret; - refspec_item_init_or_die(&item, refspec, rs->fetch); + if (rs->fetch) + ret = refspec_item_init_fetch(&item, refspec); + else + ret = refspec_item_init_push(&item, refspec); + if (!ret) + die(_("invalid refspec '%s'"), refspec); ALLOC_GROW(rs->items, rs->nr + 1, rs->alloc); rs->items[rs->nr] = item; @@ -233,7 +249,7 @@ void refspec_clear(struct refspec *rs) int valid_fetch_refspec(const char *fetch_refspec_str) { struct refspec_item refspec; - int ret = refspec_item_init(&refspec, fetch_refspec_str, REFSPEC_FETCH); + int ret = refspec_item_init_fetch(&refspec, fetch_refspec_str); refspec_item_clear(&refspec); return ret; } @@ -246,14 +262,24 @@ void refspec_ref_prefixes(const struct refspec *rs, const struct refspec_item *item = &rs->items[i]; const char *prefix = NULL; - if (item->exact_sha1 || item->negative) + if (item->negative) continue; - if (rs->fetch == REFSPEC_FETCH) - prefix = item->src; - else if (item->dst) - prefix = item->dst; - else if (item->src && !item->exact_sha1) + + if (rs->fetch) { + if (item->exact_sha1) + continue; prefix = item->src; + } else { + /* + * Pushes can have an explicit destination like + * "foo:bar", or can implicitly use the src for both + * ("foo" is the same as "foo:foo"). + */ + if (item->dst) + prefix = item->dst; + else if (item->src && !item->exact_sha1) + prefix = item->src; + } if (!prefix) continue; @@ -32,11 +32,8 @@ struct refspec_item { struct string_list; -#define REFSPEC_FETCH 1 -#define REFSPEC_PUSH 0 - -#define REFSPEC_INIT_FETCH { .fetch = REFSPEC_FETCH } -#define REFSPEC_INIT_PUSH { .fetch = REFSPEC_PUSH } +#define REFSPEC_INIT_FETCH { .fetch = 1 } +#define REFSPEC_INIT_PUSH { .fetch = 0 } /** * An array of strings can be parsed into a struct refspec using @@ -47,15 +44,14 @@ struct refspec { int alloc; int nr; - int fetch; + unsigned fetch : 1; }; -int refspec_item_init(struct refspec_item *item, const char *refspec, - int fetch); -void refspec_item_init_or_die(struct refspec_item *item, const char *refspec, - int fetch); +int refspec_item_init_fetch(struct refspec_item *item, const char *refspec); +int refspec_item_init_push(struct refspec_item *item, const char *refspec); void refspec_item_clear(struct refspec_item *item); -void refspec_init(struct refspec *rs, int fetch); +void refspec_init_fetch(struct refspec *rs); +void refspec_init_push(struct refspec *rs); void refspec_append(struct refspec *rs, const char *refspec); __attribute__((format (printf,2,3))) void refspec_appendf(struct refspec *rs, const char *fmt, ...); diff --git a/reftable/basics.c b/reftable/basics.c index 3b5ea27bbd..8c4a4433e4 100644 --- a/reftable/basics.c +++ b/reftable/basics.c @@ -147,25 +147,6 @@ char *reftable_buf_detach(struct reftable_buf *buf) return result; } -void put_be24(uint8_t *out, uint32_t i) -{ - out[0] = (uint8_t)((i >> 16) & 0xff); - out[1] = (uint8_t)((i >> 8) & 0xff); - out[2] = (uint8_t)(i & 0xff); -} - -uint32_t get_be24(uint8_t *in) -{ - return (uint32_t)(in[0]) << 16 | (uint32_t)(in[1]) << 8 | - (uint32_t)(in[2]); -} - -void put_be16(uint8_t *out, uint16_t i) -{ - out[0] = (uint8_t)((i >> 8) & 0xff); - out[1] = (uint8_t)(i & 0xff); -} - size_t binsearch(size_t sz, int (*f)(size_t k, void *args), void *args) { size_t lo = 0; diff --git a/reftable/basics.h b/reftable/basics.h index a2a010a0e1..fd59cbb772 100644 --- a/reftable/basics.h +++ b/reftable/basics.h @@ -16,6 +16,8 @@ https://developers.google.com/open-source/licenses/bsd #include "system.h" #include "reftable-basics.h" +#define REFTABLE_UNUSED __attribute__((__unused__)) + struct reftable_buf { size_t alloc; size_t len; @@ -76,9 +78,79 @@ char *reftable_buf_detach(struct reftable_buf *buf); /* Bigendian en/decoding of integers */ -void put_be24(uint8_t *out, uint32_t i); -uint32_t get_be24(uint8_t *in); -void put_be16(uint8_t *out, uint16_t i); +static inline void reftable_put_be16(void *out, uint16_t i) +{ + unsigned char *p = out; + p[0] = (uint8_t)((i >> 8) & 0xff); + p[1] = (uint8_t)((i >> 0) & 0xff); +} + +static inline void reftable_put_be24(void *out, uint32_t i) +{ + unsigned char *p = out; + p[0] = (uint8_t)((i >> 16) & 0xff); + p[1] = (uint8_t)((i >> 8) & 0xff); + p[2] = (uint8_t)((i >> 0) & 0xff); +} + +static inline void reftable_put_be32(void *out, uint32_t i) +{ + unsigned char *p = out; + p[0] = (uint8_t)((i >> 24) & 0xff); + p[1] = (uint8_t)((i >> 16) & 0xff); + p[2] = (uint8_t)((i >> 8) & 0xff); + p[3] = (uint8_t)((i >> 0) & 0xff); +} + +static inline void reftable_put_be64(void *out, uint64_t i) +{ + unsigned char *p = out; + p[0] = (uint8_t)((i >> 56) & 0xff); + p[1] = (uint8_t)((i >> 48) & 0xff); + p[2] = (uint8_t)((i >> 40) & 0xff); + p[3] = (uint8_t)((i >> 32) & 0xff); + p[4] = (uint8_t)((i >> 24) & 0xff); + p[5] = (uint8_t)((i >> 16) & 0xff); + p[6] = (uint8_t)((i >> 8) & 0xff); + p[7] = (uint8_t)((i >> 0) & 0xff); +} + +static inline uint16_t reftable_get_be16(const void *in) +{ + const unsigned char *p = in; + return (uint16_t)(p[0]) << 8 | + (uint16_t)(p[1]) << 0; +} + +static inline uint32_t reftable_get_be24(const void *in) +{ + const unsigned char *p = in; + return (uint32_t)(p[0]) << 16 | + (uint32_t)(p[1]) << 8 | + (uint32_t)(p[2]) << 0; +} + +static inline uint32_t reftable_get_be32(const void *in) +{ + const unsigned char *p = in; + return (uint32_t)(p[0]) << 24 | + (uint32_t)(p[1]) << 16 | + (uint32_t)(p[2]) << 8| + (uint32_t)(p[3]) << 0; +} + +static inline uint64_t reftable_get_be64(const void *in) +{ + const unsigned char *p = in; + return (uint64_t)(p[0]) << 56 | + (uint64_t)(p[1]) << 48 | + (uint64_t)(p[2]) << 40 | + (uint64_t)(p[3]) << 32 | + (uint64_t)(p[4]) << 24 | + (uint64_t)(p[5]) << 16 | + (uint64_t)(p[6]) << 8 | + (uint64_t)(p[7]) << 0; +} /* * find smallest index i in [0, sz) at which `f(i) > 0`, assuming that f is @@ -117,18 +189,46 @@ void reftable_free(void *p); void *reftable_calloc(size_t nelem, size_t elsize); char *reftable_strdup(const char *str); -#define REFTABLE_ALLOC_ARRAY(x, alloc) (x) = reftable_malloc(st_mult(sizeof(*(x)), (alloc))) +static inline int reftable_alloc_size(size_t nelem, size_t elsize, size_t *out) +{ + if (nelem && elsize > SIZE_MAX / nelem) + return -1; + *out = nelem * elsize; + return 0; +} + +#define REFTABLE_ALLOC_ARRAY(x, alloc) do { \ + size_t alloc_size; \ + if (reftable_alloc_size(sizeof(*(x)), (alloc), &alloc_size) < 0) { \ + errno = ENOMEM; \ + (x) = NULL; \ + } else { \ + (x) = reftable_malloc(alloc_size); \ + } \ + } while (0) #define REFTABLE_CALLOC_ARRAY(x, alloc) (x) = reftable_calloc((alloc), sizeof(*(x))) -#define REFTABLE_REALLOC_ARRAY(x, alloc) (x) = reftable_realloc((x), st_mult(sizeof(*(x)), (alloc))) +#define REFTABLE_REALLOC_ARRAY(x, alloc) do { \ + size_t alloc_size; \ + if (reftable_alloc_size(sizeof(*(x)), (alloc), &alloc_size) < 0) { \ + errno = ENOMEM; \ + (x) = NULL; \ + } else { \ + (x) = reftable_realloc((x), alloc_size); \ + } \ + } while (0) static inline void *reftable_alloc_grow(void *p, size_t nelem, size_t elsize, size_t *allocp) { void *new_p; - size_t alloc = *allocp * 2 + 1; + size_t alloc = *allocp * 2 + 1, alloc_bytes; if (alloc < nelem) alloc = nelem; - new_p = reftable_realloc(p, st_mult(elsize, alloc)); + if (reftable_alloc_size(elsize, alloc, &alloc_bytes) < 0) { + errno = ENOMEM; + return p; + } + new_p = reftable_realloc(p, alloc_bytes); if (!new_p) return p; *allocp = alloc; @@ -168,6 +268,15 @@ static inline void *reftable_alloc_grow(void *p, size_t nelem, size_t elsize, # define strdup(str) REFTABLE_BANNED(strdup) #endif +#define REFTABLE_SWAP(a, b) do { \ + void *_swap_a_ptr = &(a); \ + void *_swap_b_ptr = &(b); \ + unsigned char _swap_buffer[sizeof(a) - 2 * sizeof(a) * (sizeof(a) != sizeof(b))]; \ + memcpy(_swap_buffer, _swap_a_ptr, sizeof(a)); \ + memcpy(_swap_a_ptr, _swap_b_ptr, sizeof(a)); \ + memcpy(_swap_b_ptr, _swap_buffer, sizeof(a)); \ +} while (0) + /* Find the longest shared prefix size of `a` and `b` */ size_t common_prefix_size(struct reftable_buf *a, struct reftable_buf *b); diff --git a/reftable/block.c b/reftable/block.c index b14a8f1259..251a8e9fd3 100644 --- a/reftable/block.c +++ b/reftable/block.c @@ -49,7 +49,7 @@ static int block_writer_register_restart(struct block_writer *w, int n, if (is_restart) rlen++; if (2 + 3 * rlen + n > w->block_size - w->next) - return -1; + return REFTABLE_ENTRY_TOO_BIG_ERROR; if (is_restart) { REFTABLE_ALLOC_GROW_OR_NULL(w->restarts, w->restart_len + 1, w->restart_cap); @@ -97,9 +97,10 @@ uint8_t block_writer_type(struct block_writer *bw) return bw->block[bw->header_off]; } -/* Adds the reftable_record to the block. Returns -1 if it does not fit, 0 on - success. Returns REFTABLE_API_ERROR if attempting to write a record with - empty key. */ +/* + * Adds the reftable_record to the block. Returns 0 on success and + * appropriate error codes on failure. + */ int block_writer_add(struct block_writer *w, struct reftable_record *rec) { struct reftable_buf empty = REFTABLE_BUF_INIT; @@ -126,14 +127,14 @@ int block_writer_add(struct block_writer *w, struct reftable_record *rec) n = reftable_encode_key(&is_restart, out, last, w->scratch, reftable_record_val_type(rec)); if (n < 0) { - err = -1; + err = n; goto done; } string_view_consume(&out, n); n = reftable_record_encode(rec, out, w->hash_size); if (n < 0) { - err = -1; + err = n; goto done; } string_view_consume(&out, n); @@ -147,13 +148,13 @@ done: int block_writer_finish(struct block_writer *w) { for (uint32_t i = 0; i < w->restart_len; i++) { - put_be24(w->block + w->next, w->restarts[i]); + reftable_put_be24(w->block + w->next, w->restarts[i]); w->next += 3; } - put_be16(w->block + w->next, w->restart_len); + reftable_put_be16(w->block + w->next, w->restart_len); w->next += 2; - put_be24(w->block + 1 + w->header_off, w->next); + reftable_put_be24(w->block + 1 + w->header_off, w->next); /* * Log records are stored zlib-compressed. Note that the compression @@ -215,7 +216,7 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block, { uint32_t full_block_size = table_block_size; uint8_t typ = block->data[header_off]; - uint32_t sz = get_be24(block->data + header_off + 1); + uint32_t sz = reftable_get_be24(block->data + header_off + 1); int err = 0; uint16_t restart_count = 0; uint32_t restart_start = 0; @@ -299,7 +300,7 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block, full_block_size = sz; } - restart_count = get_be16(block->data + sz - 2); + restart_count = reftable_get_be16(block->data + sz - 2); restart_start = sz - 2 - 3 * restart_count; restart_bytes = block->data + restart_start; @@ -354,7 +355,7 @@ int block_reader_first_key(const struct block_reader *br, struct reftable_buf *k static uint32_t block_reader_restart_offset(const struct block_reader *br, size_t idx) { - return get_be24(br->restart_bytes + 3 * idx); + return reftable_get_be24(br->restart_bytes + 3 * idx); } void block_iter_seek_start(struct block_iter *it, const struct block_reader *br) @@ -508,7 +509,9 @@ int block_iter_seek_key(struct block_iter *it, const struct block_reader *br, it->block_len = br->block_len; it->hash_size = br->hash_size; - reftable_record_init(&rec, block_reader_type(br)); + err = reftable_record_init(&rec, block_reader_type(br)); + if (err < 0) + goto done; /* * We're looking for the last entry less than the wanted key so that diff --git a/reftable/block.h b/reftable/block.h index bef2b8a4c5..64732eba7d 100644 --- a/reftable/block.h +++ b/reftable/block.h @@ -53,7 +53,7 @@ int block_writer_init(struct block_writer *bw, uint8_t typ, uint8_t *block, /* returns the block type (eg. 'r' for ref records. */ uint8_t block_writer_type(struct block_writer *bw); -/* appends the record, or -1 if it doesn't fit. */ +/* Attempts to append the record. Returns 0 on success or error code on failure. */ int block_writer_add(struct block_writer *w, struct reftable_record *rec); /* appends the key restarts, and compress the block if necessary. */ diff --git a/reftable/blocksource.c b/reftable/blocksource.c index bba4a45b98..78c1be2337 100644 --- a/reftable/blocksource.c +++ b/reftable/blocksource.c @@ -13,14 +13,14 @@ https://developers.google.com/open-source/licenses/bsd #include "reftable-blocksource.h" #include "reftable-error.h" -static void reftable_buf_return_block(void *b UNUSED, struct reftable_block *dest) +static void reftable_buf_return_block(void *b REFTABLE_UNUSED, struct reftable_block *dest) { if (dest->len) memset(dest->data, 0xff, dest->len); reftable_free(dest->data); } -static void reftable_buf_close(void *b UNUSED) +static void reftable_buf_close(void *b REFTABLE_UNUSED) { } @@ -67,7 +67,7 @@ static uint64_t file_size(void *b) return ((struct file_block_source *)b)->size; } -static void file_return_block(void *b UNUSED, struct reftable_block *dest UNUSED) +static void file_return_block(void *b REFTABLE_UNUSED, struct reftable_block *dest REFTABLE_UNUSED) { } @@ -98,7 +98,7 @@ static struct reftable_block_source_vtable file_vtable = { int reftable_block_source_from_file(struct reftable_block_source *bs, const char *name) { - struct file_block_source *p; + struct file_block_source *p = NULL; struct stat st; int fd, err; @@ -122,7 +122,12 @@ int reftable_block_source_from_file(struct reftable_block_source *bs, } p->size = st.st_size; - p->data = xmmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + p->data = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (p->data == MAP_FAILED) { + err = REFTABLE_IO_ERROR; + p->data = NULL; + goto out; + } assert(!bs->ops); bs->ops = &file_vtable; @@ -135,5 +140,5 @@ out: close(fd); if (err < 0) reftable_free(p); - return 0; + return err; } diff --git a/reftable/iter.c b/reftable/iter.c index 86e801ca9f..f520382e70 100644 --- a/reftable/iter.c +++ b/reftable/iter.c @@ -25,17 +25,17 @@ int iterator_next(struct reftable_iterator *it, struct reftable_record *rec) return it->ops->next(it->iter_arg, rec); } -static int empty_iterator_seek(void *arg UNUSED, struct reftable_record *want UNUSED) +static int empty_iterator_seek(void *arg REFTABLE_UNUSED, struct reftable_record *want REFTABLE_UNUSED) { return 0; } -static int empty_iterator_next(void *arg UNUSED, struct reftable_record *rec UNUSED) +static int empty_iterator_next(void *arg REFTABLE_UNUSED, struct reftable_record *rec REFTABLE_UNUSED) { return 1; } -static void empty_iterator_close(void *arg UNUSED) +static void empty_iterator_close(void *arg REFTABLE_UNUSED) { } @@ -143,11 +143,10 @@ static int indexed_table_ref_iter_next_block(struct indexed_table_ref_iter *it) return 0; } -static int indexed_table_ref_iter_seek(void *p UNUSED, - struct reftable_record *want UNUSED) +static int indexed_table_ref_iter_seek(void *p REFTABLE_UNUSED, + struct reftable_record *want REFTABLE_UNUSED) { - BUG("seeking indexed table is not supported"); - return -1; + return REFTABLE_API_ERROR; } static int indexed_table_ref_iter_next(void *p, struct reftable_record *rec) diff --git a/reftable/merged.c b/reftable/merged.c index e72b39e178..4ff1553772 100644 --- a/reftable/merged.c +++ b/reftable/merged.c @@ -66,8 +66,11 @@ static int merged_iter_seek(struct merged_iter *mi, struct reftable_record *want int err; mi->advance_index = -1; - while (!merged_iter_pqueue_is_empty(mi->pq)) - merged_iter_pqueue_remove(&mi->pq); + while (!merged_iter_pqueue_is_empty(mi->pq)) { + err = merged_iter_pqueue_remove(&mi->pq, NULL); + if (err < 0) + return err; + } for (size_t i = 0; i < mi->subiters_len; i++) { err = iterator_seek(&mi->subiters[i].iter, want); @@ -120,7 +123,9 @@ static int merged_iter_next_entry(struct merged_iter *mi, if (empty) return 1; - entry = merged_iter_pqueue_remove(&mi->pq); + err = merged_iter_pqueue_remove(&mi->pq, &entry); + if (err < 0) + return err; /* One can also use reftable as datacenter-local storage, where the ref @@ -134,18 +139,23 @@ static int merged_iter_next_entry(struct merged_iter *mi, struct pq_entry top = merged_iter_pqueue_top(mi->pq); int cmp; - cmp = reftable_record_cmp(top.rec, entry.rec); + err = reftable_record_cmp(top.rec, entry.rec, &cmp); + if (err < 0) + return err; if (cmp > 0) break; - merged_iter_pqueue_remove(&mi->pq); + err = merged_iter_pqueue_remove(&mi->pq, NULL); + if (err < 0) + return err; + err = merged_iter_advance_subiter(mi, top.index); if (err < 0) return err; } mi->advance_index = entry.index; - SWAP(*rec, *entry.rec); + REFTABLE_SWAP(*rec, *entry.rec); return 0; } @@ -253,7 +263,10 @@ int merged_table_init_iter(struct reftable_merged_table *mt, } for (size_t i = 0; i < mt->readers_len; i++) { - reftable_record_init(&subiters[i].rec, typ); + ret = reftable_record_init(&subiters[i].rec, typ); + if (ret < 0) + goto out; + ret = reader_init_iter(mt->readers[i], &subiters[i].iter, typ); if (ret < 0) goto out; diff --git a/reftable/pq.c b/reftable/pq.c index 5591e875e1..82394a972d 100644 --- a/reftable/pq.c +++ b/reftable/pq.c @@ -15,13 +15,18 @@ https://developers.google.com/open-source/licenses/bsd int pq_less(struct pq_entry *a, struct pq_entry *b) { - int cmp = reftable_record_cmp(a->rec, b->rec); + int cmp, err; + + err = reftable_record_cmp(a->rec, b->rec, &cmp); + if (err < 0) + return err; + if (cmp == 0) return a->index > b->index; return cmp < 0; } -struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq) +int merged_iter_pqueue_remove(struct merged_iter_pqueue *pq, struct pq_entry *out) { size_t i = 0; struct pq_entry e = pq->heap[0]; @@ -32,17 +37,34 @@ struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq) size_t min = i; size_t j = 2 * i + 1; size_t k = 2 * i + 2; - if (j < pq->len && pq_less(&pq->heap[j], &pq->heap[i])) - min = j; - if (k < pq->len && pq_less(&pq->heap[k], &pq->heap[min])) - min = k; + int cmp; + + if (j < pq->len) { + cmp = pq_less(&pq->heap[j], &pq->heap[i]); + if (cmp < 0) + return -1; + else if (cmp) + min = j; + } + + if (k < pq->len) { + cmp = pq_less(&pq->heap[k], &pq->heap[min]); + if (cmp < 0) + return -1; + else if (cmp) + min = k; + } + if (min == i) break; - SWAP(pq->heap[i], pq->heap[min]); + REFTABLE_SWAP(pq->heap[i], pq->heap[min]); i = min; } - return e; + if (out) + *out = e; + + return 0; } int merged_iter_pqueue_add(struct merged_iter_pqueue *pq, const struct pq_entry *e) @@ -59,7 +81,7 @@ int merged_iter_pqueue_add(struct merged_iter_pqueue *pq, const struct pq_entry size_t j = (i - 1) / 2; if (pq_less(&pq->heap[j], &pq->heap[i])) break; - SWAP(pq->heap[j], pq->heap[i]); + REFTABLE_SWAP(pq->heap[j], pq->heap[i]); i = j; } diff --git a/reftable/pq.h b/reftable/pq.h index 83c062eeca..ff39016445 100644 --- a/reftable/pq.h +++ b/reftable/pq.h @@ -22,7 +22,7 @@ struct merged_iter_pqueue { size_t cap; }; -struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq); +int merged_iter_pqueue_remove(struct merged_iter_pqueue *pq, struct pq_entry *out); int merged_iter_pqueue_add(struct merged_iter_pqueue *pq, const struct pq_entry *e); void merged_iter_pqueue_release(struct merged_iter_pqueue *pq); int pq_less(struct pq_entry *a, struct pq_entry *b); diff --git a/reftable/reader.c b/reftable/reader.c index 24bae50ac2..172aff2c10 100644 --- a/reftable/reader.c +++ b/reftable/reader.c @@ -101,18 +101,18 @@ static int parse_footer(struct reftable_reader *r, uint8_t *footer, } f++; - r->block_size = get_be24(f); + r->block_size = reftable_get_be24(f); f += 3; - r->min_update_index = get_be64(f); + r->min_update_index = reftable_get_be64(f); f += 8; - r->max_update_index = get_be64(f); + r->max_update_index = reftable_get_be64(f); f += 8; if (r->version == 1) { r->hash_id = REFTABLE_HASH_SHA1; } else { - switch (get_be32(f)) { + switch (reftable_get_be32(f)) { case REFTABLE_FORMAT_ID_SHA1: r->hash_id = REFTABLE_HASH_SHA1; break; @@ -127,24 +127,24 @@ static int parse_footer(struct reftable_reader *r, uint8_t *footer, f += 4; } - r->ref_offsets.index_offset = get_be64(f); + r->ref_offsets.index_offset = reftable_get_be64(f); f += 8; - r->obj_offsets.offset = get_be64(f); + r->obj_offsets.offset = reftable_get_be64(f); f += 8; r->object_id_len = r->obj_offsets.offset & ((1 << 5) - 1); r->obj_offsets.offset >>= 5; - r->obj_offsets.index_offset = get_be64(f); + r->obj_offsets.index_offset = reftable_get_be64(f); f += 8; - r->log_offsets.offset = get_be64(f); + r->log_offsets.offset = reftable_get_be64(f); f += 8; - r->log_offsets.index_offset = get_be64(f); + r->log_offsets.index_offset = reftable_get_be64(f); f += 8; computed_crc = crc32(0, footer, f - footer); - file_crc = get_be32(f); + file_crc = reftable_get_be32(f); f += 4; if (computed_crc != file_crc) { err = REFTABLE_FORMAT_ERROR; @@ -214,7 +214,7 @@ static int32_t extract_block_size(uint8_t *data, uint8_t *typ, uint64_t off, *typ = data[0]; if (reftable_is_block_type(*typ)) { - result = get_be24(data + 1); + result = reftable_get_be24(data + 1); } return result; } @@ -360,7 +360,10 @@ static int table_iter_seek_linear(struct table_iter *ti, struct reftable_record rec; int err; - reftable_record_init(&rec, reftable_record_type(want)); + err = reftable_record_init(&rec, reftable_record_type(want)); + if (err < 0) + goto done; + err = reftable_record_key(want, &want_key); if (err < 0) goto done; @@ -676,8 +679,6 @@ done: void reftable_reader_incref(struct reftable_reader *r) { - if (!r->refcount) - BUG("cannot increment ref counter of dead reader"); r->refcount++; } @@ -685,8 +686,6 @@ void reftable_reader_decref(struct reftable_reader *r) { if (!r) return; - if (!r->refcount) - BUG("cannot decrement ref counter of dead reader"); if (--r->refcount) return; block_source_close(&r->source); @@ -852,7 +851,7 @@ int reftable_reader_print_blocks(const char *tablename) printf("header:\n"); printf(" block_size: %d\n", r->block_size); - for (i = 0; i < ARRAY_SIZE(sections); i++) { + for (i = 0; i < sizeof(sections) / sizeof(*sections); i++) { err = table_iter_seek_start(&ti, sections[i].type, 0); if (err < 0) goto done; diff --git a/reftable/record.c b/reftable/record.c index 8919df8a4d..c0080024ed 100644 --- a/reftable/record.c +++ b/reftable/record.c @@ -61,7 +61,7 @@ int put_var_int(struct string_view *dest, uint64_t value) while (value >>= 7) varint[--pos] = 0x80 | (--value & 0x7f); if (dest->len < sizeof(varint) - pos) - return -1; + return REFTABLE_ENTRY_TOO_BIG_ERROR; memcpy(dest->buf, varint + pos, sizeof(varint) - pos); return sizeof(varint) - pos; } @@ -129,10 +129,10 @@ static int encode_string(const char *str, struct string_view s) size_t l = strlen(str); int n = put_var_int(&s, l); if (n < 0) - return -1; + return n; string_view_consume(&s, n); if (s.len < l) - return -1; + return REFTABLE_ENTRY_TOO_BIG_ERROR; memcpy(s.buf, str, l); string_view_consume(&s, l); @@ -148,18 +148,18 @@ int reftable_encode_key(int *restart, struct string_view dest, uint64_t suffix_len = key.len - prefix_len; int n = put_var_int(&dest, prefix_len); if (n < 0) - return -1; + return n; string_view_consume(&dest, n); *restart = (prefix_len == 0); n = put_var_int(&dest, suffix_len << 3 | (uint64_t)extra); if (n < 0) - return -1; + return n; string_view_consume(&dest, n); if (dest.len < suffix_len) - return -1; + return REFTABLE_ENTRY_TOO_BIG_ERROR; memcpy(dest.buf, key.buf + prefix_len, suffix_len); string_view_consume(&dest, suffix_len); @@ -237,11 +237,11 @@ static int reftable_ref_record_copy_from(void *rec, const void *src_rec, size_t refname_cap = 0; int err; - SWAP(refname, ref->refname); - SWAP(refname_cap, ref->refname_cap); + REFTABLE_SWAP(refname, ref->refname); + REFTABLE_SWAP(refname_cap, ref->refname_cap); reftable_ref_record_release(ref); - SWAP(ref->refname, refname); - SWAP(ref->refname_cap, refname_cap); + REFTABLE_SWAP(ref->refname, refname); + REFTABLE_SWAP(ref->refname_cap, refname_cap); if (src->refname) { size_t refname_len = strlen(src->refname); @@ -324,30 +324,27 @@ static int reftable_ref_record_encode(const void *rec, struct string_view s, struct string_view start = s; int n = put_var_int(&s, r->update_index); if (n < 0) - return -1; + return n; string_view_consume(&s, n); switch (r->value_type) { case REFTABLE_REF_SYMREF: n = encode_string(r->value.symref, s); - if (n < 0) { - return -1; - } + if (n < 0) + return n; string_view_consume(&s, n); break; case REFTABLE_REF_VAL2: - if (s.len < 2 * hash_size) { - return -1; - } + if (s.len < 2 * hash_size) + return REFTABLE_ENTRY_TOO_BIG_ERROR; memcpy(s.buf, r->value.val2.value, hash_size); string_view_consume(&s, hash_size); memcpy(s.buf, r->value.val2.target_value, hash_size); string_view_consume(&s, hash_size); break; case REFTABLE_REF_VAL1: - if (s.len < hash_size) { - return -1; - } + if (s.len < hash_size) + return REFTABLE_ENTRY_TOO_BIG_ERROR; memcpy(s.buf, r->value.val1, hash_size); string_view_consume(&s, hash_size); break; @@ -376,11 +373,11 @@ static int reftable_ref_record_decode(void *rec, struct reftable_buf key, return n; string_view_consume(&in, n); - SWAP(refname, r->refname); - SWAP(refname_cap, r->refname_cap); + REFTABLE_SWAP(refname, r->refname); + REFTABLE_SWAP(refname_cap, r->refname_cap); reftable_ref_record_release(r); - SWAP(r->refname, refname); - SWAP(r->refname_cap, refname_cap); + REFTABLE_SWAP(r->refname, refname); + REFTABLE_SWAP(r->refname_cap, refname_cap); REFTABLE_ALLOC_GROW_OR_NULL(r->refname, key.len + 1, r->refname_cap); if (!r->refname) { @@ -490,7 +487,7 @@ static void reftable_obj_record_release(void *rec) } static int reftable_obj_record_copy_from(void *rec, const void *src_rec, - uint32_t hash_size UNUSED) + uint32_t hash_size REFTABLE_UNUSED) { struct reftable_obj_record *obj = rec; const struct reftable_obj_record *src = src_rec; @@ -504,11 +501,17 @@ static int reftable_obj_record_copy_from(void *rec, const void *src_rec, if (src->hash_prefix_len) memcpy(obj->hash_prefix, src->hash_prefix, obj->hash_prefix_len); - REFTABLE_ALLOC_ARRAY(obj->offsets, src->offset_len); - if (!obj->offsets) - return REFTABLE_OUT_OF_MEMORY_ERROR; - obj->offset_len = src->offset_len; - COPY_ARRAY(obj->offsets, src->offsets, src->offset_len); + if (src->offset_len) { + if (sizeof(*src->offsets) > SIZE_MAX / src->offset_len) + return REFTABLE_OUT_OF_MEMORY_ERROR; + + REFTABLE_ALLOC_ARRAY(obj->offsets, src->offset_len); + if (!obj->offsets) + return REFTABLE_OUT_OF_MEMORY_ERROR; + + memcpy(obj->offsets, src->offsets, sizeof(*src->offsets) * src->offset_len); + obj->offset_len = src->offset_len; + } return 0; } @@ -522,7 +525,7 @@ static uint8_t reftable_obj_record_val_type(const void *rec) } static int reftable_obj_record_encode(const void *rec, struct string_view s, - uint32_t hash_size UNUSED) + uint32_t hash_size REFTABLE_UNUSED) { const struct reftable_obj_record *r = rec; struct string_view start = s; @@ -531,24 +534,22 @@ static int reftable_obj_record_encode(const void *rec, struct string_view s, uint64_t last = 0; if (r->offset_len == 0 || r->offset_len >= 8) { n = put_var_int(&s, r->offset_len); - if (n < 0) { - return -1; - } + if (n < 0) + return n; string_view_consume(&s, n); } if (r->offset_len == 0) return start.len - s.len; n = put_var_int(&s, r->offsets[0]); if (n < 0) - return -1; + return n; string_view_consume(&s, n); last = r->offsets[0]; for (i = 1; i < r->offset_len; i++) { int n = put_var_int(&s, r->offsets[i] - last); - if (n < 0) { - return -1; - } + if (n < 0) + return n; string_view_consume(&s, n); last = r->offsets[i]; } @@ -557,8 +558,8 @@ static int reftable_obj_record_encode(const void *rec, struct string_view s, static int reftable_obj_record_decode(void *rec, struct reftable_buf key, uint8_t val_type, struct string_view in, - uint32_t hash_size UNUSED, - struct reftable_buf *scratch UNUSED) + uint32_t hash_size REFTABLE_UNUSED, + struct reftable_buf *scratch REFTABLE_UNUSED) { struct string_view start = in; struct reftable_obj_record *r = rec; @@ -612,13 +613,13 @@ static int reftable_obj_record_decode(void *rec, struct reftable_buf key, return start.len - in.len; } -static int not_a_deletion(const void *p UNUSED) +static int not_a_deletion(const void *p REFTABLE_UNUSED) { return 0; } static int reftable_obj_record_equal_void(const void *a, const void *b, - uint32_t hash_size UNUSED) + uint32_t hash_size REFTABLE_UNUSED) { struct reftable_obj_record *ra = (struct reftable_obj_record *) a; struct reftable_obj_record *rb = (struct reftable_obj_record *) b; @@ -683,7 +684,7 @@ static int reftable_log_record_key(const void *r, struct reftable_buf *dest) return err; ts = (~ts) - rec->update_index; - put_be64(&i64[0], ts); + reftable_put_be64(&i64[0], ts); err = reftable_buf_add(dest, i64, sizeof(i64)); if (err < 0) @@ -783,7 +784,7 @@ static int reftable_log_record_encode(const void *rec, struct string_view s, return 0; if (s.len < 2 * hash_size) - return -1; + return REFTABLE_ENTRY_TOO_BIG_ERROR; memcpy(s.buf, r->value.update.old_hash, hash_size); memcpy(s.buf + hash_size, r->value.update.new_hash, hash_size); @@ -791,30 +792,30 @@ static int reftable_log_record_encode(const void *rec, struct string_view s, n = encode_string(r->value.update.name ? r->value.update.name : "", s); if (n < 0) - return -1; + return n; string_view_consume(&s, n); n = encode_string(r->value.update.email ? r->value.update.email : "", s); if (n < 0) - return -1; + return n; string_view_consume(&s, n); n = put_var_int(&s, r->value.update.time); if (n < 0) - return -1; + return n; string_view_consume(&s, n); if (s.len < 2) - return -1; + return REFTABLE_ENTRY_TOO_BIG_ERROR; - put_be16(s.buf, r->value.update.tz_offset); + reftable_put_be16(s.buf, r->value.update.tz_offset); string_view_consume(&s, 2); n = encode_string( r->value.update.message ? r->value.update.message : "", s); if (n < 0) - return -1; + return n; string_view_consume(&s, n); return start.len - s.len; @@ -840,7 +841,7 @@ static int reftable_log_record_decode(void *rec, struct reftable_buf key, } memcpy(r->refname, key.buf, key.len - 8); - ts = get_be64(key.buf + key.len - 8); + ts = reftable_get_be64((unsigned char *)key.buf + key.len - 8); r->update_index = (~max) - ts; @@ -931,7 +932,7 @@ static int reftable_log_record_decode(void *rec, struct reftable_buf key, goto done; } - r->value.update.tz_offset = get_be16(in.buf); + r->value.update.tz_offset = reftable_get_be16(in.buf); string_view_consume(&in, 2); n = decode_string(scratch, in); @@ -1048,7 +1049,7 @@ static int reftable_index_record_key(const void *r, struct reftable_buf *dest) } static int reftable_index_record_copy_from(void *rec, const void *src_rec, - uint32_t hash_size UNUSED) + uint32_t hash_size REFTABLE_UNUSED) { struct reftable_index_record *dst = rec; const struct reftable_index_record *src = src_rec; @@ -1069,13 +1070,13 @@ static void reftable_index_record_release(void *rec) reftable_buf_release(&idx->last_key); } -static uint8_t reftable_index_record_val_type(const void *rec UNUSED) +static uint8_t reftable_index_record_val_type(const void *rec REFTABLE_UNUSED) { return 0; } static int reftable_index_record_encode(const void *rec, struct string_view out, - uint32_t hash_size UNUSED) + uint32_t hash_size REFTABLE_UNUSED) { const struct reftable_index_record *r = (const struct reftable_index_record *)rec; @@ -1091,10 +1092,10 @@ static int reftable_index_record_encode(const void *rec, struct string_view out, } static int reftable_index_record_decode(void *rec, struct reftable_buf key, - uint8_t val_type UNUSED, + uint8_t val_type REFTABLE_UNUSED, struct string_view in, - uint32_t hash_size UNUSED, - struct reftable_buf *scratch UNUSED) + uint32_t hash_size REFTABLE_UNUSED, + struct reftable_buf *scratch REFTABLE_UNUSED) { struct string_view start = in; struct reftable_index_record *r = rec; @@ -1114,7 +1115,7 @@ static int reftable_index_record_decode(void *rec, struct reftable_buf key, } static int reftable_index_record_equal(const void *a, const void *b, - uint32_t hash_size UNUSED) + uint32_t hash_size REFTABLE_UNUSED) { struct reftable_index_record *ia = (struct reftable_index_record *) a; struct reftable_index_record *ib = (struct reftable_index_record *) b; @@ -1189,12 +1190,14 @@ int reftable_record_is_deletion(struct reftable_record *rec) reftable_record_data(rec)); } -int reftable_record_cmp(struct reftable_record *a, struct reftable_record *b) +int reftable_record_cmp(struct reftable_record *a, struct reftable_record *b, + int *cmp) { if (a->type != b->type) - BUG("cannot compare reftable records of different type"); - return reftable_record_vtable(a)->cmp( - reftable_record_data(a), reftable_record_data(b)); + return -1; + *cmp = reftable_record_vtable(a)->cmp(reftable_record_data(a), + reftable_record_data(b)); + return 0; } int reftable_record_equal(struct reftable_record *a, struct reftable_record *b, uint32_t hash_size) @@ -1300,7 +1303,7 @@ reftable_record_vtable(struct reftable_record *rec) abort(); } -void reftable_record_init(struct reftable_record *rec, uint8_t typ) +int reftable_record_init(struct reftable_record *rec, uint8_t typ) { memset(rec, 0, sizeof(*rec)); rec->type = typ; @@ -1309,11 +1312,11 @@ void reftable_record_init(struct reftable_record *rec, uint8_t typ) case BLOCK_TYPE_REF: case BLOCK_TYPE_LOG: case BLOCK_TYPE_OBJ: - return; + return 0; case BLOCK_TYPE_INDEX: reftable_buf_init(&rec->u.idx.last_key); - return; + return 0; default: - BUG("unhandled record type"); + return REFTABLE_API_ERROR; } } diff --git a/reftable/record.h b/reftable/record.h index c7755a4d75..867810a932 100644 --- a/reftable/record.h +++ b/reftable/record.h @@ -130,11 +130,11 @@ struct reftable_record { } u; }; -/* Initialize the reftable record for the given type */ -void reftable_record_init(struct reftable_record *rec, uint8_t typ); +/* Initialize the reftable record for the given type. */ +int reftable_record_init(struct reftable_record *rec, uint8_t typ); /* see struct record_vtable */ -int reftable_record_cmp(struct reftable_record *a, struct reftable_record *b); +int reftable_record_cmp(struct reftable_record *a, struct reftable_record *b, int *cmp); int reftable_record_equal(struct reftable_record *a, struct reftable_record *b, uint32_t hash_size); int reftable_record_key(struct reftable_record *rec, struct reftable_buf *dest); int reftable_record_copy_from(struct reftable_record *rec, diff --git a/reftable/stack.c b/reftable/stack.c index 6c4e8be19b..6dac015b47 100644 --- a/reftable/stack.c +++ b/reftable/stack.c @@ -48,6 +48,25 @@ static int stack_fsync(const struct reftable_write_options *opts, int fd) return fsync(fd); } +static ssize_t reftable_write_data(int fd, const void *data, size_t size) +{ + size_t total_written = 0; + const char *p = data; + + while (total_written < size) { + ssize_t bytes_written = write(fd, p, size - total_written); + if (bytes_written < 0 && (errno == EAGAIN || errno == EINTR)) + continue; + if (bytes_written < 0) + return REFTABLE_IO_ERROR; + + total_written += bytes_written; + p += bytes_written; + } + + return total_written; +} + struct fd_writer { const struct reftable_write_options *opts; int fd; @@ -56,7 +75,7 @@ struct fd_writer { static ssize_t fd_writer_write(void *arg, const void *data, size_t sz) { struct fd_writer *writer = arg; - return write_in_full(writer->fd, data, sz); + return reftable_write_data(writer->fd, data, sz); } static int fd_writer_flush(void *arg) @@ -115,13 +134,16 @@ out: static int fd_read_lines(int fd, char ***namesp) { - off_t size = lseek(fd, 0, SEEK_END); char *buf = NULL; int err = 0; + off_t size; + + size = lseek(fd, 0, SEEK_END); if (size < 0) { err = REFTABLE_IO_ERROR; goto done; } + err = lseek(fd, 0, SEEK_SET); if (err < 0) { err = REFTABLE_IO_ERROR; @@ -134,9 +156,16 @@ static int fd_read_lines(int fd, char ***namesp) goto done; } - if (read_in_full(fd, buf, size) != size) { - err = REFTABLE_IO_ERROR; - goto done; + for (off_t total_read = 0; total_read < size; ) { + ssize_t bytes_read = read(fd, buf + total_read, size - total_read); + if (bytes_read < 0 && (errno == EAGAIN || errno == EINTR)) + continue; + if (bytes_read < 0 || !bytes_read) { + err = REFTABLE_IO_ERROR; + goto done; + } + + total_read += bytes_read; } buf[size] = 0; @@ -494,8 +523,8 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st, close(fd); fd = -1; - delay = delay + (delay * git_rand(CSPRNG_BYTES_INSECURE)) / UINT32_MAX + 1; - sleep_millisec(delay); + delay = delay + (delay * reftable_rand()) / UINT32_MAX + 1; + poll(NULL, 0, delay); } out: @@ -659,7 +688,7 @@ int reftable_stack_add(struct reftable_stack *st, static int format_name(struct reftable_buf *dest, uint64_t min, uint64_t max) { char buf[100]; - uint32_t rnd = git_rand(CSPRNG_BYTES_INSECURE); + uint32_t rnd = reftable_rand(); snprintf(buf, sizeof(buf), "0x%012" PRIx64 "-0x%012" PRIx64 "-%08x", min, max, rnd); reftable_buf_reset(dest); @@ -774,7 +803,8 @@ int reftable_addition_commit(struct reftable_addition *add) goto done; } - err = write_in_full(add->tables_list_lock.fd, table_list.buf, table_list.len); + err = reftable_write_data(add->tables_list_lock.fd, + table_list.buf, table_list.len); reftable_buf_release(&table_list); if (err < 0) { err = REFTABLE_IO_ERROR; @@ -1460,8 +1490,8 @@ static int stack_compact_range(struct reftable_stack *st, goto done; } - err = write_in_full(tables_list_lock.fd, - tables_list_buf.buf, tables_list_buf.len); + err = reftable_write_data(tables_list_lock.fd, + tables_list_buf.buf, tables_list_buf.len); if (err < 0) { err = REFTABLE_IO_ERROR; unlink(new_table_path.buf); diff --git a/reftable/system.c b/reftable/system.c index adf8e4d30b..1ee268b125 100644 --- a/reftable/system.c +++ b/reftable/system.c @@ -1,9 +1,16 @@ +#include "../git-compat-util.h" + #include "system.h" #include "basics.h" #include "reftable-error.h" #include "../lockfile.h" #include "../tempfile.h" +uint32_t reftable_rand(void) +{ + return git_rand(CSPRNG_BYTES_INSECURE); +} + int tmpfile_from_pattern(struct reftable_tmpfile *out, const char *pattern) { struct tempfile *tempfile; diff --git a/reftable/system.h b/reftable/system.h index d02eacea8f..072d9daea0 100644 --- a/reftable/system.h +++ b/reftable/system.h @@ -11,10 +11,17 @@ https://developers.google.com/open-source/licenses/bsd /* This header glues the reftable library to the rest of Git */ -#include "git-compat-util.h" +#define MINGW_DONT_HANDLE_IN_USE_ERROR +#include "compat/posix.h" #include "compat/zlib-compat.h" /* + * Return a random 32 bit integer. This function is expected to return + * pre-seeded data. + */ +uint32_t reftable_rand(void); + +/* * An implementation-specific temporary file. By making this specific to the * implementation it becomes possible to tie temporary files into any kind of * signal or atexit handlers for cleanup on abnormal situations. diff --git a/reftable/writer.c b/reftable/writer.c index f3ab1035d6..075ea8661b 100644 --- a/reftable/writer.c +++ b/reftable/writer.c @@ -99,9 +99,9 @@ static int writer_write_header(struct reftable_writer *w, uint8_t *dest) dest[4] = writer_version(w); - put_be24(dest + 5, w->opts.block_size); - put_be64(dest + 8, w->min_update_index); - put_be64(dest + 16, w->max_update_index); + reftable_put_be24(dest + 5, w->opts.block_size); + reftable_put_be64(dest + 8, w->min_update_index); + reftable_put_be64(dest + 16, w->max_update_index); if (writer_version(w) == 2) { uint32_t hash_id; @@ -116,7 +116,7 @@ static int writer_write_header(struct reftable_writer *w, uint8_t *dest) return -1; } - put_be32(dest + 24, hash_id); + reftable_put_be32(dest + 24, hash_id); } return header_size(writer_version(w)); @@ -158,7 +158,7 @@ int reftable_writer_new(struct reftable_writer **out, opts = *_opts; options_set_defaults(&opts); if (opts.block_size >= (1 << 24)) - BUG("configured block size exceeds 16MB"); + return REFTABLE_API_ERROR; reftable_buf_init(&wp->block_writer_data.last_key); reftable_buf_init(&wp->last_key); @@ -302,19 +302,19 @@ static int writer_add_record(struct reftable_writer *w, } if (block_writer_type(w->block_writer) != reftable_record_type(rec)) - BUG("record of type %d added to writer of type %d", - reftable_record_type(rec), block_writer_type(w->block_writer)); + return REFTABLE_API_ERROR; /* * Try to add the record to the writer. If this succeeds then we're * done. Otherwise the block writer may have hit the block size limit * and needs to be flushed. */ - if (!block_writer_add(w->block_writer, rec)) { - err = 0; + err = block_writer_add(w->block_writer, rec); + if (err == 0) goto done; - } + if (err != REFTABLE_ENTRY_TOO_BIG_ERROR) + goto done; /* * The current block is full, so we need to flush and reinitialize the * writer to start writing the next block. @@ -329,16 +329,10 @@ static int writer_add_record(struct reftable_writer *w, /* * Try to add the record to the writer again. If this still fails then * the record does not fit into the block size. - * - * TODO: it would be great to have `block_writer_add()` return proper - * error codes so that we don't have to second-guess the failure - * mode here. */ err = block_writer_add(w->block_writer, rec); - if (err) { - err = REFTABLE_ENTRY_TOO_BIG_ERROR; + if (err) goto done; - } done: return err; @@ -625,10 +619,22 @@ static void write_object_record(void *void_arg, void *key) if (arg->err < 0) goto done; + /* + * Try to add the record to the writer. If this succeeds then we're + * done. Otherwise the block writer may have hit the block size limit + * and needs to be flushed. + */ arg->err = block_writer_add(arg->w->block_writer, &rec); if (arg->err == 0) goto done; + if (arg->err != REFTABLE_ENTRY_TOO_BIG_ERROR) + goto done; + + /* + * The current block is full, so we need to flush and reinitialize the + * writer to start writing the next block. + */ arg->err = writer_flush_block(arg->w); if (arg->err < 0) goto done; @@ -637,10 +643,17 @@ static void write_object_record(void *void_arg, void *key) if (arg->err < 0) goto done; + /* + * If this still fails then we may need to reset record's offset + * length to reduce the data size to be written. + */ arg->err = block_writer_add(arg->w->block_writer, &rec); if (arg->err == 0) goto done; + if (arg->err != REFTABLE_ENTRY_TOO_BIG_ERROR) + goto done; + rec.u.obj.offset_len = 0; arg->err = block_writer_add(arg->w->block_writer, &rec); @@ -650,7 +663,7 @@ static void write_object_record(void *void_arg, void *key) done:; } -static void object_record_free(void *void_arg UNUSED, void *key) +static void object_record_free(void *void_arg REFTABLE_UNUSED, void *key) { struct obj_index_tree_node *entry = key; @@ -731,19 +744,19 @@ int reftable_writer_close(struct reftable_writer *w) } p += writer_write_header(w, footer); - put_be64(p, w->stats.ref_stats.index_offset); + reftable_put_be64(p, w->stats.ref_stats.index_offset); p += 8; - put_be64(p, (w->stats.obj_stats.offset) << 5 | w->stats.object_id_len); + reftable_put_be64(p, (w->stats.obj_stats.offset) << 5 | w->stats.object_id_len); p += 8; - put_be64(p, w->stats.obj_stats.index_offset); + reftable_put_be64(p, w->stats.obj_stats.index_offset); p += 8; - put_be64(p, w->stats.log_stats.offset); + reftable_put_be64(p, w->stats.log_stats.offset); p += 8; - put_be64(p, w->stats.log_stats.index_offset); + reftable_put_be64(p, w->stats.log_stats.index_offset); p += 8; - put_be32(p, crc32(0, footer, p - footer)); + reftable_put_be32(p, crc32(0, footer, p - footer)); p += 4; err = w->flush(w->write_arg); 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) @@ -143,8 +143,8 @@ static struct remote *make_remote(struct remote_state *remote_state, ret->prune = -1; /* unspecified */ ret->prune_tags = -1; /* unspecified */ ret->name = xstrndup(name, len); - refspec_init(&ret->push, REFSPEC_PUSH); - refspec_init(&ret->fetch, REFSPEC_FETCH); + refspec_init_push(&ret->push); + refspec_init_fetch(&ret->fetch); string_list_init_dup(&ret->server_options); ALLOC_GROW(remote_state->remotes, remote_state->remotes_nr + 1, @@ -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..b536c4a29a 100644 --- a/revision.c +++ b/revision.c @@ -3612,7 +3612,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/run-command.c b/run-command.c index 402138b8b5..8833b23367 100644 --- a/run-command.c +++ b/run-command.c @@ -515,7 +515,13 @@ static void atfork_prepare(struct atfork_state *as) { sigset_t all; - if (sigfillset(&all)) + /* + * POSIX says sigfillset() can fail, but an overly clever + * compiler can see through the header files and decide + * it cannot fail on a particular platform it is compiling for, + * triggering -Wunreachable-code false positive. + */ + if (NOT_CONSTANT(sigfillset(&all))) die_errno("sigfillset"); #ifdef NO_PTHREADS if (sigprocmask(SIG_SETMASK, &all, &as->old)) @@ -241,7 +241,7 @@ static int add_or_remove_enlistment(int add) static int start_fsmonitor_daemon(void) { - assert(have_fsmonitor_support()); + ASSERT(have_fsmonitor_support()); if (fsmonitor_ipc__get_state() != IPC_STATE__LISTENING) return run_git("fsmonitor--daemon", "start", NULL); @@ -251,7 +251,7 @@ static int start_fsmonitor_daemon(void) static int stop_fsmonitor_daemon(void) { - assert(have_fsmonitor_support()); + ASSERT(have_fsmonitor_support()); if (fsmonitor_ipc__get_state() == IPC_STATE__LISTENING) return run_git("fsmonitor--daemon", "stop", NULL); diff --git a/sequencer.c b/sequencer.c index ad0ab75c8d..e750ed55de 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); @@ -1301,7 +1301,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; @@ -4683,7 +4683,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); @@ -4965,7 +4965,7 @@ static int pick_commits(struct repository *r, ctx->reflog_message = sequencer_reflog_action(opts); if (opts->allow_ff) - assert(!(opts->signoff || opts->no_commit || + ASSERT(!(opts->signoff || opts->no_commit || opts->record_origin || should_edit(opts) || opts->committer_date_is_author_date || opts->ignore_date)); @@ -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; @@ -818,7 +818,7 @@ Skipping tests -------------- If you need to skip tests you should do so by using the three-arg form -of the test_* functions (see the "Test harness library" section +of the test_expect_* functions (see the "Test harness library" section below), e.g.: test_expect_success PERL 'I need Perl' ' @@ -965,6 +965,29 @@ see test-lib-functions.sh for the full list and their options. test_done fi + - test_lazy_prereq <prereq> <script> + + Declare the way to determine if a test prerequisite <prereq> is + satisified or not, but delay the actual determination until the + prerequisite is actually used by "test_have_prereq" or the + three-arg form of the test_expect_* functions. For example, this + is how the SYMLINKS prerequisite is declared to see if the platform + supports symbolic links: + + test_lazy_prereq SYMLINKS ' + ln -s x y && test -h y + ' + + The script is lazily invoked when SYMLINKS prerequisite is first + queried by either "test_have_prereq SYMLINKS" or "test_expect_* + SYMLINKS ...". The script is run in a temporary directory inside + a subshell, so you do not have to worry about removing temporary + files you create there. When the script exits with status 0, the + prerequisite is set. Exiting with non-zero status other than 125 + makes the prerequisite unsatisified. Exiting the script with 125 + signals a programming error and is used to mark a prerequisite that + should not be used by test scripts. + - test_expect_code <exit-code> <command> Run a command and ensure that it exits with the given exit code. diff --git a/t/helper/test-dir-iterator.c b/t/helper/test-dir-iterator.c index 6b297bd753..8d46e8ba40 100644 --- a/t/helper/test-dir-iterator.c +++ b/t/helper/test-dir-iterator.c @@ -53,6 +53,7 @@ int cmd__dir_iterator(int argc, const char **argv) printf("(%s) [%s] %s\n", diter->relative_path, diter->basename, diter->path.buf); } + dir_iterator_free(diter); if (iter_status != ITER_DONE) { printf("dir_iterator_advance failure\n"); diff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c index 72ac8d1b1b..f3c59e5028 100644 --- a/t/helper/test-path-utils.c +++ b/t/helper/test-path-utils.c @@ -504,6 +504,25 @@ int cmd__path_utils(int argc, const char **argv) return !!res; } + if (argc > 1 && !strcmp(argv[1], "is_path_owned_by_current_user")) { + int res = 0; + + for (int i = 2; i < argc; i++) { + struct strbuf buf = STRBUF_INIT; + + if (is_path_owned_by_current_user(argv[i], &buf)) + printf("'%s' is owned by current SID\n", argv[i]); + else { + printf("'%s' is not owned by current SID: %s\n", argv[i], buf.buf); + res = 1; + } + + strbuf_release(&buf); + } + + return res; + } + fprintf(stderr, "%s: unknown function name: %s\n", argv[0], argv[1] ? argv[1] : "(there was none)"); return 1; 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-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/meson.build b/t/meson.build index a59da26be3..8b3aed14ea 100644 --- a/t/meson.build +++ b/t/meson.build @@ -500,6 +500,7 @@ integration_tests = [ 't4067-diff-partial-clone.sh', 't4068-diff-symmetric-merge-base.sh', 't4069-remerge-diff.sh', + 't4070-diff-pairs.sh', 't4100-apply-stat.sh', 't4101-apply-nonl.sh', 't4102-apply-rename.sh', 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/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh index d4a08b823b..9d1dc2144c 100755 --- a/t/t0602-reffiles-fsck.sh +++ b/t/t0602-reffiles-fsck.sh @@ -14,222 +14,229 @@ test_expect_success 'ref name should be checked' ' git init repo && branch_dir_prefix=.git/refs/heads && tag_dir_prefix=.git/refs/tags && - cd repo && - - git commit --allow-empty -m initial && - git checkout -b default-branch && - git tag default-tag && - git tag multi_hierarchy/default-tag && - - cp $branch_dir_prefix/default-branch $branch_dir_prefix/@ && - git refs verify 2>err && - test_must_be_empty err && - rm $branch_dir_prefix/@ && - - cp $tag_dir_prefix/default-tag $tag_dir_prefix/tag-1.lock && - git refs verify 2>err && - rm $tag_dir_prefix/tag-1.lock && - test_must_be_empty err && - - cp $tag_dir_prefix/default-tag $tag_dir_prefix/.lock && - test_must_fail git refs verify 2>err && - cat >expect <<-EOF && - error: refs/tags/.lock: badRefName: invalid refname format - EOF - rm $tag_dir_prefix/.lock && - test_cmp expect err && - - for refname in ".refname-starts-with-dot" "~refname-has-stride" - do - cp $branch_dir_prefix/default-branch "$branch_dir_prefix/$refname" && - test_must_fail git refs verify 2>err && - cat >expect <<-EOF && - error: refs/heads/$refname: badRefName: invalid refname format - EOF - rm "$branch_dir_prefix/$refname" && - test_cmp expect err || return 1 - done && + ( + cd repo && - for refname in ".refname-starts-with-dot" "~refname-has-stride" - do - cp $tag_dir_prefix/default-tag "$tag_dir_prefix/$refname" && - test_must_fail git refs verify 2>err && - cat >expect <<-EOF && - error: refs/tags/$refname: badRefName: invalid refname format - EOF - rm "$tag_dir_prefix/$refname" && - test_cmp expect err || return 1 - done && + git commit --allow-empty -m initial && + git checkout -b default-branch && + git tag default-tag && + git tag multi_hierarchy/default-tag && - for refname in ".refname-starts-with-dot" "~refname-has-stride" - do - cp $tag_dir_prefix/multi_hierarchy/default-tag "$tag_dir_prefix/multi_hierarchy/$refname" && - test_must_fail git refs verify 2>err && - cat >expect <<-EOF && - error: refs/tags/multi_hierarchy/$refname: badRefName: invalid refname format - EOF - rm "$tag_dir_prefix/multi_hierarchy/$refname" && - test_cmp expect err || return 1 - done && + cp $branch_dir_prefix/default-branch $branch_dir_prefix/@ && + git refs verify 2>err && + test_must_be_empty err && + rm $branch_dir_prefix/@ && + + cp $tag_dir_prefix/default-tag $tag_dir_prefix/tag-1.lock && + git refs verify 2>err && + rm $tag_dir_prefix/tag-1.lock && + test_must_be_empty err && - for refname in ".refname-starts-with-dot" "~refname-has-stride" - do - mkdir "$branch_dir_prefix/$refname" && - cp $branch_dir_prefix/default-branch "$branch_dir_prefix/$refname/default-branch" && + cp $tag_dir_prefix/default-tag $tag_dir_prefix/.lock && test_must_fail git refs verify 2>err && cat >expect <<-EOF && - error: refs/heads/$refname/default-branch: badRefName: invalid refname format + error: refs/tags/.lock: badRefName: invalid refname format EOF - rm -r "$branch_dir_prefix/$refname" && - test_cmp expect err || return 1 - done + rm $tag_dir_prefix/.lock && + test_cmp expect err && + + for refname in ".refname-starts-with-dot" "~refname-has-stride" + do + cp $branch_dir_prefix/default-branch "$branch_dir_prefix/$refname" && + test_must_fail git refs verify 2>err && + cat >expect <<-EOF && + error: refs/heads/$refname: badRefName: invalid refname format + EOF + rm "$branch_dir_prefix/$refname" && + test_cmp expect err || return 1 + done && + + for refname in ".refname-starts-with-dot" "~refname-has-stride" + do + cp $tag_dir_prefix/default-tag "$tag_dir_prefix/$refname" && + test_must_fail git refs verify 2>err && + cat >expect <<-EOF && + error: refs/tags/$refname: badRefName: invalid refname format + EOF + rm "$tag_dir_prefix/$refname" && + test_cmp expect err || return 1 + done && + + for refname in ".refname-starts-with-dot" "~refname-has-stride" + do + cp $tag_dir_prefix/multi_hierarchy/default-tag "$tag_dir_prefix/multi_hierarchy/$refname" && + test_must_fail git refs verify 2>err && + cat >expect <<-EOF && + error: refs/tags/multi_hierarchy/$refname: badRefName: invalid refname format + EOF + rm "$tag_dir_prefix/multi_hierarchy/$refname" && + test_cmp expect err || return 1 + done && + + for refname in ".refname-starts-with-dot" "~refname-has-stride" + do + mkdir "$branch_dir_prefix/$refname" && + cp $branch_dir_prefix/default-branch "$branch_dir_prefix/$refname/default-branch" && + test_must_fail git refs verify 2>err && + cat >expect <<-EOF && + error: refs/heads/$refname/default-branch: badRefName: invalid refname format + EOF + rm -r "$branch_dir_prefix/$refname" && + test_cmp expect err || return 1 + done + ) ' test_expect_success 'ref name check should be adapted into fsck messages' ' test_when_finished "rm -rf repo" && git init repo && branch_dir_prefix=.git/refs/heads && - cd repo && - git commit --allow-empty -m initial && - git checkout -b branch-1 && - - cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 && - git -c fsck.badRefName=warn refs verify 2>err && - cat >expect <<-EOF && - warning: refs/heads/.branch-1: badRefName: invalid refname format - EOF - rm $branch_dir_prefix/.branch-1 && - test_cmp expect err && - - cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 && - git -c fsck.badRefName=ignore refs verify 2>err && - test_must_be_empty err + ( + cd repo && + git commit --allow-empty -m initial && + git checkout -b branch-1 && + + cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 && + git -c fsck.badRefName=warn refs verify 2>err && + cat >expect <<-EOF && + warning: refs/heads/.branch-1: badRefName: invalid refname format + EOF + rm $branch_dir_prefix/.branch-1 && + test_cmp expect err && + + cp $branch_dir_prefix/branch-1 $branch_dir_prefix/.branch-1 && + git -c fsck.badRefName=ignore refs verify 2>err && + test_must_be_empty err + ) ' test_expect_success 'ref name check should work for multiple worktrees' ' test_when_finished "rm -rf repo" && git init repo && - - cd repo && - test_commit initial && - git checkout -b branch-1 && - test_commit second && - git checkout -b branch-2 && - test_commit third && - git checkout -b branch-3 && - git worktree add ./worktree-1 branch-1 && - git worktree add ./worktree-2 branch-2 && - worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree && - worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree && - ( - cd worktree-1 && - git update-ref refs/worktree/branch-4 refs/heads/branch-3 - ) && - ( - cd worktree-2 && - git update-ref refs/worktree/branch-4 refs/heads/branch-3 - ) && - - cp $worktree1_refdir_prefix/branch-4 $worktree1_refdir_prefix/'\'' branch-5'\'' && - cp $worktree2_refdir_prefix/branch-4 $worktree2_refdir_prefix/'\''~branch-6'\'' && - - test_must_fail git refs verify 2>err && - cat >expect <<-EOF && - error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format - error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format - EOF - sort err >sorted_err && - test_cmp expect sorted_err && - - for worktree in "worktree-1" "worktree-2" - do + cd repo && + test_commit initial && + git checkout -b branch-1 && + test_commit second && + git checkout -b branch-2 && + test_commit third && + git checkout -b branch-3 && + git worktree add ./worktree-1 branch-1 && + git worktree add ./worktree-2 branch-2 && + worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree && + worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree && + ( - cd $worktree && - test_must_fail git refs verify 2>err && - cat >expect <<-EOF && - error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format - error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format - EOF - sort err >sorted_err && - test_cmp expect sorted_err || return 1 - ) - done + cd worktree-1 && + git update-ref refs/worktree/branch-4 refs/heads/branch-3 + ) && + ( + cd worktree-2 && + git update-ref refs/worktree/branch-4 refs/heads/branch-3 + ) && + + cp $worktree1_refdir_prefix/branch-4 $worktree1_refdir_prefix/'\'' branch-5'\'' && + cp $worktree2_refdir_prefix/branch-4 $worktree2_refdir_prefix/'\''~branch-6'\'' && + + test_must_fail git refs verify 2>err && + cat >expect <<-EOF && + error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format + error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format + EOF + sort err >sorted_err && + test_cmp expect sorted_err && + + for worktree in "worktree-1" "worktree-2" + do + ( + cd $worktree && + test_must_fail git refs verify 2>err && + cat >expect <<-EOF && + error: worktrees/worktree-1/refs/worktree/ branch-5: badRefName: invalid refname format + error: worktrees/worktree-2/refs/worktree/~branch-6: badRefName: invalid refname format + EOF + sort err >sorted_err && + test_cmp expect sorted_err || return 1 + ) + done + ) ' test_expect_success 'regular ref content should be checked (individual)' ' test_when_finished "rm -rf repo" && git init repo && branch_dir_prefix=.git/refs/heads && - cd repo && - test_commit default && - mkdir -p "$branch_dir_prefix/a/b" && + ( + cd repo && + test_commit default && + mkdir -p "$branch_dir_prefix/a/b" && - git refs verify 2>err && - test_must_be_empty err && + git refs verify 2>err && + test_must_be_empty err && - for bad_content in "$(git rev-parse main)x" "xfsazqfxcadas" "Xfsazqfxcadas" - do - printf "%s" $bad_content >$branch_dir_prefix/branch-bad && - test_must_fail git refs verify 2>err && - cat >expect <<-EOF && - error: refs/heads/branch-bad: badRefContent: $bad_content - EOF - rm $branch_dir_prefix/branch-bad && - test_cmp expect err || return 1 - done && + for bad_content in "$(git rev-parse main)x" "xfsazqfxcadas" "Xfsazqfxcadas" + do + printf "%s" $bad_content >$branch_dir_prefix/branch-bad && + test_must_fail git refs verify 2>err && + cat >expect <<-EOF && + error: refs/heads/branch-bad: badRefContent: $bad_content + EOF + rm $branch_dir_prefix/branch-bad && + test_cmp expect err || return 1 + done && - for bad_content in "$(git rev-parse main)x" "xfsazqfxcadas" "Xfsazqfxcadas" - do - printf "%s" $bad_content >$branch_dir_prefix/a/b/branch-bad && - test_must_fail git refs verify 2>err && - cat >expect <<-EOF && - error: refs/heads/a/b/branch-bad: badRefContent: $bad_content - EOF - rm $branch_dir_prefix/a/b/branch-bad && - test_cmp expect err || return 1 - done && - - printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline && - git refs verify 2>err && - cat >expect <<-EOF && - warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end - EOF - rm $branch_dir_prefix/branch-no-newline && - test_cmp expect err && - - for trailing_content in " garbage" " more garbage" - do - printf "%s" "$(git rev-parse main)$trailing_content" >$branch_dir_prefix/branch-garbage && + for bad_content in "$(git rev-parse main)x" "xfsazqfxcadas" "Xfsazqfxcadas" + do + printf "%s" $bad_content >$branch_dir_prefix/a/b/branch-bad && + test_must_fail git refs verify 2>err && + cat >expect <<-EOF && + error: refs/heads/a/b/branch-bad: badRefContent: $bad_content + EOF + rm $branch_dir_prefix/a/b/branch-bad && + test_cmp expect err || return 1 + done && + + printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline && git refs verify 2>err && cat >expect <<-EOF && - warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\''$trailing_content'\'' + warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end EOF - rm $branch_dir_prefix/branch-garbage && - test_cmp expect err || return 1 - done && + rm $branch_dir_prefix/branch-no-newline && + test_cmp expect err && - printf "%s\n\n\n" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage-special && - git refs verify 2>err && - cat >expect <<-EOF && - warning: refs/heads/branch-garbage-special: trailingRefContent: has trailing garbage: '\'' + for trailing_content in " garbage" " more garbage" + do + printf "%s" "$(git rev-parse main)$trailing_content" >$branch_dir_prefix/branch-garbage && + git refs verify 2>err && + cat >expect <<-EOF && + warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\''$trailing_content'\'' + EOF + rm $branch_dir_prefix/branch-garbage && + test_cmp expect err || return 1 + done && + printf "%s\n\n\n" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage-special && + git refs verify 2>err && + cat >expect <<-EOF && + warning: refs/heads/branch-garbage-special: trailingRefContent: has trailing garbage: '\'' - '\'' - EOF - rm $branch_dir_prefix/branch-garbage-special && - test_cmp expect err && - printf "%s\n\n\n garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage-special && - git refs verify 2>err && - cat >expect <<-EOF && - warning: refs/heads/branch-garbage-special: trailingRefContent: has trailing garbage: '\'' + '\'' + EOF + rm $branch_dir_prefix/branch-garbage-special && + test_cmp expect err && + + printf "%s\n\n\n garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage-special && + git refs verify 2>err && + cat >expect <<-EOF && + warning: refs/heads/branch-garbage-special: trailingRefContent: has trailing garbage: '\'' - garbage'\'' - EOF - rm $branch_dir_prefix/branch-garbage-special && - test_cmp expect err + garbage'\'' + EOF + rm $branch_dir_prefix/branch-garbage-special && + test_cmp expect err + ) ' test_expect_success 'regular ref content should be checked (aggregate)' ' @@ -237,99 +244,103 @@ test_expect_success 'regular ref content should be checked (aggregate)' ' git init repo && branch_dir_prefix=.git/refs/heads && tag_dir_prefix=.git/refs/tags && - cd repo && - test_commit default && - mkdir -p "$branch_dir_prefix/a/b" && - - bad_content_1=$(git rev-parse main)x && - bad_content_2=xfsazqfxcadas && - bad_content_3=Xfsazqfxcadas && - printf "%s" $bad_content_1 >$tag_dir_prefix/tag-bad-1 && - printf "%s" $bad_content_2 >$tag_dir_prefix/tag-bad-2 && - printf "%s" $bad_content_3 >$branch_dir_prefix/a/b/branch-bad && - printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline && - printf "%s garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage && - - test_must_fail git refs verify 2>err && - cat >expect <<-EOF && - error: refs/heads/a/b/branch-bad: badRefContent: $bad_content_3 - error: refs/tags/tag-bad-1: badRefContent: $bad_content_1 - error: refs/tags/tag-bad-2: badRefContent: $bad_content_2 - warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\'' garbage'\'' - warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end - EOF - sort err >sorted_err && - test_cmp expect sorted_err + ( + cd repo && + test_commit default && + mkdir -p "$branch_dir_prefix/a/b" && + + bad_content_1=$(git rev-parse main)x && + bad_content_2=xfsazqfxcadas && + bad_content_3=Xfsazqfxcadas && + printf "%s" $bad_content_1 >$tag_dir_prefix/tag-bad-1 && + printf "%s" $bad_content_2 >$tag_dir_prefix/tag-bad-2 && + printf "%s" $bad_content_3 >$branch_dir_prefix/a/b/branch-bad && + printf "%s" "$(git rev-parse main)" >$branch_dir_prefix/branch-no-newline && + printf "%s garbage" "$(git rev-parse main)" >$branch_dir_prefix/branch-garbage && + + test_must_fail git refs verify 2>err && + cat >expect <<-EOF && + error: refs/heads/a/b/branch-bad: badRefContent: $bad_content_3 + error: refs/tags/tag-bad-1: badRefContent: $bad_content_1 + error: refs/tags/tag-bad-2: badRefContent: $bad_content_2 + warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\'' garbage'\'' + warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end + EOF + sort err >sorted_err && + test_cmp expect sorted_err + ) ' test_expect_success 'textual symref content should be checked (individual)' ' test_when_finished "rm -rf repo" && git init repo && branch_dir_prefix=.git/refs/heads && - cd repo && - test_commit default && - mkdir -p "$branch_dir_prefix/a/b" && + ( + cd repo && + test_commit default && + mkdir -p "$branch_dir_prefix/a/b" && + + for good_referent in "refs/heads/branch" "HEAD" + do + printf "ref: %s\n" $good_referent >$branch_dir_prefix/branch-good && + git refs verify 2>err && + rm $branch_dir_prefix/branch-good && + test_must_be_empty err || return 1 + done && + + for bad_referent in "refs/heads/.branch" "refs/heads/~branch" "refs/heads/?branch" + do + printf "ref: %s\n" $bad_referent >$branch_dir_prefix/branch-bad && + test_must_fail git refs verify 2>err && + cat >expect <<-EOF && + error: refs/heads/branch-bad: badReferentName: points to invalid refname '\''$bad_referent'\'' + EOF + rm $branch_dir_prefix/branch-bad && + test_cmp expect err || return 1 + done && - for good_referent in "refs/heads/branch" "HEAD" - do - printf "ref: %s\n" $good_referent >$branch_dir_prefix/branch-good && + printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline && git refs verify 2>err && - rm $branch_dir_prefix/branch-good && - test_must_be_empty err || return 1 - done && + cat >expect <<-EOF && + warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end + EOF + rm $branch_dir_prefix/branch-no-newline && + test_cmp expect err && - for bad_referent in "refs/heads/.branch" "refs/heads/~branch" "refs/heads/?branch" - do - printf "ref: %s\n" $bad_referent >$branch_dir_prefix/branch-bad && - test_must_fail git refs verify 2>err && + printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 && + git refs verify 2>err && + cat >expect <<-EOF && + warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: misses LF at the end + warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: has trailing whitespaces or newlines + EOF + rm $branch_dir_prefix/a/b/branch-trailing-1 && + test_cmp expect err && + + printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 && + git refs verify 2>err && + cat >expect <<-EOF && + warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: has trailing whitespaces or newlines + EOF + rm $branch_dir_prefix/a/b/branch-trailing-2 && + test_cmp expect err && + + printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 && + git refs verify 2>err && cat >expect <<-EOF && - error: refs/heads/branch-bad: badReferentName: points to invalid refname '\''$bad_referent'\'' - EOF - rm $branch_dir_prefix/branch-bad && - test_cmp expect err || return 1 - done && - - printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline && - git refs verify 2>err && - cat >expect <<-EOF && - warning: refs/heads/branch-no-newline: refMissingNewline: misses LF at the end - EOF - rm $branch_dir_prefix/branch-no-newline && - test_cmp expect err && - - printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 && - git refs verify 2>err && - cat >expect <<-EOF && - warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: misses LF at the end - warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: has trailing whitespaces or newlines - EOF - rm $branch_dir_prefix/a/b/branch-trailing-1 && - test_cmp expect err && - - printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 && - git refs verify 2>err && - cat >expect <<-EOF && - warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: has trailing whitespaces or newlines - EOF - rm $branch_dir_prefix/a/b/branch-trailing-2 && - test_cmp expect err && - - printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 && - git refs verify 2>err && - cat >expect <<-EOF && - warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: has trailing whitespaces or newlines - EOF - rm $branch_dir_prefix/a/b/branch-trailing-3 && - test_cmp expect err && - - printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated && - git refs verify 2>err && - cat >expect <<-EOF && - warning: refs/heads/a/b/branch-complicated: refMissingNewline: misses LF at the end - warning: refs/heads/a/b/branch-complicated: trailingRefContent: has trailing whitespaces or newlines - EOF - rm $branch_dir_prefix/a/b/branch-complicated && - test_cmp expect err + warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: has trailing whitespaces or newlines + EOF + rm $branch_dir_prefix/a/b/branch-trailing-3 && + test_cmp expect err && + + printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated && + git refs verify 2>err && + cat >expect <<-EOF && + warning: refs/heads/a/b/branch-complicated: refMissingNewline: misses LF at the end + warning: refs/heads/a/b/branch-complicated: trailingRefContent: has trailing whitespaces or newlines + EOF + rm $branch_dir_prefix/a/b/branch-complicated && + test_cmp expect err + ) ' test_expect_success 'textual symref content should be checked (aggregate)' ' @@ -337,32 +348,34 @@ test_expect_success 'textual symref content should be checked (aggregate)' ' git init repo && branch_dir_prefix=.git/refs/heads && tag_dir_prefix=.git/refs/tags && - cd repo && - test_commit default && - mkdir -p "$branch_dir_prefix/a/b" && - - printf "ref: refs/heads/branch\n" >$branch_dir_prefix/branch-good && - printf "ref: HEAD\n" >$branch_dir_prefix/branch-head && - printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline-1 && - printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 && - printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 && - printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 && - printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated && - printf "ref: refs/heads/.branch\n" >$branch_dir_prefix/branch-bad-1 && - - test_must_fail git refs verify 2>err && - cat >expect <<-EOF && - error: refs/heads/branch-bad-1: badReferentName: points to invalid refname '\''refs/heads/.branch'\'' - warning: refs/heads/a/b/branch-complicated: refMissingNewline: misses LF at the end - warning: refs/heads/a/b/branch-complicated: trailingRefContent: has trailing whitespaces or newlines - warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: misses LF at the end - warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: has trailing whitespaces or newlines - warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: has trailing whitespaces or newlines - warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: has trailing whitespaces or newlines - warning: refs/heads/branch-no-newline-1: refMissingNewline: misses LF at the end - EOF - sort err >sorted_err && - test_cmp expect sorted_err + ( + cd repo && + test_commit default && + mkdir -p "$branch_dir_prefix/a/b" && + + printf "ref: refs/heads/branch\n" >$branch_dir_prefix/branch-good && + printf "ref: HEAD\n" >$branch_dir_prefix/branch-head && + printf "ref: refs/heads/branch" >$branch_dir_prefix/branch-no-newline-1 && + printf "ref: refs/heads/branch " >$branch_dir_prefix/a/b/branch-trailing-1 && + printf "ref: refs/heads/branch\n\n" >$branch_dir_prefix/a/b/branch-trailing-2 && + printf "ref: refs/heads/branch \n" >$branch_dir_prefix/a/b/branch-trailing-3 && + printf "ref: refs/heads/branch \n " >$branch_dir_prefix/a/b/branch-complicated && + printf "ref: refs/heads/.branch\n" >$branch_dir_prefix/branch-bad-1 && + + test_must_fail git refs verify 2>err && + cat >expect <<-EOF && + error: refs/heads/branch-bad-1: badReferentName: points to invalid refname '\''refs/heads/.branch'\'' + warning: refs/heads/a/b/branch-complicated: refMissingNewline: misses LF at the end + warning: refs/heads/a/b/branch-complicated: trailingRefContent: has trailing whitespaces or newlines + warning: refs/heads/a/b/branch-trailing-1: refMissingNewline: misses LF at the end + warning: refs/heads/a/b/branch-trailing-1: trailingRefContent: has trailing whitespaces or newlines + warning: refs/heads/a/b/branch-trailing-2: trailingRefContent: has trailing whitespaces or newlines + warning: refs/heads/a/b/branch-trailing-3: trailingRefContent: has trailing whitespaces or newlines + warning: refs/heads/branch-no-newline-1: refMissingNewline: misses LF at the end + EOF + sort err >sorted_err && + test_cmp expect sorted_err + ) ' test_expect_success 'the target of the textual symref should be checked' ' @@ -370,230 +383,490 @@ test_expect_success 'the target of the textual symref should be checked' ' git init repo && branch_dir_prefix=.git/refs/heads && tag_dir_prefix=.git/refs/tags && - cd repo && - test_commit default && - mkdir -p "$branch_dir_prefix/a/b" && + ( + cd repo && + test_commit default && + mkdir -p "$branch_dir_prefix/a/b" && + + for good_referent in "refs/heads/branch" "HEAD" "refs/tags/tag" + do + printf "ref: %s\n" $good_referent >$branch_dir_prefix/branch-good && + git refs verify 2>err && + rm $branch_dir_prefix/branch-good && + test_must_be_empty err || return 1 + done && + + for nonref_referent in "refs-back/heads/branch" "refs-back/tags/tag" "reflogs/refs/heads/branch" + do + printf "ref: %s\n" $nonref_referent >$branch_dir_prefix/branch-bad-1 && + git refs verify 2>err && + cat >expect <<-EOF && + warning: refs/heads/branch-bad-1: symrefTargetIsNotARef: points to non-ref target '\''$nonref_referent'\'' + EOF + rm $branch_dir_prefix/branch-bad-1 && + test_cmp expect err || return 1 + done + ) +' + +test_expect_success SYMLINKS 'symlink symref content should be checked' ' + test_when_finished "rm -rf repo" && + git init repo && + branch_dir_prefix=.git/refs/heads && + tag_dir_prefix=.git/refs/tags && + ( + cd repo && + test_commit default && + mkdir -p "$branch_dir_prefix/a/b" && - for good_referent in "refs/heads/branch" "HEAD" "refs/tags/tag" - do - printf "ref: %s\n" $good_referent >$branch_dir_prefix/branch-good && + ln -sf ./main $branch_dir_prefix/branch-symbolic-good && git refs verify 2>err && - rm $branch_dir_prefix/branch-good && - test_must_be_empty err || return 1 - done && + cat >expect <<-EOF && + warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref + EOF + rm $branch_dir_prefix/branch-symbolic-good && + test_cmp expect err && - for nonref_referent in "refs-back/heads/branch" "refs-back/tags/tag" "reflogs/refs/heads/branch" - do - printf "ref: %s\n" $nonref_referent >$branch_dir_prefix/branch-bad-1 && + ln -sf ../../logs/branch-escape $branch_dir_prefix/branch-symbolic && git refs verify 2>err && cat >expect <<-EOF && - warning: refs/heads/branch-bad-1: symrefTargetIsNotARef: points to non-ref target '\''$nonref_referent'\'' + warning: refs/heads/branch-symbolic: symlinkRef: use deprecated symbolic link for symref + warning: refs/heads/branch-symbolic: symrefTargetIsNotARef: points to non-ref target '\''logs/branch-escape'\'' + EOF + rm $branch_dir_prefix/branch-symbolic && + test_cmp expect err && + + ln -sf ./"branch " $branch_dir_prefix/branch-symbolic-bad && + test_must_fail git refs verify 2>err && + cat >expect <<-EOF && + warning: refs/heads/branch-symbolic-bad: symlinkRef: use deprecated symbolic link for symref + error: refs/heads/branch-symbolic-bad: badReferentName: points to invalid refname '\''refs/heads/branch '\'' + EOF + rm $branch_dir_prefix/branch-symbolic-bad && + test_cmp expect err && + + ln -sf ./".tag" $tag_dir_prefix/tag-symbolic-1 && + test_must_fail git refs verify 2>err && + cat >expect <<-EOF && + warning: refs/tags/tag-symbolic-1: symlinkRef: use deprecated symbolic link for symref + error: refs/tags/tag-symbolic-1: badReferentName: points to invalid refname '\''refs/tags/.tag'\'' EOF - rm $branch_dir_prefix/branch-bad-1 && - test_cmp expect err || return 1 - done + rm $tag_dir_prefix/tag-symbolic-1 && + test_cmp expect err + ) ' -test_expect_success SYMLINKS 'symlink symref content should be checked' ' +test_expect_success SYMLINKS 'symlink symref content should be checked (worktree)' ' test_when_finished "rm -rf repo" && git init repo && - branch_dir_prefix=.git/refs/heads && - tag_dir_prefix=.git/refs/tags && - cd repo && - test_commit default && - mkdir -p "$branch_dir_prefix/a/b" && - - ln -sf ./main $branch_dir_prefix/branch-symbolic-good && - git refs verify 2>err && - cat >expect <<-EOF && - warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref - EOF - rm $branch_dir_prefix/branch-symbolic-good && - test_cmp expect err && - - ln -sf ../../logs/branch-escape $branch_dir_prefix/branch-symbolic && - git refs verify 2>err && - cat >expect <<-EOF && - warning: refs/heads/branch-symbolic: symlinkRef: use deprecated symbolic link for symref - warning: refs/heads/branch-symbolic: symrefTargetIsNotARef: points to non-ref target '\''logs/branch-escape'\'' - EOF - rm $branch_dir_prefix/branch-symbolic && - test_cmp expect err && - - ln -sf ./"branch " $branch_dir_prefix/branch-symbolic-bad && - test_must_fail git refs verify 2>err && - cat >expect <<-EOF && - warning: refs/heads/branch-symbolic-bad: symlinkRef: use deprecated symbolic link for symref - error: refs/heads/branch-symbolic-bad: badReferentName: points to invalid refname '\''refs/heads/branch '\'' - EOF - rm $branch_dir_prefix/branch-symbolic-bad && - test_cmp expect err && - - ln -sf ./".tag" $tag_dir_prefix/tag-symbolic-1 && - test_must_fail git refs verify 2>err && - cat >expect <<-EOF && - warning: refs/tags/tag-symbolic-1: symlinkRef: use deprecated symbolic link for symref - error: refs/tags/tag-symbolic-1: badReferentName: points to invalid refname '\''refs/tags/.tag'\'' - EOF - rm $tag_dir_prefix/tag-symbolic-1 && - test_cmp expect err + ( + cd repo && + test_commit default && + git branch branch-1 && + git branch branch-2 && + git branch branch-3 && + git worktree add ./worktree-1 branch-2 && + git worktree add ./worktree-2 branch-3 && + main_worktree_refdir_prefix=.git/refs/heads && + worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree && + worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree && + + ( + cd worktree-1 && + git update-ref refs/worktree/branch-4 refs/heads/branch-1 + ) && + ( + cd worktree-2 && + git update-ref refs/worktree/branch-4 refs/heads/branch-1 + ) && + + ln -sf ../../../../refs/heads/good-branch $worktree1_refdir_prefix/branch-symbolic-good && + git refs verify 2>err && + cat >expect <<-EOF && + warning: worktrees/worktree-1/refs/worktree/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref + EOF + rm $worktree1_refdir_prefix/branch-symbolic-good && + test_cmp expect err && + + ln -sf ../../../../worktrees/worktree-1/good-branch $worktree2_refdir_prefix/branch-symbolic-good && + git refs verify 2>err && + cat >expect <<-EOF && + warning: worktrees/worktree-2/refs/worktree/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref + EOF + rm $worktree2_refdir_prefix/branch-symbolic-good && + test_cmp expect err && + + ln -sf ../../worktrees/worktree-2/good-branch $main_worktree_refdir_prefix/branch-symbolic-good && + git refs verify 2>err && + cat >expect <<-EOF && + warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref + EOF + rm $main_worktree_refdir_prefix/branch-symbolic-good && + test_cmp expect err && + + ln -sf ../../../../logs/branch-escape $worktree1_refdir_prefix/branch-symbolic && + git refs verify 2>err && + cat >expect <<-EOF && + warning: worktrees/worktree-1/refs/worktree/branch-symbolic: symlinkRef: use deprecated symbolic link for symref + warning: worktrees/worktree-1/refs/worktree/branch-symbolic: symrefTargetIsNotARef: points to non-ref target '\''logs/branch-escape'\'' + EOF + rm $worktree1_refdir_prefix/branch-symbolic && + test_cmp expect err && + + for bad_referent_name in ".tag" "branch " + do + ln -sf ./"$bad_referent_name" $worktree1_refdir_prefix/bad-symbolic && + test_must_fail git refs verify 2>err && + cat >expect <<-EOF && + warning: worktrees/worktree-1/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref + error: worktrees/worktree-1/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''worktrees/worktree-1/refs/worktree/$bad_referent_name'\'' + EOF + rm $worktree1_refdir_prefix/bad-symbolic && + test_cmp expect err && + + ln -sf ../../../../refs/heads/"$bad_referent_name" $worktree1_refdir_prefix/bad-symbolic && + test_must_fail git refs verify 2>err && + cat >expect <<-EOF && + warning: worktrees/worktree-1/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref + error: worktrees/worktree-1/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''refs/heads/$bad_referent_name'\'' + EOF + rm $worktree1_refdir_prefix/bad-symbolic && + test_cmp expect err && + + ln -sf ./"$bad_referent_name" $worktree2_refdir_prefix/bad-symbolic && + test_must_fail git refs verify 2>err && + cat >expect <<-EOF && + warning: worktrees/worktree-2/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref + error: worktrees/worktree-2/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''worktrees/worktree-2/refs/worktree/$bad_referent_name'\'' + EOF + rm $worktree2_refdir_prefix/bad-symbolic && + test_cmp expect err && + + ln -sf ../../../../refs/heads/"$bad_referent_name" $worktree2_refdir_prefix/bad-symbolic && + test_must_fail git refs verify 2>err && + cat >expect <<-EOF && + warning: worktrees/worktree-2/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref + error: worktrees/worktree-2/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''refs/heads/$bad_referent_name'\'' + EOF + rm $worktree2_refdir_prefix/bad-symbolic && + test_cmp expect err || return 1 + done + ) ' -test_expect_success SYMLINKS 'symlink symref content should be checked (worktree)' ' +test_expect_success 'ref content checks should work with worktrees' ' test_when_finished "rm -rf repo" && git init repo && - cd repo && - test_commit default && - git branch branch-1 && - git branch branch-2 && - git branch branch-3 && - git worktree add ./worktree-1 branch-2 && - git worktree add ./worktree-2 branch-3 && - main_worktree_refdir_prefix=.git/refs/heads && - worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree && - worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree && - - ( - cd worktree-1 && - git update-ref refs/worktree/branch-4 refs/heads/branch-1 - ) && ( - cd worktree-2 && - git update-ref refs/worktree/branch-4 refs/heads/branch-1 - ) && - - ln -sf ../../../../refs/heads/good-branch $worktree1_refdir_prefix/branch-symbolic-good && - git refs verify 2>err && - cat >expect <<-EOF && - warning: worktrees/worktree-1/refs/worktree/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref - EOF - rm $worktree1_refdir_prefix/branch-symbolic-good && - test_cmp expect err && - - ln -sf ../../../../worktrees/worktree-1/good-branch $worktree2_refdir_prefix/branch-symbolic-good && - git refs verify 2>err && - cat >expect <<-EOF && - warning: worktrees/worktree-2/refs/worktree/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref - EOF - rm $worktree2_refdir_prefix/branch-symbolic-good && - test_cmp expect err && - - ln -sf ../../worktrees/worktree-2/good-branch $main_worktree_refdir_prefix/branch-symbolic-good && - git refs verify 2>err && - cat >expect <<-EOF && - warning: refs/heads/branch-symbolic-good: symlinkRef: use deprecated symbolic link for symref - EOF - rm $main_worktree_refdir_prefix/branch-symbolic-good && - test_cmp expect err && - - ln -sf ../../../../logs/branch-escape $worktree1_refdir_prefix/branch-symbolic && - git refs verify 2>err && - cat >expect <<-EOF && - warning: worktrees/worktree-1/refs/worktree/branch-symbolic: symlinkRef: use deprecated symbolic link for symref - warning: worktrees/worktree-1/refs/worktree/branch-symbolic: symrefTargetIsNotARef: points to non-ref target '\''logs/branch-escape'\'' - EOF - rm $worktree1_refdir_prefix/branch-symbolic && - test_cmp expect err && - - for bad_referent_name in ".tag" "branch " - do - ln -sf ./"$bad_referent_name" $worktree1_refdir_prefix/bad-symbolic && - test_must_fail git refs verify 2>err && + cd repo && + test_commit default && + git branch branch-1 && + git branch branch-2 && + git branch branch-3 && + git worktree add ./worktree-1 branch-2 && + git worktree add ./worktree-2 branch-3 && + worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree && + worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree && + + ( + cd worktree-1 && + git update-ref refs/worktree/branch-4 refs/heads/branch-1 + ) && + ( + cd worktree-2 && + git update-ref refs/worktree/branch-4 refs/heads/branch-1 + ) && + + for bad_content in "$(git rev-parse HEAD)x" "xfsazqfxcadas" "Xfsazqfxcadas" + do + printf "%s" $bad_content >$worktree1_refdir_prefix/bad-branch-1 && + test_must_fail git refs verify 2>err && + cat >expect <<-EOF && + error: worktrees/worktree-1/refs/worktree/bad-branch-1: badRefContent: $bad_content + EOF + rm $worktree1_refdir_prefix/bad-branch-1 && + test_cmp expect err || return 1 + done && + + for bad_content in "$(git rev-parse HEAD)x" "xfsazqfxcadas" "Xfsazqfxcadas" + do + printf "%s" $bad_content >$worktree2_refdir_prefix/bad-branch-2 && + test_must_fail git refs verify 2>err && + cat >expect <<-EOF && + error: worktrees/worktree-2/refs/worktree/bad-branch-2: badRefContent: $bad_content + EOF + rm $worktree2_refdir_prefix/bad-branch-2 && + test_cmp expect err || return 1 + done && + + printf "%s" "$(git rev-parse HEAD)" >$worktree1_refdir_prefix/branch-no-newline && + git refs verify 2>err && cat >expect <<-EOF && - warning: worktrees/worktree-1/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref - error: worktrees/worktree-1/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''worktrees/worktree-1/refs/worktree/$bad_referent_name'\'' + warning: worktrees/worktree-1/refs/worktree/branch-no-newline: refMissingNewline: misses LF at the end EOF - rm $worktree1_refdir_prefix/bad-symbolic && + rm $worktree1_refdir_prefix/branch-no-newline && test_cmp expect err && - ln -sf ../../../../refs/heads/"$bad_referent_name" $worktree1_refdir_prefix/bad-symbolic && - test_must_fail git refs verify 2>err && + printf "%s garbage" "$(git rev-parse HEAD)" >$worktree1_refdir_prefix/branch-garbage && + git refs verify 2>err && cat >expect <<-EOF && - warning: worktrees/worktree-1/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref - error: worktrees/worktree-1/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''refs/heads/$bad_referent_name'\'' + warning: worktrees/worktree-1/refs/worktree/branch-garbage: trailingRefContent: has trailing garbage: '\'' garbage'\'' EOF - rm $worktree1_refdir_prefix/bad-symbolic && - test_cmp expect err && + rm $worktree1_refdir_prefix/branch-garbage && + test_cmp expect err + ) +' - ln -sf ./"$bad_referent_name" $worktree2_refdir_prefix/bad-symbolic && +test_expect_success SYMLINKS 'the filetype of packed-refs should be checked' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit default && + git branch branch-1 && + git branch branch-2 && + git branch branch-3 && + git pack-refs --all && + + mv .git/packed-refs .git/packed-refs-back && + ln -sf packed-refs-back .git/packed-refs && test_must_fail git refs verify 2>err && cat >expect <<-EOF && - warning: worktrees/worktree-2/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref - error: worktrees/worktree-2/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''worktrees/worktree-2/refs/worktree/$bad_referent_name'\'' + error: packed-refs: badRefFiletype: not a regular file but a symlink EOF - rm $worktree2_refdir_prefix/bad-symbolic && + rm .git/packed-refs && test_cmp expect err && - ln -sf ../../../../refs/heads/"$bad_referent_name" $worktree2_refdir_prefix/bad-symbolic && + mkdir .git/packed-refs && test_must_fail git refs verify 2>err && cat >expect <<-EOF && - warning: worktrees/worktree-2/refs/worktree/bad-symbolic: symlinkRef: use deprecated symbolic link for symref - error: worktrees/worktree-2/refs/worktree/bad-symbolic: badReferentName: points to invalid refname '\''refs/heads/$bad_referent_name'\'' + error: packed-refs: badRefFiletype: not a regular file EOF - rm $worktree2_refdir_prefix/bad-symbolic && - test_cmp expect err || return 1 - done + rm -r .git/packed-refs && + test_cmp expect err + ) ' -test_expect_success 'ref content checks should work with worktrees' ' +test_expect_success 'packed-refs header should be checked' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit default && + + git refs verify 2>err && + test_must_be_empty err && + + for bad_header in "# pack-refs wit: peeled fully-peeled sorted " \ + "# pack-refs with traits: peeled fully-peeled sorted " \ + "# pack-refs with a: peeled fully-peeled" \ + "# pack-refs with:peeled fully-peeled sorted" + do + printf "%s\n" "$bad_header" >.git/packed-refs && + test_must_fail git refs verify 2>err && + cat >expect <<-EOF && + error: packed-refs.header: badPackedRefHeader: '\''$bad_header'\'' does not start with '\''# pack-refs with: '\'' + EOF + rm .git/packed-refs && + test_cmp expect err || return 1 + done + ) +' + +test_expect_success 'packed-refs missing header should not be reported' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit default && + + printf "$(git rev-parse HEAD) refs/heads/main\n" >.git/packed-refs && + git refs verify 2>err && + test_must_be_empty err + ) +' + +test_expect_success 'packed-refs unknown traits should not be reported' ' test_when_finished "rm -rf repo" && git init repo && - cd repo && - test_commit default && - git branch branch-1 && - git branch branch-2 && - git branch branch-3 && - git worktree add ./worktree-1 branch-2 && - git worktree add ./worktree-2 branch-3 && - worktree1_refdir_prefix=.git/worktrees/worktree-1/refs/worktree && - worktree2_refdir_prefix=.git/worktrees/worktree-2/refs/worktree && + ( + cd repo && + test_commit default && + + printf "# pack-refs with: peeled fully-peeled sorted foo\n" >.git/packed-refs && + git refs verify 2>err && + test_must_be_empty err + ) +' +test_expect_success 'packed-refs content should be checked' ' + test_when_finished "rm -rf repo" && + git init repo && ( - cd worktree-1 && - git update-ref refs/worktree/branch-4 refs/heads/branch-1 - ) && + cd repo && + test_commit default && + git branch branch-1 && + git branch branch-2 && + git tag -a annotated-tag-1 -m tag-1 && + git tag -a annotated-tag-2 -m tag-2 && + + branch_1_oid=$(git rev-parse branch-1) && + branch_2_oid=$(git rev-parse branch-2) && + tag_1_oid=$(git rev-parse annotated-tag-1) && + tag_2_oid=$(git rev-parse annotated-tag-2) && + tag_1_peeled_oid=$(git rev-parse annotated-tag-1^{}) && + tag_2_peeled_oid=$(git rev-parse annotated-tag-2^{}) && + short_oid=$(printf "%s" $tag_1_peeled_oid | cut -c 1-4) && + + cat >.git/packed-refs <<-EOF && + # pack-refs with: peeled fully-peeled sorted + $short_oid refs/heads/branch-1 + ${branch_1_oid}x + $branch_2_oid refs/heads/bad-branch + $branch_2_oid refs/heads/branch. + $tag_1_oid refs/tags/annotated-tag-3 + ^$short_oid + $tag_2_oid refs/tags/annotated-tag-4. + ^$tag_2_peeled_oid garbage + EOF + test_must_fail git refs verify 2>err && + cat >expect <<-EOF && + error: packed-refs line 2: badPackedRefEntry: '\''$short_oid refs/heads/branch-1'\'' has invalid oid + error: packed-refs line 3: badPackedRefEntry: has no space after oid '\''$branch_1_oid'\'' but with '\''x'\'' + error: packed-refs line 4: badRefName: has bad refname '\'' refs/heads/bad-branch'\'' + error: packed-refs line 5: badRefName: has bad refname '\''refs/heads/branch.'\'' + error: packed-refs line 7: badPackedRefEntry: '\''$short_oid'\'' has invalid peeled oid + error: packed-refs line 8: badRefName: has bad refname '\''refs/tags/annotated-tag-4.'\'' + error: packed-refs line 9: badPackedRefEntry: has trailing garbage after peeled oid '\'' garbage'\'' + EOF + test_cmp expect err + ) +' + +test_expect_success 'packed-ref with sorted trait should be checked' ' + test_when_finished "rm -rf repo" && + git init repo && ( - cd worktree-2 && - git update-ref refs/worktree/branch-4 refs/heads/branch-1 - ) && + cd repo && + test_commit default && + git branch branch-1 && + git branch branch-2 && + git tag -a annotated-tag-1 -m tag-1 && + branch_1_oid=$(git rev-parse branch-1) && + branch_2_oid=$(git rev-parse branch-2) && + tag_1_oid=$(git rev-parse annotated-tag-1) && + tag_1_peeled_oid=$(git rev-parse annotated-tag-1^{}) && + refname1="refs/heads/main" && + refname2="refs/heads/foo" && + refname3="refs/tags/foo" && + + cat >.git/packed-refs <<-EOF && + # pack-refs with: peeled fully-peeled sorted + EOF + git refs verify 2>err && + rm .git/packed-refs && + test_must_be_empty err && - for bad_content in "$(git rev-parse HEAD)x" "xfsazqfxcadas" "Xfsazqfxcadas" - do - printf "%s" $bad_content >$worktree1_refdir_prefix/bad-branch-1 && + cat >.git/packed-refs <<-EOF && + # pack-refs with: peeled fully-peeled sorted + $branch_2_oid $refname1 + EOF + git refs verify 2>err && + rm .git/packed-refs && + test_must_be_empty err && + + cat >.git/packed-refs <<-EOF && + # pack-refs with: peeled fully-peeled sorted + $branch_2_oid $refname1 + $branch_1_oid $refname2 + $tag_1_oid $refname3 + EOF test_must_fail git refs verify 2>err && cat >expect <<-EOF && - error: worktrees/worktree-1/refs/worktree/bad-branch-1: badRefContent: $bad_content + error: packed-refs line 3: packedRefUnsorted: refname '\''$refname2'\'' is less than previous refname '\''$refname1'\'' EOF - rm $worktree1_refdir_prefix/bad-branch-1 && - test_cmp expect err || return 1 - done && + rm .git/packed-refs && + test_cmp expect err && - for bad_content in "$(git rev-parse HEAD)x" "xfsazqfxcadas" "Xfsazqfxcadas" - do - printf "%s" $bad_content >$worktree2_refdir_prefix/bad-branch-2 && + cat >.git/packed-refs <<-EOF && + # pack-refs with: peeled fully-peeled sorted + $tag_1_oid $refname3 + ^$tag_1_peeled_oid + $branch_2_oid $refname2 + EOF test_must_fail git refs verify 2>err && cat >expect <<-EOF && - error: worktrees/worktree-2/refs/worktree/bad-branch-2: badRefContent: $bad_content - EOF - rm $worktree2_refdir_prefix/bad-branch-2 && - test_cmp expect err || return 1 - done && - - printf "%s" "$(git rev-parse HEAD)" >$worktree1_refdir_prefix/branch-no-newline && - git refs verify 2>err && - cat >expect <<-EOF && - warning: worktrees/worktree-1/refs/worktree/branch-no-newline: refMissingNewline: misses LF at the end - EOF - rm $worktree1_refdir_prefix/branch-no-newline && - test_cmp expect err && - - printf "%s garbage" "$(git rev-parse HEAD)" >$worktree1_refdir_prefix/branch-garbage && - git refs verify 2>err && - cat >expect <<-EOF && - warning: worktrees/worktree-1/refs/worktree/branch-garbage: trailingRefContent: has trailing garbage: '\'' garbage'\'' - EOF - rm $worktree1_refdir_prefix/branch-garbage && - test_cmp expect err + error: packed-refs line 4: packedRefUnsorted: refname '\''$refname2'\'' is less than previous refname '\''$refname3'\'' + EOF + rm .git/packed-refs && + test_cmp expect err + ) +' + +test_expect_success 'packed-ref without sorted trait should not be checked' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit default && + git branch branch-1 && + git branch branch-2 && + git tag -a annotated-tag-1 -m tag-1 && + branch_1_oid=$(git rev-parse branch-1) && + branch_2_oid=$(git rev-parse branch-2) && + tag_1_oid=$(git rev-parse annotated-tag-1) && + tag_1_peeled_oid=$(git rev-parse annotated-tag-1^{}) && + refname1="refs/heads/main" && + refname2="refs/heads/foo" && + refname3="refs/tags/foo" && + + cat >.git/packed-refs <<-EOF && + # pack-refs with: peeled fully-peeled + $branch_2_oid $refname1 + $branch_1_oid $refname2 + EOF + git refs verify 2>err && + test_must_be_empty err + ) +' + +test_expect_success '--[no-]references option should apply to fsck' ' + test_when_finished "rm -rf repo" && + git init repo && + branch_dir_prefix=.git/refs/heads && + ( + cd repo && + test_commit default && + for trailing_content in " garbage" " more garbage" + do + printf "%s" "$(git rev-parse HEAD)$trailing_content" >$branch_dir_prefix/branch-garbage && + git fsck 2>err && + cat >expect <<-EOF && + warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\''$trailing_content'\'' + EOF + rm $branch_dir_prefix/branch-garbage && + test_cmp expect err || return 1 + done && + + for trailing_content in " garbage" " more garbage" + do + printf "%s" "$(git rev-parse HEAD)$trailing_content" >$branch_dir_prefix/branch-garbage && + git fsck --references 2>err && + cat >expect <<-EOF && + warning: refs/heads/branch-garbage: trailingRefContent: has trailing garbage: '\''$trailing_content'\'' + EOF + rm $branch_dir_prefix/branch-garbage && + test_cmp expect err || return 1 + done && + + for trailing_content in " garbage" " more garbage" + do + printf "%s" "$(git rev-parse HEAD)$trailing_content" >$branch_dir_prefix/branch-garbage && + git fsck --no-references 2>err && + rm $branch_dir_prefix/branch-garbage && + test_must_be_empty err || return 1 + done + ) ' test_done diff --git a/t/t0610-reftable-basics.sh b/t/t0610-reftable-basics.sh index 4618ffc108..002a75dee8 100755 --- a/t/t0610-reftable-basics.sh +++ b/t/t0610-reftable-basics.sh @@ -14,6 +14,13 @@ export GIT_TEST_DEFAULT_REF_FORMAT INVALID_OID=$(test_oid 001) +test_expect_success 'pack-refs does not crash with -h' ' + test_expect_code 129 git pack-refs -h >usage && + test_grep "[Uu]sage: git pack-refs " usage && + test_expect_code 129 nongit git pack-refs -h >usage && + test_grep "[Uu]sage: git pack-refs " usage +' + test_expect_success 'init: creates basic reftable structures' ' test_when_finished "rm -rf repo" && git init repo && diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh index 398865d6eb..86a2825473 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 && 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/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/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/t1419-exclude-refs.sh b/t/t1419-exclude-refs.sh index c04eeb7211..04797aee59 100755 --- a/t/t1419-exclude-refs.sh +++ b/t/t1419-exclude-refs.sh @@ -46,6 +46,10 @@ test_expect_success 'setup' ' echo "create refs/heads/$name/$i $base" || return 1 done || return 1 done >in && + for i in 5 6 7 + do + echo "create refs/heads/bar/4/$i $base" || return 1 + done >>in && echo "delete refs/heads/main" >>in && git update-ref --stdin <in && @@ -99,9 +103,17 @@ test_expect_success 'adjacent, non-overlapping excluded regions' ' esac ' -test_expect_success 'overlapping excluded regions' ' +test_expect_success 'non-directory excluded regions' ' for_each_ref__exclude refs/heads refs/heads/ba refs/heads/baz >actual 2>perf && - for_each_ref refs/heads/foo refs/heads/quux >expect && + for_each_ref refs/heads/bar refs/heads/foo refs/heads/quux >expect && + + test_cmp expect actual && + assert_jumps 1 perf +' + +test_expect_success 'overlapping excluded regions' ' + for_each_ref__exclude refs/heads refs/heads/bar refs/heads/bar/4 >actual 2>perf && + for_each_ref refs/heads/baz refs/heads/foo refs/heads/quux >expect && test_cmp expect actual && assert_jumps 1 perf @@ -155,4 +167,14 @@ test_expect_success 'meta-characters are discarded' ' assert_no_jumps perf ' +test_expect_success 'empty string exclude pattern is ignored' ' + git update-ref refs/heads/loose $(git rev-parse refs/heads/foo/1) && + + for_each_ref__exclude refs/heads "" >actual 2>perf && + for_each_ref >expect && + + test_cmp expect actual && + assert_no_jumps perf +' + test_done diff --git a/t/t2006-checkout-index-basic.sh b/t/t2006-checkout-index-basic.sh index bac231b167..fedd2cc097 100755 --- a/t/t2006-checkout-index-basic.sh +++ b/t/t2006-checkout-index-basic.sh @@ -21,6 +21,13 @@ test_expect_success 'checkout-index -h in broken repository' ' test_grep "[Uu]sage" broken/usage ' +test_expect_success 'checkout-index does not crash with -h' ' + test_expect_code 129 git checkout-index -h >usage && + test_grep "[Uu]sage: git checkout-index " usage && + test_expect_code 129 nongit git checkout-index -h >usage && + test_grep "[Uu]sage: git checkout-index " usage +' + test_expect_success 'checkout-index reports errors (cmdline)' ' test_must_fail git checkout-index -- does-not-exist 2>stderr && test_grep not.in.the.cache stderr diff --git a/t/t3004-ls-files-basic.sh b/t/t3004-ls-files-basic.sh index a1078f8701..4034a5a59f 100755 --- a/t/t3004-ls-files-basic.sh +++ b/t/t3004-ls-files-basic.sh @@ -34,6 +34,13 @@ test_expect_success 'ls-files -h in corrupt repository' ' test_grep "[Uu]sage: git ls-files " broken/usage ' +test_expect_success 'ls-files does not crash with -h' ' + test_expect_code 129 git ls-files -h >usage && + test_grep "[Uu]sage: git ls-files " usage && + test_expect_code 129 nongit git ls-files -h >usage && + test_grep "[Uu]sage: git ls-files " usage +' + test_expect_success SYMLINKS 'ls-files with absolute paths to symlinks' ' mkdir subs && ln -s nosuch link && diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh index 389670262e..58b3759935 100755 --- a/t/t3650-replay-basics.sh +++ b/t/t3650-replay-basics.sh @@ -195,4 +195,26 @@ test_expect_success 'using replay on bare repo to rebase multiple divergent bran done ' +test_expect_success 'merge.directoryRenames=false' ' + # create a test case that stress-tests the rename caching + git switch -c rename-onto && + + mkdir -p to-rename && + test_commit to-rename/move && + + mkdir -p renamed-directory && + git mv to-rename/move* renamed-directory/ && + test_tick && + git commit -m renamed-directory && + + git switch -c rename-from HEAD^ && + test_commit to-rename/add-a-file && + echo modified >to-rename/add-a-file.t && + test_tick && + git commit -m modified to-rename/add-a-file.t && + + git -c merge.directoryRenames=false replay \ + --onto rename-onto rename-onto..rename-from +' + test_done diff --git a/t/t4018/ini-section b/t/t4018/ini-section new file mode 100644 index 0000000000..c895ad9b4f --- /dev/null +++ b/t/t4018/ini-section @@ -0,0 +1,5 @@ +[RIGHT] + # comment + ; comment + name = value + ChangeMe diff --git a/t/t4018/ini-section-noindent b/t/t4018/ini-section-noindent new file mode 100644 index 0000000000..733d23c801 --- /dev/null +++ b/t/t4018/ini-section-noindent @@ -0,0 +1,5 @@ +[RIGHT] +# comment +; comment +name = value +ChangeMe diff --git a/t/t4018/ini-section-same-line b/t/t4018/ini-section-same-line new file mode 100644 index 0000000000..522a1fa4a1 --- /dev/null +++ b/t/t4018/ini-section-same-line @@ -0,0 +1,4 @@ +[RIGHT] name = value + # comment + ; comment + ChangeMe diff --git a/t/t4018/ini-subsection b/t/t4018/ini-subsection new file mode 100644 index 0000000000..3d47349e60 --- /dev/null +++ b/t/t4018/ini-subsection @@ -0,0 +1,12 @@ +[LEFT] + + [LEFT "CENTER"] + # comment + ; comment + name = value + + [LEFT "RIGHT"] + # comment + ; comment + name = value + ChangeMe diff --git a/t/t4018/ini-subsection-noindent b/t/t4018/ini-subsection-noindent new file mode 100644 index 0000000000..698ea00ea3 --- /dev/null +++ b/t/t4018/ini-subsection-noindent @@ -0,0 +1,12 @@ +[LEFT] + +[LEFT "CENTER"] +# comment +; comment +name = value + +[LEFT "RIGHT"] +# comment +; comment +name = value +ChangeMe diff --git a/t/t4055-diff-context.sh b/t/t4055-diff-context.sh index f7ff234cf9..ec2804eea6 100755 --- a/t/t4055-diff-context.sh +++ b/t/t4055-diff-context.sh @@ -89,4 +89,14 @@ test_expect_success '-U0 is valid, so is diff.context=0' ' grep "^+MODIFIED" output ' +test_expect_success '-U2147483647 works' ' + echo APPENDED >>x && + test_line_count = 16 x && + git diff -U2147483647 >output && + test_line_count = 22 output && + grep "^-ADDED" output && + grep "^+MODIFIED" output && + grep "^+APPENDED" output +' + test_done diff --git a/t/t4070-diff-pairs.sh b/t/t4070-diff-pairs.sh new file mode 100755 index 0000000000..70deafb860 --- /dev/null +++ b/t/t4070-diff-pairs.sh @@ -0,0 +1,90 @@ +#!/bin/sh + +test_description='basic diff-pairs tests' +. ./test-lib.sh + +# This creates a diff with added, modified, deleted, renamed, copied, and +# typechange entries. This includes a submodule to test submodule diff support. +test_expect_success 'setup' ' + test_config_global protocol.file.allow always && + git init sub && + test_commit -C sub initial && + + git init main && + cd main && + echo to-be-gone >deleted && + echo original >modified && + echo now-a-file >symlink && + test_seq 200 >two-hundred && + test_seq 201 500 >five-hundred && + git add . && + test_tick && + git commit -m base && + git tag base && + + git submodule add ../sub && + echo now-here >added && + echo new >modified && + rm deleted && + mkdir subdir && + echo content >subdir/file && + mv two-hundred renamed && + test_seq 201 500 | sed s/300/modified/ >copied && + rm symlink && + git add -A . && + test_ln_s_add dest symlink && + test_tick && + git commit -m new && + git tag new +' + +test_expect_success 'diff-pairs recreates --raw' ' + git diff-tree -r -M -C -C -z base new >expect && + git diff-pairs --raw -z >actual <expect && + test_cmp expect actual +' + +test_expect_success 'diff-pairs can create -p output' ' + git diff-tree -p -M -C -C base new >expect && + git diff-tree -r -M -C -C -z base new | + git diff-pairs -p -z >actual && + test_cmp expect actual +' + +test_expect_success 'diff-pairs does not support normal raw diff input' ' + git diff-tree -r base new | + test_must_fail git diff-pairs >out 2>err && + + echo "usage: working without -z is not supported" >expect && + test_must_be_empty out && + test_cmp expect err +' + +test_expect_success 'diff-pairs does not support tree objects as input' ' + git diff-tree -z base new | + test_must_fail git diff-pairs -z >out 2>err && + + echo "fatal: tree objects not supported" >expect && + test_must_be_empty out && + test_cmp expect err +' + +test_expect_success 'diff-pairs does not support pathspec arguments' ' + git diff-tree -r -z base new | + test_must_fail git diff-pairs -z -- new >out 2>err && + + echo "usage: pathspec arguments not supported" >expect && + test_must_be_empty out && + test_cmp expect err +' + +test_expect_success 'diff-pairs explicit queue flush' ' + git diff-tree -r -M -C -C -z base new >expect && + printf "\0" >>expect && + git diff-tree -r -M -C -C -z base new >>expect && + + git diff-pairs --raw -z <expect >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t4151-am-abort.sh b/t/t4151-am-abort.sh index edb38da701..8e1ecf8a68 100755 --- a/t/t4151-am-abort.sh +++ b/t/t4151-am-abort.sh @@ -112,7 +112,7 @@ test_expect_success 'am --abort will keep dirty index intact' ' test_expect_success 'am -3 stops on conflict on unborn branch' ' git checkout -f --orphan orphan && git reset && - rm -f otherfile-4 && + rm -f file-1 otherfile-4 && test_must_fail git am -3 0003-*.patch && test 2 -eq $(git ls-files -u | wc -l) && test 4 = "$(cat otherfile-4)" diff --git a/t/t4206-log-follow-harder-copies.sh b/t/t4206-log-follow-harder-copies.sh index bcab71c8e8..190c484321 100755 --- a/t/t4206-log-follow-harder-copies.sh +++ b/t/t4206-log-follow-harder-copies.sh @@ -54,4 +54,36 @@ test_expect_success 'validate the output.' ' compare_diff_patch current expected ' +test_expect_success 'log --follow -B does not BUG' ' + git switch --orphan break_and_follow_are_icky_so_use_both && + + test_seq 1 127 >numbers && + git add numbers && + git commit -m "numbers" && + + printf "%s\n" A B C D E F G H I J K L M N O Q R S T U V W X Y Z >pool && + echo changed >numbers && + git add pool numbers && + git commit -m "pool" && + + git log -1 -B --raw --follow -- "p*" +' + +test_expect_success 'log --follow -B does not die or use uninitialized memory' ' + printf "%s\n" A B C D E F G H I J K L M N O P Q R S T U V W X Y Z >z && + git add z && + git commit -m "Initial" && + + test_seq 1 130 >z && + echo lame >somefile && + git add z somefile && + git commit -m "Rewrite z, introduce lame somefile" && + + echo Content >somefile && + git add somefile && + git commit -m "Rewrite somefile" && + + git log -B --follow somefile +' + test_done diff --git a/t/t4255-am-submodule.sh b/t/t4255-am-submodule.sh index a7ba08f728..e6679a01b4 100755 --- a/t/t4255-am-submodule.sh +++ b/t/t4255-am-submodule.sh @@ -19,7 +19,6 @@ am_3way () { $2 git am --3way patch } -KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1 test_submodule_switch_func "am_3way" test_expect_success 'setup diff.submodule' ' diff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh index eea19907b5..44f7d07759 100755 --- a/t/t4301-merge-tree-write-tree.sh +++ b/t/t4301-merge-tree-write-tree.sh @@ -73,6 +73,12 @@ test_expect_success 'Clean merge' ' test_cmp expect actual ' +# Repeat the previous test, but turn off rename detection +test_expect_success 'Failed merge without rename detection' ' + test_must_fail git -c diff.renames=false merge-tree --write-tree side1 side3 >out && + grep "CONFLICT (modify/delete): numbers deleted" out +' + test_expect_success 'Content merge and a few conflicts' ' git checkout side1^0 && test_must_fail git merge side2 && diff --git a/t/t5323-pack-redundant.sh b/t/t5323-pack-redundant.sh index 688cd9706c..bc30bc9652 100755 --- a/t/t5323-pack-redundant.sh +++ b/t/t5323-pack-redundant.sh @@ -36,7 +36,7 @@ relationship between packs and objects is as follows: . ./test-lib.sh -if ! test_have_prereq WITHOUT_BREAKING_CHANGES +if test_have_prereq WITH_BREAKING_CHANGES then skip_all='skipping git-pack-redundant tests; built with breaking changes' test_done diff --git a/t/t5329-pack-objects-cruft.sh b/t/t5329-pack-objects-cruft.sh index b71a0aef40..25ddda5cf3 100755 --- a/t/t5329-pack-objects-cruft.sh +++ b/t/t5329-pack-objects-cruft.sh @@ -360,43 +360,6 @@ test_expect_success 'expired objects are pruned' ' ) ' -test_expect_success 'repack --cruft generates a cruft pack' ' - git init repo && - test_when_finished "rm -fr repo" && - ( - cd repo && - - test_commit reachable && - git branch -M main && - git checkout --orphan other && - test_commit unreachable && - - git checkout main && - git branch -D other && - git tag -d unreachable && - # objects are not cruft if they are contained in the reflogs - git reflog expire --all --expire=all && - - git rev-list --objects --all --no-object-names >reachable.raw && - git cat-file --batch-all-objects --batch-check="%(objectname)" >objects && - sort <reachable.raw >reachable && - comm -13 reachable objects >unreachable && - - git repack --cruft -d && - - cruft=$(basename $(ls $packdir/pack-*.mtimes) .mtimes) && - pack=$(basename $(ls $packdir/pack-*.pack | grep -v $cruft) .pack) && - - git show-index <$packdir/$pack.idx >actual.raw && - cut -f2 -d" " actual.raw | sort >actual && - test_cmp reachable actual && - - git show-index <$packdir/$cruft.idx >actual.raw && - cut -f2 -d" " actual.raw | sort >actual && - test_cmp unreachable actual - ) -' - test_expect_success 'loose objects mtimes upsert others' ' git init repo && test_when_finished "rm -fr repo" && @@ -470,219 +433,6 @@ test_expect_success 'expiring cruft objects with git gc' ' ) ' -test_expect_success 'cruft packs are not included in geometric repack' ' - git init repo && - test_when_finished "rm -fr repo" && - ( - cd repo && - - test_commit reachable && - git repack -Ad && - git branch -M main && - - git checkout --orphan other && - test_commit cruft && - git repack -d && - - git checkout main && - git branch -D other && - git tag -d cruft && - git reflog expire --all --expire=all && - - git repack --cruft && - - find $packdir -type f | sort >before && - git repack --geometric=2 -d && - find $packdir -type f | sort >after && - - test_cmp before after - ) -' - -test_expect_success 'repack --geometric collects once-cruft objects' ' - git init repo && - test_when_finished "rm -fr repo" && - ( - cd repo && - - test_commit reachable && - git repack -Ad && - git branch -M main && - - git checkout --orphan other && - git rm -rf . && - test_commit --no-tag cruft && - cruft="$(git rev-parse HEAD)" && - - git checkout main && - git branch -D other && - git reflog expire --all --expire=all && - - # Pack the objects created in the previous step into a cruft - # pack. Intentionally leave loose copies of those objects - # around so we can pick them up in a subsequent --geometric - # reapack. - git repack --cruft && - - # Now make those objects reachable, and ensure that they are - # packed into the new pack created via a --geometric repack. - git update-ref refs/heads/other $cruft && - - # Without this object, the set of unpacked objects is exactly - # the set of objects already in the cruft pack. Tweak that set - # to ensure we do not overwrite the cruft pack entirely. - test_commit reachable2 && - - find $packdir -name "pack-*.idx" | sort >before && - git repack --geometric=2 -d && - find $packdir -name "pack-*.idx" | sort >after && - - { - git rev-list --objects --no-object-names $cruft && - git rev-list --objects --no-object-names reachable..reachable2 - } >want.raw && - sort want.raw >want && - - pack=$(comm -13 before after) && - git show-index <$pack >objects.raw && - - cut -d" " -f2 objects.raw | sort >got && - - test_cmp want got - ) -' - -test_expect_success 'cruft repack with no reachable objects' ' - git init repo && - test_when_finished "rm -fr repo" && - ( - cd repo && - - test_commit base && - git repack -ad && - - base="$(git rev-parse base)" && - - git for-each-ref --format="delete %(refname)" >in && - git update-ref --stdin <in && - git reflog expire --all --expire=all && - rm -fr .git/index && - - git repack --cruft -d && - - git cat-file -t $base - ) -' - -write_blob () { - test-tool genrandom "$@" >in && - git hash-object -w -t blob in -} - -find_pack () { - for idx in $(ls $packdir/pack-*.idx) - do - git show-index <$idx >out && - if grep -q "$1" out - then - echo $idx - fi || return 1 - done -} - -test_expect_success 'cruft repack with --max-pack-size' ' - git init max-pack-size && - ( - cd max-pack-size && - test_commit base && - - # two cruft objects which exceed the maximum pack size - foo=$(write_blob foo 1048576) && - bar=$(write_blob bar 1048576) && - test-tool chmtime --get -1000 \ - "$objdir/$(test_oid_to_path $foo)" >foo.mtime && - test-tool chmtime --get -2000 \ - "$objdir/$(test_oid_to_path $bar)" >bar.mtime && - git repack --cruft --max-pack-size=1M && - find $packdir -name "*.mtimes" >cruft && - test_line_count = 2 cruft && - - foo_mtimes="$(basename $(find_pack $foo) .idx).mtimes" && - bar_mtimes="$(basename $(find_pack $bar) .idx).mtimes" && - test-tool pack-mtimes $foo_mtimes >foo.actual && - test-tool pack-mtimes $bar_mtimes >bar.actual && - - echo "$foo $(cat foo.mtime)" >foo.expect && - echo "$bar $(cat bar.mtime)" >bar.expect && - - test_cmp foo.expect foo.actual && - test_cmp bar.expect bar.actual && - test "$foo_mtimes" != "$bar_mtimes" - ) -' - -test_expect_success 'cruft repack with pack.packSizeLimit' ' - ( - cd max-pack-size && - # repack everything back together to remove the existing cruft - # pack (but to keep its objects) - git repack -adk && - git -c pack.packSizeLimit=1M repack --cruft && - # ensure the same post condition is met when --max-pack-size - # would otherwise be inferred from the configuration - find $packdir -name "*.mtimes" >cruft && - test_line_count = 2 cruft && - for pack in $(cat cruft) - do - test-tool pack-mtimes "$(basename $pack)" >objects && - test_line_count = 1 objects || return 1 - done - ) -' - -test_expect_success 'cruft repack respects repack.cruftWindow' ' - git init repo && - test_when_finished "rm -fr repo" && - ( - cd repo && - - test_commit base && - - GIT_TRACE2_EVENT=$(pwd)/event.trace \ - git -c pack.window=1 -c repack.cruftWindow=2 repack \ - --cruft --window=3 && - - grep "pack-objects.*--window=2.*--cruft" event.trace - ) -' - -test_expect_success 'cruft repack respects --window by default' ' - git init repo && - test_when_finished "rm -fr repo" && - ( - cd repo && - - test_commit base && - - GIT_TRACE2_EVENT=$(pwd)/event.trace \ - git -c pack.window=2 repack --cruft --window=3 && - - grep "pack-objects.*--window=3.*--cruft" event.trace - ) -' - -test_expect_success 'cruft repack respects --quiet' ' - git init repo && - test_when_finished "rm -fr repo" && - ( - cd repo && - - test_commit base && - GIT_PROGRESS_DELAY=0 git repack --cruft --quiet 2>err && - test_must_be_empty err - ) -' - test_expect_success 'cruft --local drops unreachable objects' ' git init alternate && git init repo && @@ -945,4 +695,56 @@ test_expect_success 'additional cruft blobs via gc.recentObjectsHook' ' ) ' +test_expect_success 'split cruft packs with --max-cruft-size' ' + repo=cruft-with--max-cruft-size && + test_when_finished "rm -fr $repo" && + + git init "$repo" && + + ( + cd "$repo" && + + git config core.compression 0 && + + sz=$((1024 * 1024)) && # 1MiB + test-tool genrandom foo $sz >foo && + test-tool genrandom bar $sz >bar && + foo="$(git hash-object -w -t blob foo)" && + bar="$(git hash-object -w -t blob bar)" && + + to=$packdir/pack && + # Pack together foo and bar into a single 2MiB pack. + pack="$(git pack-objects $to <<-EOF + $foo + $bar + EOF + )" && + + # Then generate a cruft pack containing foo and bar. + # + # Generate the pack with --max-pack-size equal to the + # size of one object, forcing us to write two cruft + # packs. + git pack-objects --cruft --max-pack-size=$sz $to <<-EOF && + -pack-$pack.pack + EOF + + ls $packdir/pack-*.mtimes >crufts && + test_line_count = 2 crufts && + + for cruft in $(cat crufts) + do + test-tool pack-mtimes "$(basename "$cruft")" || return 1 + done >actual.raw && + + cut -d" " -f1 <actual.raw | sort >actual && + sort >expect <<-EOF && + $foo + $bar + EOF + + test_cmp expect actual + ) +' + test_done diff --git a/t/t5334-incremental-multi-pack-index.sh b/t/t5334-incremental-multi-pack-index.sh index 26257e5660..d30d7253d6 100755 --- a/t/t5334-incremental-multi-pack-index.sh +++ b/t/t5334-incremental-multi-pack-index.sh @@ -44,4 +44,91 @@ test_expect_success 'convert incremental to non-incremental' ' compare_results_with_midx 'non-incremental MIDX conversion' +write_midx_layer () { + n=1 + if test -f $midx_chain + then + n="$(($(wc -l <$midx_chain) + 1))" + fi + + for i in 1 2 + do + test_commit $n.$i && + git repack -d || return 1 + done && + git multi-pack-index write --bitmap --incremental +} + +test_expect_success 'write initial MIDX layer' ' + git repack -ad && + write_midx_layer +' + +test_expect_success 'read bitmap from first MIDX layer' ' + git rev-list --test-bitmap 1.2 +' + +test_expect_success 'write another MIDX layer' ' + write_midx_layer +' + +test_expect_success 'midx verify with multiple layers' ' + test_path_is_file "$midx_chain" && + test_line_count = 2 "$midx_chain" && + + git multi-pack-index verify +' + +test_expect_success 'read bitmap from second MIDX layer' ' + git rev-list --test-bitmap 2.2 +' + +test_expect_success 'read earlier bitmap from second MIDX layer' ' + git rev-list --test-bitmap 1.2 +' + +test_expect_success 'show object from first pack' ' + git cat-file -p 1.1 +' + +test_expect_success 'show object from second pack' ' + git cat-file -p 2.2 +' + +for reuse in false single multi +do + test_expect_success "full clone (pack.allowPackReuse=$reuse)" ' + rm -fr clone.git && + + git config pack.allowPackReuse $reuse && + git clone --no-local --bare . clone.git + ' +done + +test_expect_success 'relink existing MIDX layer' ' + rm -fr "$midxdir" && + + GIT_TEST_MIDX_WRITE_REV=1 git multi-pack-index write --bitmap && + + midx_hash="$(test-tool read-midx --checksum $objdir)" && + + test_path_is_file "$packdir/multi-pack-index" && + test_path_is_file "$packdir/multi-pack-index-$midx_hash.bitmap" && + test_path_is_file "$packdir/multi-pack-index-$midx_hash.rev" && + + test_commit another && + git repack -d && + git multi-pack-index write --bitmap --incremental && + + test_path_is_missing "$packdir/multi-pack-index" && + test_path_is_missing "$packdir/multi-pack-index-$midx_hash.bitmap" && + test_path_is_missing "$packdir/multi-pack-index-$midx_hash.rev" && + + test_path_is_file "$midxdir/multi-pack-index-$midx_hash.midx" && + test_path_is_file "$midxdir/multi-pack-index-$midx_hash.bitmap" && + test_path_is_file "$midxdir/multi-pack-index-$midx_hash.rev" && + test_line_count = 2 "$midx_chain" + +' + test_done diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh index 3f81f16e13..8f018d2f23 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -55,6 +55,13 @@ test_expect_success setup ' echo Rebase && git log' +test_expect_success 'send-pack does not crash with -h' ' + test_expect_code 129 git send-pack -h >usage && + test_grep "[Uu]sage: git send-pack " usage && + test_expect_code 129 nongit git send-pack -h >usage && + test_grep "[Uu]sage: git send-pack " usage +' + test_expect_success 'pack the source repository' ' git repack -a -d && git prune diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index bb7e0c6879..82fccf8e36 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -1123,7 +1123,7 @@ Pull: refs/heads/main:refs/heads/origin Pull: refs/heads/next:refs/heads/origin2 EOF -test_expect_success WITHOUT_BREAKING_CHANGES 'migrate a remote from named file in $GIT_DIR/remotes' ' +test_expect_success !WITH_BREAKING_CHANGES 'migrate a remote from named file in $GIT_DIR/remotes' ' git clone one five && origin_url=$(pwd)/one && ( @@ -1149,7 +1149,7 @@ test_expect_success WITHOUT_BREAKING_CHANGES 'migrate a remote from named file i ) ' -test_expect_success WITHOUT_BREAKING_CHANGES 'migrate a remote from named file in $GIT_DIR/branches' ' +test_expect_success !WITH_BREAKING_CHANGES 'migrate a remote from named file in $GIT_DIR/branches' ' git clone --template= one six && origin_url=$(pwd)/one && ( @@ -1165,7 +1165,7 @@ test_expect_success WITHOUT_BREAKING_CHANGES 'migrate a remote from named file i ) ' -test_expect_success WITHOUT_BREAKING_CHANGES 'migrate a remote from named file in $GIT_DIR/branches (2)' ' +test_expect_success !WITH_BREAKING_CHANGES 'migrate a remote from named file in $GIT_DIR/branches (2)' ' git clone --template= one seven && ( cd seven && diff --git a/t/t5515-fetch-merge-logic.sh b/t/t5515-fetch-merge-logic.sh index 4e6026c611..8ac04d742c 100755 --- a/t/t5515-fetch-merge-logic.sh +++ b/t/t5515-fetch-merge-logic.sh @@ -104,7 +104,7 @@ test_expect_success setup ' git config remote.config-glob.fetch refs/heads/*:refs/remotes/rem/* && remotes="$remotes config-glob" && - if test_have_prereq WITHOUT_BREAKING_CHANGES + if ! test_have_prereq WITH_BREAKING_CHANGES then mkdir -p .git/remotes && cat >.git/remotes/remote-explicit <<-\EOF && diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 85ed049627..dabcc5f811 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -495,7 +495,7 @@ test_expect_success 'push tag with non-existent, incomplete dest' ' ' -test_expect_success 'push sha1 with non-existent, incomplete dest' ' +test_expect_success 'push oid with non-existent, incomplete dest' ' mk_test testrepo && test_must_fail git push testrepo $(git rev-parse main):foo @@ -975,7 +975,7 @@ test_expect_success 'allow push to HEAD of non-bare repository (config)' ' ! grep "warning: updating the current branch" stderr ' -test_expect_success WITHOUT_BREAKING_CHANGES 'fetch with branches' ' +test_expect_success !WITH_BREAKING_CHANGES 'fetch with branches' ' mk_empty testrepo && git branch second $the_first_commit && git checkout second && @@ -991,7 +991,7 @@ test_expect_success WITHOUT_BREAKING_CHANGES 'fetch with branches' ' git checkout main ' -test_expect_success WITHOUT_BREAKING_CHANGES 'fetch with branches containing #' ' +test_expect_success !WITH_BREAKING_CHANGES 'fetch with branches containing #' ' mk_empty testrepo && mkdir testrepo/.git/branches && echo "..#second" > testrepo/.git/branches/branch2 && @@ -1005,7 +1005,7 @@ test_expect_success WITHOUT_BREAKING_CHANGES 'fetch with branches containing #' git checkout main ' -test_expect_success WITHOUT_BREAKING_CHANGES 'push with branches' ' +test_expect_success !WITH_BREAKING_CHANGES 'push with branches' ' mk_empty testrepo && git checkout second && @@ -1022,7 +1022,7 @@ test_expect_success WITHOUT_BREAKING_CHANGES 'push with branches' ' ) ' -test_expect_success WITHOUT_BREAKING_CHANGES 'push with branches containing #' ' +test_expect_success !WITH_BREAKING_CHANGES 'push with branches containing #' ' mk_empty testrepo && test_when_finished "rm -rf .git/branches" && @@ -1251,7 +1251,7 @@ do ' done -test_expect_success 'fetch exact SHA1' ' +test_expect_success 'fetch exact oid' ' mk_test testrepo heads/main hidden/one && git push testrepo main:refs/hidden/one && ( @@ -1297,7 +1297,7 @@ test_expect_success 'fetch exact SHA1' ' ) ' -test_expect_success 'fetch exact SHA1 in protocol v2' ' +test_expect_success 'fetch exact oid in protocol v2' ' mk_test testrepo heads/main hidden/one && git push testrepo main:refs/hidden/one && git -C testrepo config transfer.hiderefs refs/hidden && @@ -1312,8 +1312,10 @@ test_expect_success 'fetch exact SHA1 in protocol v2' ' test_must_fail git -C child cat-file -t $the_commit && # fetching the hidden object succeeds by default - # NEEDSWORK: should this match the v0 behavior instead? - git -C child fetch -v ../testrepo $the_commit:refs/heads/copy + GIT_TRACE_PACKET=$PWD/trace.out \ + git -C child fetch -v ../testrepo $the_commit:refs/heads/copy && + + test_grep ! "ref-prefix.*$the_commit" trace.out ' for configallowtipsha1inwant in true false 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/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh index d3df81e785..4d0cbe9872 100755 --- a/t/t5702-protocol-v2.sh +++ b/t/t5702-protocol-v2.sh @@ -665,7 +665,7 @@ test_expect_success 'even with handcrafted request, filter does not work if not test-tool -C server serve-v2 --stateless-rpc <in >/dev/null ' -test_expect_success 'default refspec is used to filter ref when fetchcing' ' +test_expect_success 'default refspec is used to filter ref when fetching' ' test_when_finished "rm -f log" && GIT_TRACE_PACKET="$(pwd)/log" git -C file_child -c protocol.version=2 \ @@ -679,6 +679,48 @@ test_expect_success 'default refspec is used to filter ref when fetchcing' ' grep "ref-prefix refs/tags/" log ' +test_expect_success 'set up parent for prefix tests' ' + git init prefix-parent && + git -C prefix-parent commit --allow-empty -m foo && + git -C prefix-parent tag my-tag && + git -C prefix-parent branch unrelated-branch +' + +test_expect_success 'empty refspec filters refs when fetching' ' + git init configless-child && + + test_when_finished "rm -f log" && + GIT_TRACE_PACKET="$(pwd)/log" \ + git -C configless-child fetch ../prefix-parent && + test_grep ! unrelated-branch log +' + +test_expect_success 'exact oid fetch with tag following' ' + git init exact-oid-tags && + + commit=$(git -C prefix-parent rev-parse --verify HEAD) && + + test_when_finished "rm -f log" && + GIT_TRACE_PACKET="$(pwd)/log" \ + git -C exact-oid-tags fetch ../prefix-parent \ + $commit:refs/heads/exact && + test_grep ! unrelated-branch log && + git -C exact-oid-tags rev-parse --verify my-tag +' + +test_expect_success 'exact oid fetch avoids pointless HEAD request' ' + git init exact-oid-head && + git -C exact-oid-head remote add origin ../prefix-parent && + + commit=$(git -C prefix-parent rev-parse --verify HEAD) && + + test_when_finished "rm -f log" && + GIT_TRACE_PACKET="$(pwd)/log" \ + git -C exact-oid-head fetch --no-tags origin \ + $commit:refs/heads/exact && + test_grep ! command=ls-refs log +' + test_expect_success 'fetch supports various ways of have lines' ' rm -rf server client trace && git init server && diff --git a/t/t5710-promisor-remote-capability.sh b/t/t5710-promisor-remote-capability.sh index d2cc69a17e..b35b774235 100755 --- a/t/t5710-promisor-remote-capability.sh +++ b/t/t5710-promisor-remote-capability.sh @@ -93,6 +93,7 @@ test_expect_success "setup for testing promisor remote advertisement" ' test_expect_success "clone with promisor.advertise set to 'true'" ' git -C server config promisor.advertise true && + test_when_finished "rm -rf client" && # Clone from server to create a client GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \ @@ -100,7 +101,6 @@ test_expect_success "clone with promisor.advertise set to 'true'" ' -c remote.lop.url="file://$(pwd)/lop" \ -c promisor.acceptfromserver=All \ --no-local --filter="blob:limit=5k" server client && - test_when_finished "rm -rf client" && # Check that the largest object is still missing on the server check_missing_objects server 1 "$oid" @@ -108,6 +108,7 @@ test_expect_success "clone with promisor.advertise set to 'true'" ' test_expect_success "clone with promisor.advertise set to 'false'" ' git -C server config promisor.advertise false && + test_when_finished "rm -rf client" && # Clone from server to create a client GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \ @@ -115,7 +116,6 @@ test_expect_success "clone with promisor.advertise set to 'false'" ' -c remote.lop.url="file://$(pwd)/lop" \ -c promisor.acceptfromserver=All \ --no-local --filter="blob:limit=5k" server client && - test_when_finished "rm -rf client" && # Check that the largest object is not missing on the server check_missing_objects server 0 "" && @@ -126,6 +126,7 @@ test_expect_success "clone with promisor.advertise set to 'false'" ' test_expect_success "clone with promisor.acceptfromserver set to 'None'" ' git -C server config promisor.advertise true && + test_when_finished "rm -rf client" && # Clone from server to create a client GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \ @@ -133,7 +134,6 @@ test_expect_success "clone with promisor.acceptfromserver set to 'None'" ' -c remote.lop.url="file://$(pwd)/lop" \ -c promisor.acceptfromserver=None \ --no-local --filter="blob:limit=5k" server client && - test_when_finished "rm -rf client" && # Check that the largest object is not missing on the server check_missing_objects server 0 "" && @@ -144,8 +144,8 @@ test_expect_success "clone with promisor.acceptfromserver set to 'None'" ' test_expect_success "init + fetch with promisor.advertise set to 'true'" ' git -C server config promisor.advertise true && - test_when_finished "rm -rf client" && + mkdir client && git -C client init && git -C client config remote.lop.promisor true && @@ -162,6 +162,7 @@ test_expect_success "init + fetch with promisor.advertise set to 'true'" ' test_expect_success "clone with promisor.acceptfromserver set to 'KnownName'" ' git -C server config promisor.advertise true && + test_when_finished "rm -rf client" && # Clone from server to create a client GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \ @@ -169,7 +170,6 @@ test_expect_success "clone with promisor.acceptfromserver set to 'KnownName'" ' -c remote.lop.url="file://$(pwd)/lop" \ -c promisor.acceptfromserver=KnownName \ --no-local --filter="blob:limit=5k" server client && - test_when_finished "rm -rf client" && # Check that the largest object is still missing on the server check_missing_objects server 1 "$oid" @@ -177,6 +177,7 @@ test_expect_success "clone with promisor.acceptfromserver set to 'KnownName'" ' test_expect_success "clone with 'KnownName' and different remote names" ' git -C server config promisor.advertise true && + test_when_finished "rm -rf client" && # Clone from server to create a client GIT_NO_LAZY_FETCH=0 git clone -c remote.serverTwo.promisor=true \ @@ -184,8 +185,26 @@ test_expect_success "clone with 'KnownName' and different remote names" ' -c remote.serverTwo.url="file://$(pwd)/lop" \ -c promisor.acceptfromserver=KnownName \ --no-local --filter="blob:limit=5k" server client && + + # Check that the largest object is not missing on the server + check_missing_objects server 0 "" && + + # Reinitialize server so that the largest object is missing again + initialize_server 1 "$oid" +' + +test_expect_success "clone with 'KnownName' and missing URL in the config" ' + git -C server config promisor.advertise true && test_when_finished "rm -rf client" && + # Clone from server to create a client + # Lazy fetching by the client from the LOP will fail because of the + # missing URL in the client config, so the server will have to lazy + # fetch from the LOP. + GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \ + -c promisor.acceptfromserver=KnownName \ + --no-local --filter="blob:limit=5k" server client && + # Check that the largest object is not missing on the server check_missing_objects server 0 "" && @@ -195,6 +214,7 @@ test_expect_success "clone with 'KnownName' and different remote names" ' test_expect_success "clone with promisor.acceptfromserver set to 'KnownUrl'" ' git -C server config promisor.advertise true && + test_when_finished "rm -rf client" && # Clone from server to create a client GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \ @@ -202,7 +222,6 @@ test_expect_success "clone with promisor.acceptfromserver set to 'KnownUrl'" ' -c remote.lop.url="file://$(pwd)/lop" \ -c promisor.acceptfromserver=KnownUrl \ --no-local --filter="blob:limit=5k" server client && - test_when_finished "rm -rf client" && # Check that the largest object is still missing on the server check_missing_objects server 1 "$oid" @@ -212,6 +231,7 @@ test_expect_success "clone with 'KnownUrl' and different remote urls" ' ln -s lop serverTwo && git -C server config promisor.advertise true && + test_when_finished "rm -rf client" && # Clone from server to create a client GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \ @@ -219,7 +239,6 @@ test_expect_success "clone with 'KnownUrl' and different remote urls" ' -c remote.lop.url="file://$(pwd)/serverTwo" \ -c promisor.acceptfromserver=KnownUrl \ --no-local --filter="blob:limit=5k" server client && - test_when_finished "rm -rf client" && # Check that the largest object is not missing on the server check_missing_objects server 0 "" && @@ -228,6 +247,48 @@ test_expect_success "clone with 'KnownUrl' and different remote urls" ' initialize_server 1 "$oid" ' +test_expect_success "clone with 'KnownUrl' and url not configured on the server" ' + git -C server config promisor.advertise true && + test_when_finished "rm -rf client" && + + test_when_finished "git -C server config set remote.lop.url \"file://$(pwd)/lop\"" && + git -C server config unset remote.lop.url && + + # Clone from server to create a client + # It should fail because the client will reject the LOP as URLs are + # different, and the server cannot lazy fetch as the LOP URL is + # missing, so the remote name will be used instead which will fail. + test_must_fail env GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \ + -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \ + -c remote.lop.url="file://$(pwd)/lop" \ + -c promisor.acceptfromserver=KnownUrl \ + --no-local --filter="blob:limit=5k" server client && + + # Check that the largest object is still missing on the server + check_missing_objects server 1 "$oid" +' + +test_expect_success "clone with 'KnownUrl' and empty url, so not advertised" ' + git -C server config promisor.advertise true && + test_when_finished "rm -rf client" && + + test_when_finished "git -C server config set remote.lop.url \"file://$(pwd)/lop\"" && + git -C server config set remote.lop.url "" && + + # Clone from server to create a client + # It should fail because the client will reject the LOP as an empty URL is + # not advertised, and the server cannot lazy fetch as the LOP URL is empty, + # so the remote name will be used instead which will fail. + test_must_fail env GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \ + -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \ + -c remote.lop.url="file://$(pwd)/lop" \ + -c promisor.acceptfromserver=KnownUrl \ + --no-local --filter="blob:limit=5k" server client && + + # Check that the largest object is still missing on the server + check_missing_objects server 1 "$oid" +' + test_expect_success "clone with promisor.advertise set to 'true' but don't delete the client" ' git -C server config promisor.advertise true && diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index 76843a6169..256ccaefb7 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -292,15 +292,23 @@ test_expect_success 'name-rev --annotate-stdin' ' echo "$rev ($name)" >>expect.unsorted || return 1 done && sort <expect.unsorted >expect && - git rev-list --all | git name-rev --annotate-stdin >actual.unsorted && + git rev-list --all >list && + git name-rev --annotate-stdin <list >actual.unsorted && sort <actual.unsorted >actual && test_cmp expect actual ' -test_expect_success 'name-rev --stdin deprecated' " - git rev-list --all | git name-rev --stdin 2>actual && - grep -E 'warning: --stdin is deprecated' actual -" +test_expect_success 'name-rev --stdin deprecated' ' + git rev-list --all >list && + if ! test_have_prereq WITH_BREAKING_CHANGES + then + git name-rev --stdin <list 2>actual && + test_grep "warning: --stdin is deprecated" actual + else + test_must_fail git name-rev --stdin <list 2>actual && + test_grep "unknown option .stdin." actual + fi +' test_expect_success 'describe --contains with the exact tags' ' echo "A^0" >expect && diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index a5c7794385..9b4f4306c4 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -292,6 +292,13 @@ test_expect_success 'Check invalid atoms names are errors' ' test_must_fail git for-each-ref --format="%(INVALID)" refs/heads ' +test_expect_success 'for-each-ref does not crash with -h' ' + test_expect_code 129 git for-each-ref -h >usage && + test_grep "[Uu]sage: git for-each-ref " usage && + test_expect_code 129 nongit git for-each-ref -h >usage && + test_grep "[Uu]sage: git for-each-ref " usage +' + test_expect_success 'Check format specifiers are ignored in naming date atoms' ' git for-each-ref --format="%(authordate)" refs/heads && git for-each-ref --format="%(authordate:default) %(authordate)" refs/heads && diff --git a/t/t6423-merge-rename-directories.sh b/t/t6423-merge-rename-directories.sh index 94080c65d1..e0785410cd 100755 --- a/t/t6423-merge-rename-directories.sh +++ b/t/t6423-merge-rename-directories.sh @@ -5363,6 +5363,47 @@ test_expect_merge_algorithm failure success '12m: Change parent of renamed-dir t ) ' +test_setup_12n () { + git init 12n && + ( + cd 12n && + + mkdir tools && + echo hello >tools/hello && + git add tools/hello && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git switch A && + echo world >world && + git add world && + git commit -q world -m 'Add world' && + + git mv world tools/world && + git commit -m "Move world into tools/" && + + git switch B && + git mv tools/hello hello && + git commit -m "Move hello from tools/ to toplevel" + ) +} + +test_expect_success '12n: Directory rename transitively makes rename back to self' ' + test_setup_12n && + ( + cd 12n && + + git checkout -q B^0 && + + test_must_fail git cherry-pick A^0 >out && + grep "CONFLICT (file location).*should perhaps be moved" out + ) +' + + ########################################################################### # SECTION 13: Checking informational and conflict messages # @@ -5549,9 +5590,9 @@ test_expect_success '13b(info): messages for transitive rename with conflicted c # Commit A: y/{b,c,d}, x/e # Commit B: z/{b,c,d}, x/e # Expected: y/{b,c,d}, x/e, with info or conflict messages for d -# A: renamed x/d -> z/d; B: renamed z/ -> y/ AND renamed x/d to y/d -# One could argue A had partial knowledge of what was done with -# d and B had full knowledge, but that's a slippery slope as +# B: renamed x/d -> z/d; A: renamed z/ -> y/ AND renamed x/d to y/d +# One could argue B had partial knowledge of what was done with +# d and A had full knowledge, but that's a slippery slope as # shown in testcase 13d. test_setup_13c () { diff --git a/t/t6427-diff3-conflict-markers.sh b/t/t6427-diff3-conflict-markers.sh index dd5fe6a402..57569c4f4b 100755 --- a/t/t6427-diff3-conflict-markers.sh +++ b/t/t6427-diff3-conflict-markers.sh @@ -207,7 +207,7 @@ test_expect_success 'rebase --apply describes fake ancestor base' ' cd rebase && git rebase --abort && test_must_fail git -c merge.conflictstyle=diff3 rebase --apply main && - grep "||||||| constructed merge base" file + grep "||||||| constructed fake ancestor" file ) ' diff --git a/t/t7030-verify-tag.sh b/t/t7030-verify-tag.sh index 6f526c37c2..2c147072c1 100755 --- a/t/t7030-verify-tag.sh +++ b/t/t7030-verify-tag.sh @@ -7,6 +7,13 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh . "$TEST_DIRECTORY/lib-gpg.sh" +test_expect_success GPG 'verify-tag does not crash with -h' ' + test_expect_code 129 git verify-tag -h >usage && + test_grep "[Uu]sage: git verify-tag " usage && + test_expect_code 129 nongit git verify-tag -h >usage && + test_grep "[Uu]sage: git verify-tag " usage +' + test_expect_success GPG 'create signed tags' ' echo 1 >file && git add file && test_tick && git commit -m initial && diff --git a/t/t7510-signed-commit.sh b/t/t7510-signed-commit.sh index 0d2dd29fe6..39677e859a 100755 --- a/t/t7510-signed-commit.sh +++ b/t/t7510-signed-commit.sh @@ -8,6 +8,13 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME GNUPGHOME_NOT_USED=$GNUPGHOME . "$TEST_DIRECTORY/lib-gpg.sh" +test_expect_success GPG 'verify-commit does not crash with -h' ' + test_expect_code 129 git verify-commit -h >usage && + test_grep "[Uu]sage: git verify-commit " usage && + test_expect_code 129 nongit git verify-commit -h >usage && + test_grep "[Uu]sage: git verify-commit " usage +' + test_expect_success GPG 'create signed commits' ' test_oid_cache <<-\EOF && header sha1:gpgsig diff --git a/t/t7615-diff-algo-with-mergy-operations.sh b/t/t7615-diff-algo-with-mergy-operations.sh index 3b1aad0167..ac5863e788 100755 --- a/t/t7615-diff-algo-with-mergy-operations.sh +++ b/t/t7615-diff-algo-with-mergy-operations.sh @@ -26,7 +26,7 @@ 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 c2 + test_must_fail git merge -s recursive -Xdiff-algorithm=myers c2 ' test_expect_success 'merge c2 to c1 with recursive merge strategy succeeds with -Xdiff-algorithm=histogram' ' @@ -42,7 +42,7 @@ test_expect_success 'merge c2 to c1 with recursive merge strategy succeeds with test_expect_success 'cherry-pick c2 to c1 with recursive merge strategy fails with the current default myers diff algorithm' ' git reset --hard c1 && - test_must_fail git cherry-pick -s recursive c2 + test_must_fail git cherry-pick -s recursive -Xdiff-algorithm=myers c2 ' test_expect_success 'cherry-pick c2 to c1 with recursive merge strategy succeeds with -Xdiff-algorithm=histogram' ' diff --git a/t/t7704-repack-cruft.sh b/t/t7704-repack-cruft.sh index 959e6e2648..8aebfb45f5 100755 --- a/t/t7704-repack-cruft.sh +++ b/t/t7704-repack-cruft.sh @@ -149,7 +149,7 @@ generate_cruft_pack () { echo "$packdir/pack-$pack.mtimes" } -test_expect_success '--max-cruft-size creates new packs when above threshold' ' +test_expect_success '--max-cruft-size creates new packs when too large' ' git init max-cruft-size-large && ( cd max-cruft-size-large && @@ -173,7 +173,7 @@ test_expect_success '--max-cruft-size creates new packs when above threshold' ' ) ' -test_expect_success '--max-cruft-size combines existing packs when below threshold' ' +test_expect_success '--max-cruft-size combines existing packs when not too large' ' git init max-cruft-size-small && ( cd max-cruft-size-small && @@ -194,10 +194,13 @@ test_expect_success '--max-cruft-size combines existing packs when below thresho ) ' -test_expect_success '--max-cruft-size combines smaller packs first' ' - git init max-cruft-size-consume-small && +test_expect_success '--combine-cruft-below-size combines packs' ' + repo=combine-cruft-below-size && + test_when_finished "rm -fr $repo" && + + git init "$repo" && ( - cd max-cruft-size-consume-small && + cd "$repo" && test_commit base && git repack -ad && @@ -211,11 +214,11 @@ test_expect_success '--max-cruft-size combines smaller packs first' ' test-tool pack-mtimes "$(basename $cruft_bar)" >>expect.raw && sort expect.raw >expect.objects && - # repacking with `--max-cruft-size=2M` should combine - # both 0.5 MiB packs together, instead of, say, one of - # the 0.5 MiB packs with the 1.0 MiB pack + # Repacking with `--combine-cruft-below-size=1M` + # should combine both 0.5 MiB packs together, but + # ignore the two packs which are >= 1.0 MiB. ls $packdir/pack-*.mtimes | sort >cruft.before && - git repack -d --cruft --max-cruft-size=2M && + git repack -d --cruft --combine-cruft-below-size=1M && ls $packdir/pack-*.mtimes | sort >cruft.after && comm -13 cruft.before cruft.after >cruft.new && @@ -224,11 +227,12 @@ test_expect_success '--max-cruft-size combines smaller packs first' ' test_line_count = 1 cruft.new && test_line_count = 2 cruft.removed && - # the two smaller packs should be rolled up first + # The two packs smaller than 1.0MiB should be repacked + # together. printf "%s\n" $cruft_foo $cruft_bar | sort >expect.removed && test_cmp expect.removed cruft.removed && - # ...and contain the set of objects rolled up + # ...and contain the set of objects rolled up. test-tool pack-mtimes "$(basename $(cat cruft.new))" >actual.raw && sort actual.raw >actual.objects && @@ -236,10 +240,10 @@ test_expect_success '--max-cruft-size combines smaller packs first' ' ) ' -test_expect_success 'setup --max-cruft-size with freshened objects' ' - git init max-cruft-size-freshen && +test_expect_success 'setup cruft with freshened objects' ' + git init cruft-freshen && ( - cd max-cruft-size-freshen && + cd cruft-freshen && test_commit base && git repack -ad && @@ -257,9 +261,9 @@ test_expect_success 'setup --max-cruft-size with freshened objects' ' ) ' -test_expect_success '--max-cruft-size with freshened objects (loose)' ' +test_expect_success 'cruft with freshened objects (loose)' ' ( - cd max-cruft-size-freshen && + cd cruft-freshen && # regenerate the object, setting its mtime to be more recent foo="$(generate_random_blob foo 64)" && @@ -275,9 +279,9 @@ test_expect_success '--max-cruft-size with freshened objects (loose)' ' ) ' -test_expect_success '--max-cruft-size with freshened objects (packed)' ' +test_expect_success 'cruft with freshened objects (packed)' ' ( - cd max-cruft-size-freshen && + cd cruft-freshen && # regenerate the object and store it in a packfile, # setting its mtime to be more recent @@ -304,6 +308,70 @@ test_expect_success '--max-cruft-size with freshened objects (packed)' ' ) ' +test_expect_success 'multi-cruft with freshened objects (previously cruft)' ' + repo="max-cruft-size-threshold" && + + test_when_finished "rm -fr $repo" && + git init "$repo" && + ( + cd "$repo" && + + test_commit base && + foo="$(generate_random_blob foo $((2*1024*1024)))" && + bar="$(generate_random_blob bar $((2*1024*1024)))" && + baz="$(generate_random_blob baz $((2*1024*1024)))" && + + test-tool chmtime --get -100000 \ + "$objdir/$(test_oid_to_path "$foo")" >foo.old && + test-tool chmtime --get -100000 \ + "$objdir/$(test_oid_to_path "$bar")" >bar.old && + test-tool chmtime --get -100000 \ + "$objdir/$(test_oid_to_path "$baz")" >baz.old && + + git repack --cruft -d && + + # Make an identical copy of foo stored in a pack with a more + # recent mtime. + foo="$(generate_random_blob foo $((2*1024*1024)))" && + foo_pack="$(echo "$foo" | git pack-objects $packdir/pack)" && + test-tool chmtime --get -100 \ + "$packdir/pack-$foo_pack.pack" >foo.new && + git prune-packed && + + # Make a loose copy of bar, also with a more recent mtime. + bar="$(generate_random_blob bar $((2*1024*1024)))" && + test-tool chmtime --get -100 \ + "$objdir/$(test_oid_to_path "$bar")" >bar.new && + + # Make a new cruft object $quux to ensure we do not + # generate an identical pack to the existing cruft + # pack. + quux="$(generate_random_blob quux $((1024)))" && + test-tool chmtime --get -100 \ + "$objdir/$(test_oid_to_path "$quux")" >quux.new && + + git repack --cruft --max-cruft-size=3M -d && + + for p in $packdir/pack-*.mtimes + do + test-tool pack-mtimes "$(basename "$p")" || return 1 + done >actual.raw && + sort actual.raw >actual && + + # Among the set of all cruft packs, we should see the + # new mtimes for object $foo and $bar, as well as the + # single new copy of $baz. + sort >expect <<-EOF && + $foo $(cat foo.new) + $bar $(cat bar.new) + $baz $(cat baz.old) + $quux $(cat quux.new) + EOF + + test_cmp expect actual + ) +' + test_expect_success '--max-cruft-size with pruning' ' git init max-cruft-size-prune && ( @@ -411,4 +479,249 @@ test_expect_success 'reachable packs are preferred over cruft ones' ' ) ' +test_expect_success 'repack --cruft generates a cruft pack' ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + + test_commit reachable && + git branch -M main && + git checkout --orphan other && + test_commit unreachable && + + git checkout main && + git branch -D other && + git tag -d unreachable && + # objects are not cruft if they are contained in the reflogs + git reflog expire --all --expire=all && + + git rev-list --objects --all --no-object-names >reachable.raw && + git cat-file --batch-all-objects --batch-check="%(objectname)" >objects && + sort <reachable.raw >reachable && + comm -13 reachable objects >unreachable && + + git repack --cruft -d && + + cruft=$(basename $(ls $packdir/pack-*.mtimes) .mtimes) && + pack=$(basename $(ls $packdir/pack-*.pack | grep -v $cruft) .pack) && + + git show-index <$packdir/$pack.idx >actual.raw && + cut -f2 -d" " actual.raw | sort >actual && + test_cmp reachable actual && + + git show-index <$packdir/$cruft.idx >actual.raw && + cut -f2 -d" " actual.raw | sort >actual && + test_cmp unreachable actual + ) +' + +test_expect_success 'cruft packs are not included in geometric repack' ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + + test_commit reachable && + git repack -Ad && + git branch -M main && + + git checkout --orphan other && + test_commit cruft && + git repack -d && + + git checkout main && + git branch -D other && + git tag -d cruft && + git reflog expire --all --expire=all && + + git repack --cruft && + + find $packdir -type f | sort >before && + git repack --geometric=2 -d && + find $packdir -type f | sort >after && + + test_cmp before after + ) +' + +test_expect_success 'repack --geometric collects once-cruft objects' ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + + test_commit reachable && + git repack -Ad && + git branch -M main && + + git checkout --orphan other && + git rm -rf . && + test_commit --no-tag cruft && + cruft="$(git rev-parse HEAD)" && + + git checkout main && + git branch -D other && + git reflog expire --all --expire=all && + + # Pack the objects created in the previous step into a cruft + # pack. Intentionally leave loose copies of those objects + # around so we can pick them up in a subsequent --geometric + # reapack. + git repack --cruft && + + # Now make those objects reachable, and ensure that they are + # packed into the new pack created via a --geometric repack. + git update-ref refs/heads/other $cruft && + + # Without this object, the set of unpacked objects is exactly + # the set of objects already in the cruft pack. Tweak that set + # to ensure we do not overwrite the cruft pack entirely. + test_commit reachable2 && + + find $packdir -name "pack-*.idx" | sort >before && + git repack --geometric=2 -d && + find $packdir -name "pack-*.idx" | sort >after && + + { + git rev-list --objects --no-object-names $cruft && + git rev-list --objects --no-object-names reachable..reachable2 + } >want.raw && + sort want.raw >want && + + pack=$(comm -13 before after) && + git show-index <$pack >objects.raw && + + cut -d" " -f2 objects.raw | sort >got && + + test_cmp want got + ) +' + +test_expect_success 'cruft repack with no reachable objects' ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + + test_commit base && + git repack -ad && + + base="$(git rev-parse base)" && + + git for-each-ref --format="delete %(refname)" >in && + git update-ref --stdin <in && + git reflog expire --all --expire=all && + rm -fr .git/index && + + git repack --cruft -d && + + git cat-file -t $base + ) +' + +find_pack () { + for idx in $(ls $packdir/pack-*.idx) + do + git show-index <$idx >out && + if grep -q "$1" out + then + echo $idx + fi || return 1 + done +} + +test_expect_success 'cruft repack with --max-pack-size' ' + git init max-pack-size && + ( + cd max-pack-size && + test_commit base && + + # two cruft objects which exceed the maximum pack size + foo=$(generate_random_blob foo 1048576) && + bar=$(generate_random_blob bar 1048576) && + test-tool chmtime --get -1000 \ + "$objdir/$(test_oid_to_path $foo)" >foo.mtime && + test-tool chmtime --get -2000 \ + "$objdir/$(test_oid_to_path $bar)" >bar.mtime && + git repack --cruft --max-pack-size=1M && + find $packdir -name "*.mtimes" >cruft && + test_line_count = 2 cruft && + + foo_mtimes="$(basename $(find_pack $foo) .idx).mtimes" && + bar_mtimes="$(basename $(find_pack $bar) .idx).mtimes" && + test-tool pack-mtimes $foo_mtimes >foo.actual && + test-tool pack-mtimes $bar_mtimes >bar.actual && + + echo "$foo $(cat foo.mtime)" >foo.expect && + echo "$bar $(cat bar.mtime)" >bar.expect && + + test_cmp foo.expect foo.actual && + test_cmp bar.expect bar.actual && + test "$foo_mtimes" != "$bar_mtimes" + ) +' + +test_expect_success 'cruft repack with pack.packSizeLimit' ' + ( + cd max-pack-size && + # repack everything back together to remove the existing cruft + # pack (but to keep its objects) + git repack -adk && + git -c pack.packSizeLimit=1M repack --cruft && + # ensure the same post condition is met when --max-pack-size + # would otherwise be inferred from the configuration + find $packdir -name "*.mtimes" >cruft && + test_line_count = 2 cruft && + for pack in $(cat cruft) + do + test-tool pack-mtimes "$(basename $pack)" >objects && + test_line_count = 1 objects || return 1 + done + ) +' + +test_expect_success 'cruft repack respects repack.cruftWindow' ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + + test_commit base && + + GIT_TRACE2_EVENT=$(pwd)/event.trace \ + git -c pack.window=1 -c repack.cruftWindow=2 repack \ + --cruft --window=3 && + + grep "pack-objects.*--window=2.*--cruft" event.trace + ) +' + +test_expect_success 'cruft repack respects --window by default' ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + + test_commit base && + + GIT_TRACE2_EVENT=$(pwd)/event.trace \ + git -c pack.window=2 repack --cruft --window=3 && + + grep "pack-objects.*--window=3.*--cruft" event.trace + ) +' + +test_expect_success 'cruft repack respects --quiet' ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + + test_commit base && + GIT_PROGRESS_DELAY=0 git repack --cruft --quiet 2>err && + test_must_be_empty err + ) +' + test_done diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh index 1909aed95e..834ddb5ad6 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) diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh index 40427883ec..304bac5b1d 100755 --- a/t/t9350-fast-export.sh +++ b/t/t9350-fast-export.sh @@ -8,6 +8,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh +. "$TEST_DIRECTORY/lib-gpg.sh" test_expect_success 'setup' ' @@ -253,6 +254,24 @@ test_expect_success 'signed-tags=verbatim' ' ' +test_expect_success 'signed-tags=warn-verbatim' ' + + git fast-export --signed-tags=warn-verbatim sign-your-name >output 2>err && + grep PGP output && + test -s err + +' + +# 'warn' is a backward-compatibility alias for 'warn-verbatim'; test +# that it keeps working. +test_expect_success 'signed-tags=warn' ' + + git fast-export --signed-tags=warn sign-your-name >output 2>err && + grep PGP output && + test -s err + +' + test_expect_success 'signed-tags=strip' ' git fast-export --signed-tags=strip sign-your-name > output && @@ -266,10 +285,107 @@ test_expect_success 'signed-tags=warn-strip' ' test -s err ' +test_expect_success GPG 'set up signed commit' ' + + # Generate a commit with both "gpgsig" and "encoding" set, so + # that we can test that fast-import gets the ordering correct + # between the two. + test_config i18n.commitEncoding ISO-8859-1 && + git checkout -f -b commit-signing main && + echo Sign your name >file-sign && + git add file-sign && + git commit -S -m "signed commit" && + COMMIT_SIGNING=$(git rev-parse --verify commit-signing) + +' + +test_expect_success GPG 'signed-commits default' ' + + sane_unset FAST_EXPORT_SIGNED_COMMITS_NOABORT && + test_must_fail git fast-export --reencode=no commit-signing && + + FAST_EXPORT_SIGNED_COMMITS_NOABORT=1 git fast-export --reencode=no commit-signing >output 2>err && + ! grep ^gpgsig output && + grep "^encoding ISO-8859-1" output && + test -s err && + sed "s/commit-signing/commit-strip-signing/" output | ( + cd new && + git fast-import && + STRIPPED=$(git rev-parse --verify refs/heads/commit-strip-signing) && + test $COMMIT_SIGNING != $STRIPPED + ) + +' + +test_expect_success GPG 'signed-commits=abort' ' + + test_must_fail git fast-export --signed-commits=abort commit-signing + +' + +test_expect_success GPG 'signed-commits=verbatim' ' + + git fast-export --signed-commits=verbatim --reencode=no commit-signing >output && + grep "^gpgsig sha" output && + grep "encoding ISO-8859-1" output && + ( + cd new && + git fast-import && + STRIPPED=$(git rev-parse --verify refs/heads/commit-signing) && + test $COMMIT_SIGNING = $STRIPPED + ) <output + +' + +test_expect_success GPG 'signed-commits=warn-verbatim' ' + + git fast-export --signed-commits=warn-verbatim --reencode=no commit-signing >output 2>err && + grep "^gpgsig sha" output && + grep "encoding ISO-8859-1" output && + test -s err && + ( + cd new && + git fast-import && + STRIPPED=$(git rev-parse --verify refs/heads/commit-signing) && + test $COMMIT_SIGNING = $STRIPPED + ) <output + +' + +test_expect_success GPG 'signed-commits=strip' ' + + git fast-export --signed-commits=strip --reencode=no commit-signing >output && + ! grep ^gpgsig output && + grep "^encoding ISO-8859-1" output && + sed "s/commit-signing/commit-strip-signing/" output | ( + cd new && + git fast-import && + STRIPPED=$(git rev-parse --verify refs/heads/commit-strip-signing) && + test $COMMIT_SIGNING != $STRIPPED + ) + +' + +test_expect_success GPG 'signed-commits=warn-strip' ' + + git fast-export --signed-commits=warn-strip --reencode=no commit-signing >output 2>err && + ! grep ^gpgsig output && + grep "^encoding ISO-8859-1" output && + test -s err && + sed "s/commit-signing/commit-strip-signing/" output | ( + cd new && + git fast-import && + STRIPPED=$(git rev-parse --verify refs/heads/commit-strip-signing) && + test $COMMIT_SIGNING != $STRIPPED + ) + +' + test_expect_success 'setup submodule' ' test_config_global protocol.file.allow always && git checkout -f main && + test_might_fail git update-ref -d refs/heads/commit-signing && mkdir sub && ( cd sub && diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index 51bd750837..343b8cd191 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -149,7 +149,8 @@ fi test_expect_success 'setup for __git_find_repo_path/__gitdir tests' ' mkdir -p subdir/subsubdir && mkdir -p non-repo && - git init -b main otherrepo + git init -b main otherrepo && + git init -b main slashrepo ' test_expect_success '__git_find_repo_path - from command line (through $__git_dir)' ' @@ -455,6 +456,32 @@ test_expect_success '__git_dequote - open double quote' ' ' +test_expect_success '__git_count_path_components - no slashes' ' + echo 1 >expected && + __git_count_path_components a >"$actual" && + test_cmp expected "$actual" +' + +test_expect_success '__git_count_path_components - relative' ' + echo 3 >expected && + __git_count_path_components a/b/c >"$actual" && + test_cmp expected "$actual" + +' + +test_expect_success '__git_count_path_components - absolute' ' + echo 3 >expected && + __git_count_path_components /a/b/c >"$actual" && + test_cmp expected "$actual" +' + +test_expect_success '__git_count_path_components - trailing slash' ' + echo 3 >expected && + __git_count_path_components a/b/c/ >"$actual" && + test_cmp expected "$actual" +' + + test_expect_success '__gitcomp_direct - puts everything into COMPREPLY as-is' ' sed -e "s/Z$//g" >expected <<-EOF && with-trailing-space Z @@ -648,6 +675,13 @@ test_expect_success 'setup for ref completion' ' ) && git remote add other "$ROOT/otherrepo/.git" && git fetch --no-tags other && + ( + cd slashrepo && + git commit --allow-empty -m initial && + git branch -m main branch/with/slash + ) && + git remote add remote/with/slash "$ROOT/slashrepo/.git" && + git fetch --no-tags remote/with/slash && rm -f .git/FETCH_HEAD && git init thirdrepo ' @@ -660,6 +694,8 @@ test_expect_success '__git_refs - simple' ' other/HEAD other/branch-in-other other/main-in-other + remote/with/slash/HEAD + remote/with/slash/branch/with/slash matching-tag EOF ( @@ -676,6 +712,8 @@ test_expect_success '__git_refs - full refs' ' refs/remotes/other/HEAD refs/remotes/other/branch-in-other refs/remotes/other/main-in-other + refs/remotes/remote/with/slash/HEAD + refs/remotes/remote/with/slash/branch/with/slash refs/tags/matching-tag EOF ( @@ -741,6 +779,19 @@ test_expect_success '__git_refs - configured remote' ' test_cmp expected "$actual" ' +test_expect_success '__git_refs - configured remote - with slash' ' + cat >expected <<-EOF && + HEAD + HEAD + branch/with/slash + EOF + ( + cur= && + __git_refs remote/with/slash >"$actual" + ) && + test_cmp expected "$actual" +' + test_expect_success '__git_refs - configured remote - full refs' ' cat >expected <<-EOF && HEAD @@ -883,17 +934,19 @@ test_expect_success '__git_refs - unique remote branches for git checkout DWIMer other/ambiguous other/branch-in-other other/main-in-other - remote/ambiguous - remote/branch-in-remote + remote/with/slash/HEAD + remote/with/slash/ambiguous + remote/with/slash/branch-in-remote + remote/with/slash/branch/with/slash matching-tag - HEAD branch-in-other branch-in-remote + branch/with/slash main-in-other EOF for remote_ref in refs/remotes/other/ambiguous \ - refs/remotes/remote/ambiguous \ - refs/remotes/remote/branch-in-remote + refs/remotes/remote/with/slash/ambiguous \ + refs/remotes/remote/with/slash/branch-in-remote do git update-ref $remote_ref main && test_when_finished "git update-ref -d $remote_ref" || return 1 @@ -913,6 +966,8 @@ test_expect_success '__git_refs - after --opt=' ' other/HEAD other/branch-in-other other/main-in-other + remote/with/slash/HEAD + remote/with/slash/branch/with/slash matching-tag EOF ( @@ -929,6 +984,8 @@ test_expect_success '__git_refs - after --opt= - full refs' ' refs/remotes/other/HEAD refs/remotes/other/branch-in-other refs/remotes/other/main-in-other + refs/remotes/remote/with/slash/HEAD + refs/remotes/remote/with/slash/branch/with/slash refs/tags/matching-tag EOF ( @@ -946,6 +1003,8 @@ test_expect_success '__git refs - excluding refs' ' ^other/HEAD ^other/branch-in-other ^other/main-in-other + ^remote/with/slash/HEAD + ^remote/with/slash/branch/with/slash ^matching-tag EOF ( @@ -962,6 +1021,8 @@ test_expect_success '__git refs - excluding full refs' ' ^refs/remotes/other/HEAD ^refs/remotes/other/branch-in-other ^refs/remotes/other/main-in-other + ^refs/remotes/remote/with/slash/HEAD + ^refs/remotes/remote/with/slash/branch/with/slash ^refs/tags/matching-tag EOF ( @@ -989,6 +1050,8 @@ test_expect_success '__git_refs - do not filter refs unless told so' ' other/branch-in-other other/main-in-other other/matching/branch-in-other + remote/with/slash/HEAD + remote/with/slash/branch/with/slash matching-tag matching/tag EOF @@ -1109,6 +1172,8 @@ test_expect_success '__git_complete_refs - simple' ' other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z matching-tag Z EOF ( @@ -1147,6 +1212,20 @@ test_expect_success '__git_complete_refs - remote' ' test_cmp expected out ' +test_expect_success '__git_complete_refs - remote - with slash' ' + sed -e "s/Z$//" >expected <<-EOF && + HEAD Z + HEAD Z + branch/with/slash Z + EOF + ( + cur= && + __git_complete_refs --remote=remote/with/slash && + print_comp + ) && + test_cmp expected out +' + test_expect_success '__git_complete_refs - track' ' sed -e "s/Z$//" >expected <<-EOF && HEAD Z @@ -1155,9 +1234,11 @@ test_expect_success '__git_complete_refs - track' ' other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z matching-tag Z - HEAD Z branch-in-other Z + branch/with/slash Z main-in-other Z EOF ( @@ -1202,6 +1283,8 @@ test_expect_success '__git_complete_refs - suffix' ' other/HEAD. other/branch-in-other. other/main-in-other. + remote/with/slash/HEAD. + remote/with/slash/branch/with/slash. matching-tag. EOF ( @@ -1227,6 +1310,20 @@ test_expect_success '__git_complete_fetch_refspecs - simple' ' test_cmp expected out ' +test_expect_success '__git_complete_fetch_refspecs - with slash' ' + sed -e "s/Z$//" >expected <<-EOF && + HEAD:HEAD Z + HEAD:HEAD Z + branch/with/slash:branch/with/slash Z + EOF + ( + cur= && + __git_complete_fetch_refspecs remote/with/slash && + print_comp + ) && + test_cmp expected out +' + test_expect_success '__git_complete_fetch_refspecs - matching' ' sed -e "s/Z$//" >expected <<-EOF && branch-in-other:branch-in-other Z @@ -1307,8 +1404,8 @@ test_expect_success '__git_complete_worktree_paths with -C' ' test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' ' test_completion "git switch " <<-\EOF - HEAD Z branch-in-other Z + branch/with/slash Z main Z main-in-other Z matching-branch Z @@ -1454,8 +1551,8 @@ test_expect_success 'git-bisect - existing view subcommand is recognized and ena test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' ' test_completion "git checkout " <<-\EOF HEAD Z - HEAD Z branch-in-other Z + branch/with/slash Z main Z main-in-other Z matching-branch Z @@ -1463,6 +1560,8 @@ test_expect_success 'git checkout - completes refs and unique remote branches fo other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' @@ -1482,8 +1581,8 @@ test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, compl test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' ' GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git switch --guess " <<-\EOF - HEAD Z branch-in-other Z + branch/with/slash Z main Z main-in-other Z matching-branch Z @@ -1492,8 +1591,8 @@ test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_G test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' ' test_completion "git switch --no-guess --guess " <<-\EOF - HEAD Z branch-in-other Z + branch/with/slash Z main Z main-in-other Z matching-branch Z @@ -1516,14 +1615,16 @@ test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only complete other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' ' GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git checkout --guess " <<-\EOF HEAD Z - HEAD Z branch-in-other Z + branch/with/slash Z main Z main-in-other Z matching-branch Z @@ -1531,6 +1632,8 @@ test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' @@ -1543,14 +1646,16 @@ test_expect_success 'git checkout - with --no-guess, only completes refs' ' other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' ' test_completion "git checkout --no-guess --guess " <<-\EOF HEAD Z - HEAD Z branch-in-other Z + branch/with/slash Z main Z main-in-other Z matching-branch Z @@ -1558,6 +1663,8 @@ test_expect_success 'git checkout - a later --guess overrides previous --no-gues other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' @@ -1570,6 +1677,8 @@ test_expect_success 'git checkout - a later --no-guess overrides previous --gues other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' @@ -1583,6 +1692,8 @@ test_expect_success 'git checkout - with checkout.guess = false, only completes other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' @@ -1590,8 +1701,8 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a test_config checkout.guess true && test_completion "git checkout " <<-\EOF HEAD Z - HEAD Z branch-in-other Z + branch/with/slash Z main Z main-in-other Z matching-branch Z @@ -1599,6 +1710,8 @@ test_expect_success 'git checkout - with checkout.guess = true, completes refs a other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' @@ -1606,8 +1719,8 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout. test_config checkout.guess false && test_completion "git checkout --guess " <<-\EOF HEAD Z - HEAD Z branch-in-other Z + branch/with/slash Z main Z main-in-other Z matching-branch Z @@ -1615,6 +1728,8 @@ test_expect_success 'git checkout - a later --guess overrides previous checkout. other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' @@ -1628,6 +1743,8 @@ test_expect_success 'git checkout - a later --no-guess overrides previous checko other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' @@ -1640,6 +1757,8 @@ test_expect_success 'git switch - with --detach, complete all references' ' other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' @@ -1652,6 +1771,8 @@ test_expect_success 'git checkout - with --detach, complete only references' ' other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' @@ -1824,6 +1945,8 @@ test_expect_success 'git switch - with -d, complete all references' ' other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' @@ -1836,6 +1959,8 @@ test_expect_success 'git checkout - with -d, complete only references' ' other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' @@ -1844,11 +1969,15 @@ test_expect_success 'git switch - with --track, complete only remote branches' ' other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF test_completion "git switch -t " <<-\EOF other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' @@ -1857,11 +1986,15 @@ test_expect_success 'git checkout - with --track, complete only remote branches' other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF test_completion "git checkout -t " <<-\EOF other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' @@ -1881,6 +2014,8 @@ test_expect_success 'git checkout - with --no-track, complete only local referen other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' @@ -1893,6 +2028,8 @@ test_expect_success 'git switch - with -c, complete all references' ' other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' @@ -1905,6 +2042,8 @@ test_expect_success 'git switch - with -C, complete all references' ' other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' @@ -1917,6 +2056,8 @@ test_expect_success 'git switch - with -c and --track, complete all references' other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' @@ -1929,6 +2070,8 @@ test_expect_success 'git switch - with -C and --track, complete all references' other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' @@ -1941,6 +2084,8 @@ test_expect_success 'git switch - with -c and --no-track, complete all reference other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' @@ -1953,6 +2098,8 @@ test_expect_success 'git switch - with -C and --no-track, complete all reference other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' @@ -1965,6 +2112,8 @@ test_expect_success 'git checkout - with -b, complete all references' ' other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' @@ -1977,6 +2126,8 @@ test_expect_success 'git checkout - with -B, complete all references' ' other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' @@ -1989,6 +2140,8 @@ test_expect_success 'git checkout - with -b and --track, complete all references other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' @@ -2001,6 +2154,8 @@ test_expect_success 'git checkout - with -B and --track, complete all references other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' @@ -2013,6 +2168,8 @@ test_expect_success 'git checkout - with -b and --no-track, complete all referen other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' @@ -2025,13 +2182,15 @@ test_expect_success 'git checkout - with -B and --no-track, complete all referen other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' test_expect_success 'git switch - for -c, complete local branches and unique remote branches' ' test_completion "git switch -c " <<-\EOF - HEAD Z branch-in-other Z + branch/with/slash Z main Z main-in-other Z matching-branch Z @@ -2040,8 +2199,8 @@ test_expect_success 'git switch - for -c, complete local branches and unique rem test_expect_success 'git switch - for -C, complete local branches and unique remote branches' ' test_completion "git switch -C " <<-\EOF - HEAD Z branch-in-other Z + branch/with/slash Z main Z main-in-other Z matching-branch Z @@ -2078,8 +2237,8 @@ test_expect_success 'git switch - for -C with --no-track, complete local branche test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' ' test_completion "git checkout -b " <<-\EOF - HEAD Z branch-in-other Z + branch/with/slash Z main Z main-in-other Z matching-branch Z @@ -2088,8 +2247,8 @@ test_expect_success 'git checkout - for -b, complete local branches and unique r test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' ' test_completion "git checkout -B " <<-\EOF - HEAD Z branch-in-other Z + branch/with/slash Z main Z main-in-other Z matching-branch Z @@ -2126,8 +2285,8 @@ test_expect_success 'git checkout - for -B with --no-track, complete local branc test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' ' test_completion "git switch --orphan " <<-\EOF - HEAD Z branch-in-other Z + branch/with/slash Z main Z main-in-other Z matching-branch Z @@ -2142,8 +2301,8 @@ test_expect_success 'git switch - --orphan with branch already provided complete test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' ' test_completion "git checkout --orphan " <<-\EOF - HEAD Z branch-in-other Z + branch/with/slash Z main Z main-in-other Z matching-branch Z @@ -2159,6 +2318,8 @@ test_expect_success 'git checkout - --orphan with branch already provided comple other/HEAD Z other/branch-in-other Z other/main-in-other Z + remote/with/slash/HEAD Z + remote/with/slash/branch/with/slash Z EOF ' @@ -2173,7 +2334,8 @@ test_expect_success 'git restore completes modified files' ' test_expect_success 'teardown after ref completion' ' git branch -d matching-branch && git tag -d matching-tag && - git remote remove other + git remote remove other && + git remote remove remote/with/slash ' diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 79377bc0fc..16eaaaf4c3 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -773,6 +773,8 @@ mkdir -p "$TRASH_DIRECTORY/prereq-test-dir-'"$1"'" && rm -rf "$TRASH_DIRECTORY/prereq-test-dir-$1" if test "$eval_ret" = 0; then say >&3 "prerequisite $1 ok" + elif test "$eval_ret" = 125; then + :; else say >&3 "prerequisite $1 not satisfied" fi @@ -811,6 +813,9 @@ test_have_prereq () { if test_run_lazy_prereq_ "$prerequisite" "$script" then test_set_prereq $prerequisite + elif test $? = 125 + then + BUG "Do not use $prerequisite" fi lazily_tested_prereq="$lazily_tested_prereq$prerequisite " esac diff --git a/t/test-lib.sh b/t/test-lib.sh index 9001ed3a64..fffbfb89ef 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -1862,8 +1862,13 @@ test_lazy_prereq CURL ' curl --version ' +test_lazy_prereq WITH_BREAKING_CHANGES ' + test -n "$WITH_BREAKING_CHANGES" +' + test_lazy_prereq WITHOUT_BREAKING_CHANGES ' - test -z "$WITH_BREAKING_CHANGES" + # Signal that this prereq should not be used. + exit 125 ' # SHA1 is a test if the hash algorithm in use is SHA-1. This is both for tests diff --git a/t/unit-tests/clar/clar/fs.h b/t/unit-tests/clar/clar/fs.h index 8b206179fc..2203743fb4 100644 --- a/t/unit-tests/clar/clar/fs.h +++ b/t/unit-tests/clar/clar/fs.h @@ -376,9 +376,12 @@ fs_copydir_helper(const char *source, const char *dest, int dest_mode) mkdir(dest, dest_mode); cl_assert_(source_dir = opendir(source), "Could not open source dir"); - while ((d = (errno = 0, readdir(source_dir))) != NULL) { + for (;;) { char *child; + errno = 0; + if ((d = readdir(source_dir)) == NULL) + break; if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) continue; @@ -479,9 +482,12 @@ fs_rmdir_helper(const char *path) struct dirent *d; cl_assert_(dir = opendir(path), "Could not open dir"); - while ((d = (errno = 0, readdir(dir))) != NULL) { + for (;;) { char *child; + errno = 0; + if ((d = readdir(dir)) == NULL) + break; if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) continue; diff --git a/t/unit-tests/t-reftable-basics.c b/t/unit-tests/t-reftable-basics.c index 9ba7eb05ad..c9e751e49e 100644 --- a/t/unit-tests/t-reftable-basics.c +++ b/t/unit-tests/t-reftable-basics.c @@ -128,12 +128,30 @@ int cmd_main(int argc UNUSED, const char *argv[] UNUSED) reftable_buf_release(&b); } - if_test ("put_be24 and get_be24 work") { + if_test ("reftable_put_be64 and reftable_get_be64 work") { + uint64_t in = 0x1122334455667788; + uint8_t dest[8]; + uint64_t out; + reftable_put_be64(dest, in); + out = reftable_get_be64(dest); + check_int(in, ==, out); + } + + if_test ("reftable_put_be32 and reftable_get_be32 work") { + uint32_t in = 0x11223344; + uint8_t dest[4]; + uint32_t out; + reftable_put_be32(dest, in); + out = reftable_get_be32(dest); + check_int(in, ==, out); + } + + if_test ("reftable_put_be24 and reftable_get_be24 work") { uint32_t in = 0x112233; uint8_t dest[3]; uint32_t out; - put_be24(dest, in); - out = get_be24(dest); + reftable_put_be24(dest, in); + out = reftable_get_be24(dest); check_int(in, ==, out); } @@ -141,8 +159,8 @@ int cmd_main(int argc UNUSED, const char *argv[] UNUSED) uint32_t in = 0xfef1; uint8_t dest[3]; uint32_t out; - put_be16(dest, in); - out = get_be16(dest); + reftable_put_be16(dest, in); + out = reftable_get_be16(dest); check_int(in, ==, out); } diff --git a/t/unit-tests/t-reftable-pq.c b/t/unit-tests/t-reftable-pq.c index f3f8a0cdf3..c128fe8616 100644 --- a/t/unit-tests/t-reftable-pq.c +++ b/t/unit-tests/t-reftable-pq.c @@ -21,7 +21,9 @@ static void merged_iter_pqueue_check(const struct merged_iter_pqueue *pq) static int pq_entry_equal(struct pq_entry *a, struct pq_entry *b) { - return !reftable_record_cmp(a->rec, b->rec) && (a->index == b->index); + int cmp; + check(!reftable_record_cmp(a->rec, b->rec, &cmp)); + return !cmp && (a->index == b->index); } static void t_pq_record(void) @@ -32,7 +34,7 @@ static void t_pq_record(void) char *last = NULL; for (i = 0; i < N; i++) { - reftable_record_init(&recs[i], BLOCK_TYPE_REF); + check(!reftable_record_init(&recs[i], BLOCK_TYPE_REF)); recs[i].u.ref.refname = xstrfmt("%02"PRIuMAX, (uintmax_t)i); } @@ -49,7 +51,9 @@ static void t_pq_record(void) while (!merged_iter_pqueue_is_empty(pq)) { struct pq_entry top = merged_iter_pqueue_top(pq); - struct pq_entry e = merged_iter_pqueue_remove(&pq); + struct pq_entry e; + + check(!merged_iter_pqueue_remove(&pq, &e)); merged_iter_pqueue_check(&pq); check(pq_entry_equal(&top, &e)); @@ -72,7 +76,7 @@ static void t_pq_index(void) size_t N = ARRAY_SIZE(recs), i; for (i = 0; i < N; i++) { - reftable_record_init(&recs[i], BLOCK_TYPE_REF); + check(!reftable_record_init(&recs[i], BLOCK_TYPE_REF)); recs[i].u.ref.refname = (char *) "refs/heads/master"; } @@ -90,7 +94,9 @@ static void t_pq_index(void) for (i = N - 1; i > 0; i--) { struct pq_entry top = merged_iter_pqueue_top(pq); - struct pq_entry e = merged_iter_pqueue_remove(&pq); + struct pq_entry e; + + check(!merged_iter_pqueue_remove(&pq, &e)); merged_iter_pqueue_check(&pq); check(pq_entry_equal(&top, &e)); @@ -111,7 +117,7 @@ static void t_merged_iter_pqueue_top(void) size_t N = ARRAY_SIZE(recs), i; for (i = 0; i < N; i++) { - reftable_record_init(&recs[i], BLOCK_TYPE_REF); + check(!reftable_record_init(&recs[i], BLOCK_TYPE_REF)); recs[i].u.ref.refname = (char *) "refs/heads/master"; } @@ -129,7 +135,9 @@ static void t_merged_iter_pqueue_top(void) for (i = N - 1; i > 0; i--) { struct pq_entry top = merged_iter_pqueue_top(pq); - struct pq_entry e = merged_iter_pqueue_remove(&pq); + struct pq_entry e; + + check(!merged_iter_pqueue_remove(&pq, &e)); merged_iter_pqueue_check(&pq); check(pq_entry_equal(&top, &e)); diff --git a/t/unit-tests/t-reftable-record.c b/t/unit-tests/t-reftable-record.c index d49d2a2729..5954966373 100644 --- a/t/unit-tests/t-reftable-record.c +++ b/t/unit-tests/t-reftable-record.c @@ -17,7 +17,7 @@ static void t_copy(struct reftable_record *rec) uint8_t typ; typ = reftable_record_type(rec); - reftable_record_init(©, typ); + check(!reftable_record_init(©, typ)); reftable_record_copy_from(©, rec, REFTABLE_HASH_SIZE_SHA1); /* do it twice to catch memory leaks */ reftable_record_copy_from(©, rec, REFTABLE_HASH_SIZE_SHA1); @@ -100,16 +100,20 @@ static void t_reftable_ref_record_comparison(void) .u.ref.value.symref = (char *) "refs/heads/master", }, }; + int cmp; check(!reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1)); - check(!reftable_record_cmp(&in[0], &in[1])); + check(!reftable_record_cmp(&in[0], &in[1], &cmp)); + check(!cmp); check(!reftable_record_equal(&in[1], &in[2], REFTABLE_HASH_SIZE_SHA1)); - check_int(reftable_record_cmp(&in[1], &in[2]), >, 0); + check(!reftable_record_cmp(&in[1], &in[2], &cmp)); + check_int(cmp, >, 0); in[1].u.ref.value_type = in[0].u.ref.value_type; check(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1)); - check(!reftable_record_cmp(&in[0], &in[1])); + check(!reftable_record_cmp(&in[0], &in[1], &cmp)); + check(!cmp); } static void t_reftable_ref_record_compare_name(void) @@ -209,17 +213,20 @@ static void t_reftable_log_record_comparison(void) .u.log.update_index = 22, }, }; + int cmp; check(!reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1)); check(!reftable_record_equal(&in[1], &in[2], REFTABLE_HASH_SIZE_SHA1)); - check_int(reftable_record_cmp(&in[1], &in[2]), >, 0); + check(!reftable_record_cmp(&in[1], &in[2], &cmp)); + check_int(cmp, >, 0); /* comparison should be reversed for equal keys, because * comparison is now performed on the basis of update indices */ - check_int(reftable_record_cmp(&in[0], &in[1]), <, 0); + check(!reftable_record_cmp(&in[0], &in[1], &cmp)); + check_int(cmp, <, 0); in[1].u.log.update_index = in[0].u.log.update_index; check(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1)); - check(!reftable_record_cmp(&in[0], &in[1])); + check(!reftable_record_cmp(&in[0], &in[1], &cmp)); } static void t_reftable_log_record_compare_key(void) @@ -396,16 +403,20 @@ static void t_reftable_obj_record_comparison(void) .u.obj.hash_prefix_len = 5, }, }; + int cmp; check(!reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1)); - check(!reftable_record_cmp(&in[0], &in[1])); + check(!reftable_record_cmp(&in[0], &in[1], &cmp)); + check(!cmp); check(!reftable_record_equal(&in[1], &in[2], REFTABLE_HASH_SIZE_SHA1)); - check_int(reftable_record_cmp(&in[1], &in[2]), >, 0); + check(!reftable_record_cmp(&in[1], &in[2], &cmp)); + check_int(cmp, >, 0); in[1].u.obj.offset_len = in[0].u.obj.offset_len; check(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1)); - check(!reftable_record_cmp(&in[0], &in[1])); + check(!reftable_record_cmp(&in[0], &in[1], &cmp)); + check(!cmp); } static void t_reftable_obj_record_roundtrip(void) @@ -486,19 +497,24 @@ static void t_reftable_index_record_comparison(void) .u.idx.last_key = REFTABLE_BUF_INIT, }, }; + int cmp; + check(!reftable_buf_addstr(&in[0].u.idx.last_key, "refs/heads/master")); check(!reftable_buf_addstr(&in[1].u.idx.last_key, "refs/heads/master")); check(!reftable_buf_addstr(&in[2].u.idx.last_key, "refs/heads/branch")); check(!reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1)); - check(!reftable_record_cmp(&in[0], &in[1])); + check(!reftable_record_cmp(&in[0], &in[1], &cmp)); + check(!cmp); check(!reftable_record_equal(&in[1], &in[2], REFTABLE_HASH_SIZE_SHA1)); - check_int(reftable_record_cmp(&in[1], &in[2]), >, 0); + check(!reftable_record_cmp(&in[1], &in[2], &cmp)); + check_int(cmp, >, 0); in[1].u.idx.offset = in[0].u.idx.offset; check(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1)); - check(!reftable_record_cmp(&in[0], &in[1])); + check(!reftable_record_cmp(&in[0], &in[1], &cmp)); + check(!cmp); for (size_t i = 0; i < ARRAY_SIZE(in); i++) reftable_record_release(&in[i]); diff --git a/transport-helper.c b/transport-helper.c index d457b42550..69391ee7d2 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -162,7 +162,7 @@ static struct child_process *get_helper(struct transport *transport) data->helper = helper; data->no_disconnect_req = 0; - refspec_init(&data->rs, REFSPEC_FETCH); + refspec_init_fetch(&data->rs); /* * Open the output as FILE* so strbuf_getline_*() family of 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/worktree.c b/worktree.c index 6449b6798d..c34b9eb74e 100644 --- a/worktree.c +++ b/worktree.c @@ -199,6 +199,11 @@ struct worktree **get_worktrees(void) return get_worktrees_internal(0); } +struct worktree **get_worktrees_without_reading_head(void) +{ + return get_worktrees_internal(1); +} + char *get_worktree_git_dir(const struct worktree *wt) { if (!wt) diff --git a/worktree.h b/worktree.h index 16368588a0..e4bcccdc0a 100644 --- a/worktree.h +++ b/worktree.h @@ -31,6 +31,14 @@ struct worktree { struct worktree **get_worktrees(void); /* + * Like `get_worktrees`, but does not read HEAD. Skip reading HEAD allows to + * get the worktree without worrying about failures pertaining to parsing + * the HEAD ref. This is useful in contexts where it is assumed that the + * refdb may not be in a consistent state. + */ +struct worktree **get_worktrees_without_reading_head(void); + +/* * Returns 1 if linked worktrees exist, 0 otherwise. */ int submodule_uses_worktrees(const char *path); 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; diff --git a/xdiff/xemit.c b/xdiff/xemit.c index f8e3f25b03..1d40c9cb40 100644 --- a/xdiff/xemit.c +++ b/xdiff/xemit.c @@ -43,6 +43,10 @@ static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t * return 0; } +static long saturating_add(long a, long b) +{ + return signed_add_overflows(a, b) ? LONG_MAX : a + b; +} /* * Starting at the passed change atom, find the latest change atom to be included @@ -52,7 +56,9 @@ static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t * xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg) { xdchange_t *xch, *xchp, *lxch; - long max_common = 2 * xecfg->ctxlen + xecfg->interhunkctxlen; + long max_common = saturating_add(saturating_add(xecfg->ctxlen, + xecfg->ctxlen), + xecfg->interhunkctxlen); long max_ignorable = xecfg->ctxlen; long ignored = 0; /* number of ignored blank lines */ |
