diff options
194 files changed, 4313 insertions, 1158 deletions
diff --git a/.gitignore b/.gitignore index a452215764..42fd7253b4 100644 --- a/.gitignore +++ b/.gitignore @@ -185,6 +185,7 @@ /git-worktree /git-write-tree /git-core-*/?* +/git.res /gitweb/GITWEB-BUILD-OPTIONS /gitweb/gitweb.cgi /gitweb/static/gitweb.js @@ -225,7 +226,6 @@ *.hcc *.obj *.lib -*.res *.sln *.sp *.suo diff --git a/Documentation/RelNotes/2.38.0.txt b/Documentation/RelNotes/2.38.0.txt new file mode 100644 index 0000000000..662a92c347 --- /dev/null +++ b/Documentation/RelNotes/2.38.0.txt @@ -0,0 +1,175 @@ +Git v2.38 Release Notes +======================= + +UI, Workflows & Features + + * "git remote show [-n] frotz" now pays attention to negative + pathspec. + + * "git push" sometimes perform poorly when reachability bitmaps are + used, even in a repository where other operations are helped by + bitmaps. The push.useBitmaps configuration variable is introduced + to allow disabling use of reachability bitmaps only for "git push". + + * "git grep -m<max-hits>" is a way to limit the hits shown per file. + + * "git merge-tree" learned a new mode where it takes two commits and + computes a tree that would result in the merge commit, if the + histories leading to these two commits were to be merged. + + * "git mv A B" in a sparsely populated working tree can be asked to + move a path between directories that are "in cone" (i.e. expected + to be materialized in the working tree) and "out of cone" + (i.e. expected to be hidden). The handling of such cases has been + improved. + + * Earlier, HTTP transport clients learned to tell the server side + what locale they are in by sending Accept-Language HTTP header, but + this was done only for some requests but not others. + + * Introduce a discovery.barerepository configuration variable that + allows users to forbid discovery of bare repositories. + + * Various messages that come from the pack-bitmap codepaths have been + tweaked. + + +Performance, Internal Implementation, Development Support etc. + + * Collection of what is referenced by objects in promisor packs have + been optimized to inspect these objects in the in-pack order. + + * Introduce a helper to see if a branch is already being worked on + (hence should not be newly checked out in a working tree), which + performs much better than the existing find_shared_symref() to + replace many uses of the latter. + + * Teach "git archive" to (optionally and then by default) avoid + spawning an external "gzip" process when creating ".tar.gz" (and + ".tgz") archives. + + * Allow large objects read from a packstream to be streamed into a + loose object file straight, without having to keep it in-core as a + whole. + + * Further preparation to turn git-submodule.sh into a builtin + continues. + + * Apply Coccinelle rule to turn raw memmove() into MOVE_ARRAY() cpp + macro, which would improve maintainability and readability. + + * Teach "make all" to build gitweb as well. + + * Tweak tests so that they still work when the "git init" template + did not create .git/info directory. + + * Add Coccinelle rules to detect the pattern of initializing and then + finalizing a structure without using it in between at all, which + happens after code restructuring and the compilers fail to + recognize as an unused variable. + + * The code to convert between GPG trust level strings and internal + constants we use to represent them have been cleaned up. + + * Support for libnettle as SHA256 implementation has been added. + + * The way "git multi-pack" uses parse-options API has been improved. + + * A coccinelle rule (in contrib/) to encourage use of COPY_ARRAY + macro has been improved. + + * API tweak to make it easier to run fuzz testing on commit-graph parser. + + * Omit fsync-related trace2 entries when their values are all zero. + + +Fixes since v2.37 +----------------- + + * Rewrite of "git add -i" in C that appeared in Git 2.25 didn't + correctly record a removed file to the index, which was fixed. + + * Certain diff options are currently ignored when combined-diff is + shown; mark them as incompatible with the feature. + + * Adjust technical/bitmap-format to be formatted by AsciiDoc, and + add some missing information to the documentation. + + * Fixes for tests when the source directory has unusual characters in + its path, e.g. whitespaces, double-quotes, etc. + + * "git mktree --missing" lazily fetched objects that are missing from + the local object store, which was totally unnecessary for the purpose + of creating the tree object(s) from its input. + + * Give _() markings to fatal/warning/usage: labels that are shown in + front of these messages. + + * References to commands-to-be-typed-literally in "git rebase" + documentation mark-up have been corrected. + + * In a non-bare repository, the behavior of Git when the + core.worktree configuration variable points at a directory that has + a repository as its subdirectory, regressed in Git 2.27 days. + + * Recent update to vimdiff layout code has been made more robust + against different end-user vim settings. + + * Plug various memory leaks. + (merge ece3974ba6 ab/leakfix later to maint). + + * Plug various memory leaks in test-tool commands. + (merge f40a693450 ab/test-tool-leakfix later to maint). + + * Fixes a long-standing corner case bug around directory renames in + the merge-ort strategy. + + * The resolve-undo information in the index was not protected against + GC, which has been corrected. + (merge e0ad13977a jc/resolve-undo later to maint). + + * A corner case bug where lazily fetching objects from a promisor + remote resulted in infinite recursion has been corrected. + (merge cb88b37cb9 hx/lookup-commit-in-graph-fix later to maint). + + * "git clone" from a repository with some ref whose HEAD is unborn + did not set the HEAD in the resulting repository correctly, which + has been corrected. + (merge daf7898abb jk/clone-unborn-confusion later to maint). + + * An earlier attempt to plug leaks placed a clean-up label to jump to + at a bogus place, which as been corrected. + + * Variable quoting fix in the vimdiff driver of "git mergetool" + + * "git shortlog -n" relied on the underlying qsort() to be stable, + which shouldn't have. Fixed. + + * A fix for a regression in test framework. + + * mkstemp() emulation on Windows has been improved. + (merge ae25974de3 rs/mingw-tighten-mkstemp later to maint). + + * Add missing documentation for "include" and "includeIf" features in + "git config" file format, which incidentally teaches the command + line completion to include them in its offerings. + (merge 07aed58017 mb/config-document-include later to maint). + + * Avoid "white/black-list" in documentation and code comments. + (merge f5adaa5cc3 ds/doc-wo-whitelist later to maint). + + * Workaround for a compiler warning against use of die() in + osx-keychain (in contrib/). + (merge f2fc531585 ld/osx-keychain-usage-fix later to maint). + + * Workaround for a false positive compiler warning. + (merge b4f52f09ae ds/win-syslog-compiler-fix later to maint). + + * "git p4" working on UTF-16 files on Windows did not implement + CRLF-to-LF conversion correctly, which has been corrected. + (merge 4d35f74421 mb/p4-utf16-crlf later to maint). + + * Other code cleanup, docfix, build fix, etc. + (merge a700395eaf ma/t4200-update later to maint). + (merge ae436f283c ma/sparse-checkout-cone-doc-fix later to maint). + (merge a10f6e2bda sg/index-format-doc-update later to maint). diff --git a/Documentation/config.txt b/Documentation/config.txt index e376d547ce..5b5b976569 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -445,6 +445,8 @@ include::config/i18n.txt[] include::config/imap.txt[] +include::config/includeif.txt[] + include::config/index.txt[] include::config/init.txt[] diff --git a/Documentation/config/core.txt b/Documentation/config/core.txt index 41e330f306..37afbaf5a4 100644 --- a/Documentation/config/core.txt +++ b/Documentation/config/core.txt @@ -444,17 +444,32 @@ You probably do not need to adjust this value. Common unit suffixes of 'k', 'm', or 'g' are supported. core.bigFileThreshold:: - Files larger than this size are stored deflated, without - attempting delta compression. Storing large files without - delta compression avoids excessive memory usage, at the - slight expense of increased disk usage. Additionally files - larger than this size are always treated as binary. + The size of files considered "big", which as discussed below + changes the behavior of numerous git commands, as well as how + such files are stored within the repository. The default is + 512 MiB. Common unit suffixes of 'k', 'm', or 'g' are + supported. + -Default is 512 MiB on all platforms. This should be reasonable -for most projects as source code and other text files can still -be delta compressed, but larger binary media files won't be. +Files above the configured limit will be: + -Common unit suffixes of 'k', 'm', or 'g' are supported. +* Stored deflated in packfiles, without attempting delta compression. ++ +The default limit is primarily set with this use-case in mind. With it, +most projects will have their source code and other text files delta +compressed, but not larger binary media files. ++ +Storing large files without delta compression avoids excessive memory +usage, at the slight expense of increased disk usage. ++ +* Will be treated as if they were labeled "binary" (see + linkgit:gitattributes[5]). e.g. linkgit:git-log[1] and + linkgit:git-diff[1] will not compute diffs for files above this limit. ++ +* Will generally be streamed when written, which avoids excessive +memory usage, at the cost of some fixed overhead. Commands that make +use of this include linkgit:git-archive[1], +linkgit:git-fast-import[1], linkgit:git-index-pack[1], +linkgit:git-unpack-objects[1] and linkgit:git-fsck[1]. core.excludesFile:: Specifies the pathname to the file that contains patterns to @@ -707,8 +722,8 @@ core.sparseCheckout:: core.sparseCheckoutCone:: Enables the "cone mode" of the sparse checkout feature. When the sparse-checkout file contains a limited set of patterns, this - mode provides significant performance advantages. The "non - cone mode" can be requested to allow specifying a more flexible + mode provides significant performance advantages. The "non-cone + mode" can be requested to allow specifying more flexible patterns by setting this variable to 'false'. See linkgit:git-sparse-checkout[1] for more information. diff --git a/Documentation/config/includeif.txt b/Documentation/config/includeif.txt new file mode 100644 index 0000000000..82fe431c34 --- /dev/null +++ b/Documentation/config/includeif.txt @@ -0,0 +1,6 @@ +include.path:: +includeIf.<condition>.path:: + Special variables to include other configuration files. See + the "CONFIGURATION FILE" section in the main + linkgit:git-config[1] documentation, + specifically the "Includes" and "Conditional Includes" subsections. diff --git a/Documentation/config/push.txt b/Documentation/config/push.txt index e32801e6c9..7386fea225 100644 --- a/Documentation/config/push.txt +++ b/Documentation/config/push.txt @@ -137,3 +137,8 @@ push.negotiate:: server attempt to find commits in common. If "false", Git will rely solely on the server's ref advertisement to find commits in common. + +push.useBitmaps:: + If set to "false", disable use of bitmaps for "git push" even if + `pack.useBitmaps` is "true", without preventing other git operations + from using bitmaps. Default is true. diff --git a/Documentation/config/safe.txt b/Documentation/config/safe.txt index fa02f3ccc5..bde7f31459 100644 --- a/Documentation/config/safe.txt +++ b/Documentation/config/safe.txt @@ -1,3 +1,22 @@ +safe.bareRepository:: + Specifies which bare repositories Git will work with. The currently + supported values are: ++ +* `all`: Git works with all bare repositories. This is the default. +* `explicit`: Git only works with bare repositories specified via + the top-level `--git-dir` command-line option, or the `GIT_DIR` + environment variable (see linkgit:git[1]). ++ +If you do not use bare repositories in your workflow, then it may be +beneficial to set `safe.bareRepository` to `explicit` in your global +config. This will protect you from attacks that involve cloning a +repository that contains a bare repository and running a Git command +within that directory. ++ +This config setting is only respected in protected configuration (see +<<SCOPES>>). This prevents the untrusted repository from tampering with +this value. + safe.directory:: These config entries specify Git-tracked directories that are considered safe even if they are owned by someone other than the @@ -12,9 +31,9 @@ via `git config --add`. To reset the list of safe directories (e.g. to override any such directories specified in the system config), add a `safe.directory` entry with an empty value. + -This config setting is only respected when specified in a system or global -config, not when it is specified in a repository config, via the command -line option `-c safe.directory=<path>`, or in environment variables. +This config setting is only respected in protected configuration (see +<<SCOPES>>). This prevents the untrusted repository from tampering with this +value. + The value of this setting is interpolated, i.e. `~/<path>` expands to a path relative to the home directory and `%(prefix)/<path>` expands to a diff --git a/Documentation/config/uploadpack.txt b/Documentation/config/uploadpack.txt index 32fad5bbe8..16264d82a7 100644 --- a/Documentation/config/uploadpack.txt +++ b/Documentation/config/uploadpack.txt @@ -49,9 +49,9 @@ uploadpack.packObjectsHook:: `pack-objects` to the hook, and expects a completed packfile on stdout. + -Note that this configuration variable is ignored if it is seen in the -repository-level config (this is a safety measure against fetching from -untrusted repositories). +Note that this configuration variable is only respected when it is specified +in protected configuration (see <<SCOPES>>). This is a safety measure +against fetching from untrusted repositories. uploadpack.allowFilter:: If this option is set, `upload-pack` will support partial diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt index 56989a2f34..60c040988b 100644 --- a/Documentation/git-archive.txt +++ b/Documentation/git-archive.txt @@ -34,10 +34,12 @@ OPTIONS ------- --format=<fmt>:: - Format of the resulting archive: 'tar' or 'zip'. If this option + Format of the resulting archive. Possible values are `tar`, + `zip`, `tar.gz`, `tgz`, and any format defined using the + configuration option `tar.<format>.command`. If `--format` is not given, and the output file is specified, the format is - inferred from the filename if possible (e.g. writing to "foo.zip" - makes the output to be in the zip format). Otherwise the output + inferred from the filename if possible (e.g. writing to `foo.zip` + makes the output to be in the `zip` format). Otherwise the output format is `tar`. -l:: @@ -143,17 +145,16 @@ tar.<format>.command:: is executed using the shell with the generated tar file on its standard input, and should produce the final output on its standard output. Any compression-level options will be passed - to the command (e.g., "-9"). An output file with the same - extension as `<format>` will be use this format if no other - format is given. + to the command (e.g., `-9`). + -The "tar.gz" and "tgz" formats are defined automatically and default to -`gzip -cn`. You may override them with custom commands. +The `tar.gz` and `tgz` formats are defined automatically and use the +magic command `git archive gzip` by default, which invokes an internal +implementation of gzip. tar.<format>.remote:: - If true, enable `<format>` for use by remote clients via + If true, enable the format for use by remote clients via linkgit:git-upload-archive[1]. Defaults to false for - user-defined formats, but true for the "tar.gz" and "tgz" + user-defined formats, but true for the `tar.gz` and `tgz` formats. [[ATTRIBUTES]] diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt index 9376e39aef..7a2bcb2f6c 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -297,23 +297,20 @@ The default is to use a pager. FILES ----- -If not set explicitly with `--file`, there are four files where -'git config' will search for configuration options: +By default, 'git config' will read configuration options from multiple +files: $(prefix)/etc/gitconfig:: System-wide configuration file. $XDG_CONFIG_HOME/git/config:: - Second user-specific configuration file. If $XDG_CONFIG_HOME is not set - or empty, `$HOME/.config/git/config` will be used. Any single-valued - variable set in this file will be overwritten by whatever is in - `~/.gitconfig`. It is a good idea not to create this file if - you sometimes use older versions of Git, as support for this - file was added fairly recently. - ~/.gitconfig:: - User-specific configuration file. Also called "global" - configuration file. + User-specific configuration files. When the XDG_CONFIG_HOME environment + variable is not set or empty, $HOME/.config/ is used as + $XDG_CONFIG_HOME. ++ +These are also called "global" configuration files. If both files exist, both +files are read in the order given above. $GIT_DIR/config:: Repository specific configuration file. @@ -322,28 +319,80 @@ $GIT_DIR/config.worktree:: This is optional and is only searched when `extensions.worktreeConfig` is present in $GIT_DIR/config. -If no further options are given, all reading options will read all of these -files that are available. If the global or the system-wide configuration -file are not available they will be ignored. If the repository configuration -file is not available or readable, 'git config' will exit with a non-zero -error code. However, in neither case will an error message be issued. +You may also provide additional configuration parameters when running any +git command by using the `-c` option. See linkgit:git[1] for details. + +Options will be read from all of these files that are available. If the +global or the system-wide configuration files are missing or unreadable they +will be ignored. If the repository configuration file is missing or unreadable, +'git config' will exit with a non-zero error code. An error message is produced +if the file is unreadable, but not if it is missing. The files are read in the order given above, with last value found taking precedence over values read earlier. When multiple values are taken then all values of a key from all files will be used. -You may override individual configuration parameters when running any git -command by using the `-c` option. See linkgit:git[1] for details. - -All writing options will per default write to the repository specific +By default, options are only written to the repository specific configuration file. Note that this also affects options like `--replace-all` and `--unset`. *'git config' will only ever change one file at a time*. -You can override these rules using the `--global`, `--system`, -`--local`, `--worktree`, and `--file` command-line options; see -<<OPTIONS>> above. +You can limit which configuration sources are read from or written to by +specifying the path of a file with the `--file` option, or by specifying a +configuration scope with `--system`, `--global`, `--local`, or `--worktree`. +For more, see <<OPTIONS>> above. + +[[SCOPES]] +SCOPES +------ + +Each configuration source falls within a configuration scope. The scopes +are: + +system:: + $(prefix)/etc/gitconfig + +global:: + $XDG_CONFIG_HOME/git/config ++ +~/.gitconfig + +local:: + $GIT_DIR/config + +worktree:: + $GIT_DIR/config.worktree + +command:: + GIT_CONFIG_{COUNT,KEY,VALUE} environment variables (see <<ENVIRONMENT>> + below) ++ +the `-c` option + +With the exception of 'command', each scope corresponds to a command line +option: `--system`, `--global`, `--local`, `--worktree`. + +When reading options, specifying a scope will only read options from the +files within that scope. When writing options, specifying a scope will write +to the files within that scope (instead of the repository specific +configuration file). See <<OPTIONS>> above for a complete description. + +Most configuration options are respected regardless of the scope it is +defined in, but some options are only respected in certain scopes. See the +respective option's documentation for the full details. + +Protected configuration +~~~~~~~~~~~~~~~~~~~~~~~ + +Protected configuration refers to the 'system', 'global', and 'command' scopes. +For security reasons, certain options are only respected when they are +specified in protected configuration, and ignored otherwise. +Git treats these scopes as if they are controlled by the user or a trusted +administrator. This is because an attacker who controls these scopes can do +substantial harm without using Git, so it is assumed that the user's environment +protects these scopes against attackers. +[[ENVIRONMENT]] ENVIRONMENT ----------- diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt index 4dc57ed254..53f111bc0a 100644 --- a/Documentation/git-cvsserver.txt +++ b/Documentation/git-cvsserver.txt @@ -63,11 +63,10 @@ Print version information and exit Print usage information and exit <directory>:: -You can specify a list of allowed directories. If no directories -are given, all are allowed. This is an additional restriction, gitcvs -access still needs to be enabled by the `gitcvs.enabled` config option -unless `--export-all` was given, too. - +The remaining arguments provide a list of directories. If no directories +are given, then all are allowed. Repositories within these directories +still require the `gitcvs.enabled` config option, unless `--export-all` +is specified. LIMITATIONS ----------- @@ -311,11 +310,13 @@ ENVIRONMENT These variables obviate the need for command-line options in some circumstances, allowing easier restricted usage through git-shell. -GIT_CVSSERVER_BASE_PATH takes the place of the argument to --base-path. +GIT_CVSSERVER_BASE_PATH:: + This variable replaces the argument to --base-path. -GIT_CVSSERVER_ROOT specifies a single-directory whitelist. The -repository must still be configured to allow access through -git-cvsserver, as described above. +GIT_CVSSERVER_ROOT:: + This variable specifies a single directory, replacing the + `<directory>...` argument list. The repository still requires the + `gitcvs.enabled` config option, unless `--export-all` is specified. When these environment variables are set, the corresponding command-line arguments may not be used. diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index fdc28c041c..236df516c7 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -32,8 +32,8 @@ that service if it is enabled. It verifies that the directory has the magic file "git-daemon-export-ok", and it will refuse to export any Git directory that hasn't explicitly been marked for export this way (unless the `--export-all` parameter is specified). If you -pass some directory paths as 'git daemon' arguments, you can further restrict -the offers to a whitelist comprising of those. +pass some directory paths as 'git daemon' arguments, the offers are limited to +repositories within those directories. By default, only `upload-pack` service is enabled, which serves 'git fetch-pack' and 'git ls-remote' clients, which are invoked @@ -50,7 +50,7 @@ OPTIONS Match paths exactly (i.e. don't allow "/foo/repo" when the real path is "/foo/repo.git" or "/foo/repo/.git") and don't do user-relative paths. 'git daemon' will refuse to start when this option is enabled and no - whitelist is specified. + directory arguments are provided. --base-path=<path>:: Remap all the path requests as relative to the given path. @@ -73,7 +73,7 @@ OPTIONS %IP for the server's IP address, %P for the port number, and %D for the absolute path of the named repository. After interpolation, the path is validated against the directory - whitelist. + list. --export-all:: Allow pulling from all directories that look like Git repositories @@ -218,9 +218,11 @@ standard output to be sent to the requestor as an error message when it declines the service. <directory>:: - A directory to add to the whitelist of allowed directories. Unless - --strict-paths is specified this will also include subdirectories - of each named directory. + The remaining arguments provide a list of directories. If any + directories are specified, then the `git-daemon` process will + serve a requested directory only if it is contained in one of + these directories. If `--strict-paths` is specified, then the + requested directory must match one of these directories exactly. SERVICES -------- @@ -264,9 +266,8 @@ git 9418/tcp # Git Version Control System 'git daemon' as inetd server:: To set up 'git daemon' as an inetd service that handles any - repository under the whitelisted set of directories, /pub/foo - and /pub/bar, place an entry like the following into - /etc/inetd all on one line: + repository within `/pub/foo` or `/pub/bar`, place an entry like + the following into `/etc/inetd` all on one line: + ------------------------------------------------ git stream tcp nowait nobody /usr/bin/git diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index 3d393fbac1..58d944bd57 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -23,6 +23,7 @@ SYNOPSIS [--break] [--heading] [-p | --show-function] [-A <post-context>] [-B <pre-context>] [-C <context>] [-W | --function-context] + [(-m | --max-count) <num>] [--threads <num>] [-f <file>] [-e] <pattern> [--and|--or|--not|(|)|-e <pattern>...] @@ -238,6 +239,14 @@ providing this option will cause it to die. `git diff` works out patch hunk headers (see 'Defining a custom hunk-header' in linkgit:gitattributes[5]). +-m <num>:: +--max-count <num>:: + Limit the amount of matches per file. When using the `-v` or + `--invert-match` option, the search stops after the specified + number of non-matches. A value of -1 will return unlimited + results (the default). A value of 0 will exit immediately with + a non-zero status. + --threads <num>:: Number of grep worker threads to use. See `grep.threads` in 'CONFIGURATION' for more information. diff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt index 58731c1942..d6c356740e 100644 --- a/Documentation/git-merge-tree.txt +++ b/Documentation/git-merge-tree.txt @@ -3,26 +3,237 @@ git-merge-tree(1) NAME ---- -git-merge-tree - Show three-way merge without touching index +git-merge-tree - Perform merge without touching index or working tree SYNOPSIS -------- [verse] -'git merge-tree' <base-tree> <branch1> <branch2> +'git merge-tree' [--write-tree] [<options>] <branch1> <branch2> +'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2> (deprecated) +[[NEWMERGE]] DESCRIPTION ----------- -Reads three tree-ish, and output trivial merge results and -conflicting stages to the standard output. This is similar to -what three-way 'git read-tree -m' does, but instead of storing the -results in the index, the command outputs the entries to the -standard output. - -This is meant to be used by higher level scripts to compute -merge results outside of the index, and stuff the results back into the -index. For this reason, the output from the command omits -entries that match the <branch1> tree. + +This command has a modern `--write-tree` mode and a deprecated +`--trivial-merge` mode. With the exception of the +<<DEPMERGE,DEPRECATED DESCRIPTION>> section at the end, the rest of +this documentation describes modern `--write-tree` mode. + +Performs a merge, but does not make any new commits and does not read +from or write to either the working tree or index. + +The performed merge will use the same feature as the "real" +linkgit:git-merge[1], including: + + * three way content merges of individual files + * rename detection + * proper directory/file conflict handling + * recursive ancestor consolidation (i.e. when there is more than one + merge base, creating a virtual merge base by merging the merge bases) + * etc. + +After the merge completes, a new toplevel tree object is created. See +`OUTPUT` below for details. + +OPTIONS +------- + +-z:: + Do not quote filenames in the <Conflicted file info> section, + and end each filename with a NUL character rather than + newline. Also begin the messages section with a NUL character + instead of a newline. See <<OUTPUT>> below for more information. + +--name-only:: + In the Conflicted file info section, instead of writing a list + of (mode, oid, stage, path) tuples to output for conflicted + files, just provide a list of filenames with conflicts (and + do not list filenames multiple times if they have multiple + conflicting stages). + +--[no-]messages:: + Write any informational messages such as "Auto-merging <path>" + or CONFLICT notices to the end of stdout. If unspecified, the + default is to include these messages if there are merge + conflicts, and to omit them otherwise. + +--allow-unrelated-histories:: + merge-tree will by default error out if the two branches specified + share no common history. This flag can be given to override that + check and make the merge proceed anyway. + +[[OUTPUT]] +OUTPUT +------ + +For a successful merge, the output from git-merge-tree is simply one +line: + + <OID of toplevel tree> + +Whereas for a conflicted merge, the output is by default of the form: + + <OID of toplevel tree> + <Conflicted file info> + <Informational messages> + +These are discussed individually below. + +[[OIDTLT]] +OID of toplevel tree +~~~~~~~~~~~~~~~~~~~~ + +This is a tree object that represents what would be checked out in the +working tree at the end of `git merge`. If there were conflicts, then +files within this tree may have embedded conflict markers. This section +is always followed by a newline (or NUL if `-z` is passed). + +[[CFI]] +Conflicted file info +~~~~~~~~~~~~~~~~~~~~ + +This is a sequence of lines with the format + + <mode> <object> <stage> <filename> + +The filename will be quoted as explained for the configuration +variable `core.quotePath` (see linkgit:git-config[1]). However, if +the `--name-only` option is passed, the mode, object, and stage will +be omitted. If `-z` is passed, the "lines" are terminated by a NUL +character instead of a newline character. + +[[IM]] +Informational messages +~~~~~~~~~~~~~~~~~~~~~~ + +This always starts with a blank line (or NUL if `-z` is passed) to +separate it from the previous sections, and then has free-form +messages about the merge, such as: + + * "Auto-merging <file>" + * "CONFLICT (rename/delete): <oldfile> renamed...but deleted in..." + * "Failed to merge submodule <submodule> (<reason>)" + * "Warning: cannot merge binary files: <filename>" + +Note that these free-form messages will never have a NUL character +in or between them, even if -z is passed. It is simply a large block +of text taking up the remainder of the output. + +EXIT STATUS +----------- + +For a successful, non-conflicted merge, the exit status is 0. When the +merge has conflicts, the exit status is 1. If the merge is not able to +complete (or start) due to some kind of error, the exit status is +something other than 0 or 1 (and the output is unspecified). + +USAGE NOTES +----------- + +This command is intended as low-level plumbing, similar to +linkgit:git-hash-object[1], linkgit:git-mktree[1], +linkgit:git-commit-tree[1], linkgit:git-write-tree[1], +linkgit:git-update-ref[1], and linkgit:git-mktag[1]. Thus, it can be +used as a part of a series of steps such as: + + NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2) + test $? -eq 0 || die "There were conflicts..." + NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2) + git update-ref $BRANCH1 $NEWCOMMIT + +Note that when the exit status is non-zero, `NEWTREE` in this sequence +will contain a lot more output than just a tree. + +For conflicts, the output includes the same information that you'd get +with linkgit:git-merge[1]: + + * what would be written to the working tree (the + <<OIDTLT,OID of toplevel tree>>) + * the higher order stages that would be written to the index (the + <<CFI,Conflicted file info>>) + * any messages that would have been printed to stdout (the + <<IM,Informational messages>>) + +MISTAKES TO AVOID +----------------- + +Do NOT look through the resulting toplevel tree to try to find which +files conflict; parse the <<CFI,Conflicted file info>> section instead. +Not only would parsing an entire tree be horrendously slow in large +repositories, there are numerous types of conflicts not representable by +conflict markers (modify/delete, mode conflict, binary file changed on +both sides, file/directory conflicts, various rename conflict +permutations, etc.) + +Do NOT interpret an empty <<CFI,Conflicted file info>> list as a clean +merge; check the exit status. A merge can have conflicts without having +individual files conflict (there are a few types of directory rename +conflicts that fall into this category, and others might also be added +in the future). + +Do NOT attempt to guess or make the user guess the conflict types from +the <<CFI,Conflicted file info>> list. The information there is +insufficient to do so. For example: Rename/rename(1to2) conflicts (both +sides renamed the same file differently) will result in three different +file having higher order stages (but each only has one higher order +stage), with no way (short of the <<IM,Informational messages>> section) +to determine which three files are related. File/directory conflicts +also result in a file with exactly one higher order stage. +Possibly-involved-in-directory-rename conflicts (when +"merge.directoryRenames" is unset or set to "conflicts") also result in +a file with exactly one higher order stage. In all cases, the +<<IM,Informational messages>> section has the necessary info, though it +is not designed to be machine parseable. + +Do NOT assume that each paths from <<CFI,Conflicted file info>>, and +the logical conflicts in the <<IM,Informational messages>> have a +one-to-one mapping, nor that there is a one-to-many mapping, nor a +many-to-one mapping. Many-to-many mappings exist, meaning that each +path can have many logical conflict types in a single merge, and each +logical conflict type can affect many paths. + +Do NOT assume all filenames listed in the <<IM,Informational messages>> +section had conflicts. Messages can be included for files that have no +conflicts, such as "Auto-merging <file>". + +AVOID taking the OIDS from the <<CFI,Conflicted file info>> and +re-merging them to present the conflicts to the user. This will lose +information. Instead, look up the version of the file found within the +<<OIDTLT,OID of toplevel tree>> and show that instead. In particular, +the latter will have conflict markers annotated with the original +branch/commit being merged and, if renames were involved, the original +filename. While you could include the original branch/commit in the +conflict marker annotations when re-merging, the original filename is +not available from the <<CFI,Conflicted file info>> and thus you would +be losing information that might help the user resolve the conflict. + +[[DEPMERGE]] +DEPRECATED DESCRIPTION +---------------------- + +Per the <<NEWMERGE,DESCRIPTION>> and unlike the rest of this +documentation, this section describes the deprecated `--trivial-merge` +mode. + +Other than the optional `--trivial-merge`, this mode accepts no +options. + +This mode reads three tree-ish, and outputs trivial merge results and +conflicting stages to the standard output in a semi-diff format. +Since this was designed for higher level scripts to consume and merge +the results back into the index, it omits entries that match +<branch1>. The result of this second form is similar to what +three-way 'git read-tree -m' does, but instead of storing the results +in the index, the command outputs the entries to the standard output. + +This form not only has limited applicability (a trivial merge cannot +handle content merges of individual files, rename detection, proper +directory/file conflict handling, etc.), the output format is also +difficult to work with, and it will generally be less performant than +the first form even on successful merges (especially if working in +large repositories). GIT --- diff --git a/Documentation/git.txt b/Documentation/git.txt index 302607a496..47a6095ff4 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -885,9 +885,7 @@ for full details. If set to a colon-separated list of protocols, behave as if `protocol.allow` is set to `never`, and each of the listed protocols has `protocol.<name>.allow` set to `always` - (overriding any existing configuration). In other words, any - protocol not mentioned will be disallowed (i.e., this is a - whitelist, not a blacklist). See the description of + (overriding any existing configuration). See the description of `protocol.allow` in linkgit:git-config[1] for more details. `GIT_PROTOCOL_FROM_USER`:: diff --git a/Documentation/technical/index-format.txt b/Documentation/technical/index-format.txt index 65da0daaa5..f691c20ab0 100644 --- a/Documentation/technical/index-format.txt +++ b/Documentation/technical/index-format.txt @@ -26,8 +26,6 @@ Git index format Extensions are identified by signature. Optional extensions can be ignored if Git does not understand them. - Git currently supports cache tree and resolve undo extensions. - 4-byte extension signature. If the first byte is 'A'..'Z' the extension is optional and can be ignored. diff --git a/Documentation/technical/scalar.txt b/Documentation/technical/scalar.txt new file mode 100644 index 0000000000..08bc09c225 --- /dev/null +++ b/Documentation/technical/scalar.txt @@ -0,0 +1,127 @@ +Scalar +====== + +Scalar is a repository management tool that optimizes Git for use in large +repositories. It accomplishes this by helping users to take advantage of +advanced performance features in Git. Unlike most other Git built-in commands, +Scalar is not executed as a subcommand of 'git'; rather, it is built as a +separate executable containing its own series of subcommands. + +Background +---------- + +Scalar was originally designed as an add-on to Git and implemented as a .NET +Core application. It was created based on the learnings from the VFS for Git +project (another application aimed at improving the experience of working with +large repositories). As part of its initial implementation, Scalar relied on +custom features in the Microsoft fork of Git that have since been integrated +into core Git: + +* partial clone, +* commit graphs, +* multi-pack index, +* sparse checkout (cone mode), +* scheduled background maintenance, +* etc + +With the requisite Git functionality in place and a desire to bring the benefits +of Scalar to the larger Git community, the Scalar application itself was ported +from C# to C and integrated upstream. + +Features +-------- + +Scalar is comprised of two major pieces of functionality: automatically +configuring built-in Git performance features and managing repository +enlistments. + +The Git performance features configured by Scalar (see "Background" for +examples) confer substantial performance benefits to large repositories, but are +either too experimental to enable for all of Git yet, or only benefit large +repositories. As new features are introduced, Scalar should be updated +accordingly to incorporate them. This will prevent the tool from becoming stale +while also providing a path for more easily bringing features to the appropriate +users. + +Enlistments are how Scalar knows which repositories on a user's system should +utilize Scalar-configured features. This allows it to update performance +settings when new ones are added to the tool, as well as centrally manage +repository maintenance. The enlistment structure - a root directory with a +`src/` subdirectory containing the cloned repository itself - is designed to +encourage users to route build outputs outside of the repository to avoid the +performance-limiting overhead of ignoring those files in Git. + +Design +------ + +Scalar is implemented in C and interacts with Git via a mix of child process +invocations of Git and direct usage of `libgit.a`. Internally, it is structured +much like other built-ins with subcommands (e.g., `git stash`), containing a +`cmd_<subcommand>()` function for each subcommand, routed through a `cmd_main()` +function. Most options are unique to each subcommand, with `scalar` respecting +some "global" `git` options (e.g., `-c` and `-C`). + +Because `scalar` is not invoked as a Git subcommand (like `git scalar`), it is +built and installed as its own executable in the `bin/` directory, alongside +`git`, `git-gui`, etc. + +Roadmap +------- + +NOTE: this section will be removed once the remaining tasks outlined in this +roadmap are complete. + +Scalar is a large enough project that it is being upstreamed incrementally, +living in `contrib/` until it is feature-complete. So far, the following patch +series have been accepted: + +- `scalar-the-beginning`: The initial patch series which sets up + `contrib/scalar/` and populates it with a minimal `scalar` command that + demonstrates the fundamental ideas. + +- `scalar-c-and-C`: The `scalar` command learns about two options that can be + specified before the command, `-c <key>=<value>` and `-C <directory>`. + +- `scalar-diagnose`: The `scalar` command is taught the `diagnose` subcommand. + +Roughly speaking (and subject to change), the following series are needed to +"finish" this initial version of Scalar: + +- Finish Scalar features: Enable the built-in FSMonitor in Scalar enlistments + and implement `scalar help`. At the end of this series, Scalar should be + feature-complete from the perspective of a user. + +- Generalize features not specific to Scalar: In the spirit of making Scalar + configure only what is needed for large repo performance, move common + utilities into other parts of Git. Some of this will be internal-only, but one + major change will be generalizing `scalar diagnose` for use with any Git + repository. + +- Move Scalar to toplevel: Move Scalar out of `contrib/` and into the root of + `git`, including updates to build and install it with the rest of Git. This + change will incorporate Scalar into the Git CI and test framework, as well as + expand regression and performance testing to ensure the tool is stable. + +Finally, there are two additional patch series that exist in Microsoft's fork of +Git, but there is no current plan to upstream them. There are some interesting +ideas there, but the implementation is too specific to Azure Repos and/or VFS +for Git to be of much help in general. + +These still exist mainly because the GVFS protocol is what Azure Repos has +instead of partial clone, while Git is focused on improving partial clone: + +- `scalar-with-gvfs`: The primary purpose of this patch series is to support + existing Scalar users whose repositories are hosted in Azure Repos (which does + not support Git's partial clones, but supports its predecessor, the GVFS + protocol, which is used by Scalar to emulate the partial clone). + + Since the GVFS protocol will never be supported by core Git, this patch series + will remain in Microsoft's fork of Git. + +- `run-scalar-functional-tests`: The Scalar project developed a quite + comprehensive set of integration tests (or, "Functional Tests"). They are the + sole remaining part of the original C#-based Scalar project, and this patch + adds a GitHub workflow that runs them all. + + Since the tests partially depend on features that are only provided in the + `scalar-with-gvfs` patch series, this patch cannot be upstreamed. diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 1b2c580ecc..6ec9e34282 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.37.1 +DEF_VER=v2.37.GIT LF=' ' @@ -182,6 +182,8 @@ include shared.mak # # Define BLK_SHA256 to use the built-in SHA-256 routines. # +# Define NETTLE_SHA256 to use the SHA-256 routines in libnettle. +# # Define GCRYPT_SHA256 to use the SHA-256 routines in libgcrypt. # # Define OPENSSL_SHA256 to use the SHA-256 routines in OpenSSL. @@ -309,6 +311,11 @@ include shared.mak # distributions that want to use their packaged versions of Perl # modules, instead of the fallbacks shipped with Git. # +# Define NO_GITWEB if you do not want to build or install +# 'gitweb'. Note that defining NO_PERL currently has the same effect +# on not installing gitweb, but not on whether it's built in the +# gitweb/ directory. +# # Define PYTHON_PATH to the path of your Python binary (often /usr/bin/python # but /usr/bin/python2.7 or /usr/bin/python3 on some platforms). # @@ -544,6 +551,7 @@ gitexecdir = libexec/git-core mergetoolsdir = $(gitexecdir)/mergetools sharedir = $(prefix)/share gitwebdir = $(sharedir)/gitweb +gitwebstaticdir = $(gitwebdir)/static perllibdir = $(sharedir)/perl5 localedir = $(sharedir)/locale template_dir = share/git-core/templates @@ -562,7 +570,7 @@ localedir_relative = $(patsubst $(prefix)/%,%,$(localedir)) htmldir_relative = $(patsubst $(prefix)/%,%,$(htmldir)) perllibdir_relative = $(patsubst $(prefix)/%,%,$(perllibdir)) -export prefix bindir sharedir sysconfdir gitwebdir perllibdir localedir +export prefix bindir sharedir sysconfdir perllibdir localedir # Set our default programs CC = cc @@ -1286,7 +1294,7 @@ SANITIZE_ADDRESS = # For the 'coccicheck' target; setting SPATCH_BATCH_SIZE higher will # usually result in less CPU usage at the cost of higher peak memory. # Setting it to 0 will feed all files in a single spatch invocation. -SPATCH_FLAGS = --all-includes --patch . +SPATCH_FLAGS = --all-includes SPATCH_BATCH_SIZE = 1 include config.mak.uname @@ -1842,6 +1850,10 @@ ifdef OPENSSL_SHA256 EXTLIBS += $(LIB_4_CRYPTO) BASIC_CFLAGS += -DSHA256_OPENSSL else +ifdef NETTLE_SHA256 + BASIC_CFLAGS += -DSHA256_NETTLE + EXTLIBS += -lnettle +else ifdef GCRYPT_SHA256 BASIC_CFLAGS += -DSHA256_GCRYPT EXTLIBS += -lgcrypt @@ -1850,6 +1862,7 @@ else BASIC_CFLAGS += -DSHA256_BLK endif endif +endif ifdef SHA1_MAX_BLOCK_SIZE LIB_OBJS += compat/sha1-chunked.o @@ -2089,6 +2102,7 @@ htmldir_relative_SQ = $(subst ','\'',$(htmldir_relative)) prefix_SQ = $(subst ','\'',$(prefix)) perllibdir_relative_SQ = $(subst ','\'',$(perllibdir_relative)) gitwebdir_SQ = $(subst ','\'',$(gitwebdir)) +gitwebstaticdir_SQ = $(subst ','\'',$(gitwebstaticdir)) SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) TEST_SHELL_PATH_SQ = $(subst ','\'',$(TEST_SHELL_PATH)) @@ -2417,10 +2431,6 @@ GIT-PERL-HEADER: $(PERL_HEADER_TEMPLATE) GIT-PERL-DEFINES Makefile perllibdir: @echo '$(perllibdir_SQ)' -.PHONY: gitweb -gitweb: - $(QUIET_SUBDIR0)gitweb $(QUIET_SUBDIR1) all - git-instaweb: git-instaweb.sh GIT-SCRIPT-DEFINES $(QUIET_GEN)$(cmd_munge_script) && \ chmod +x $@+ && \ @@ -3091,6 +3101,9 @@ $(SP_OBJ): %.sp: %.c %.o sparse: $(SP_OBJ) EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/% +ifndef NETTLE_SHA256 + EXCEPT_HDRS += sha256/nettle.h +endif ifndef GCRYPT_SHA256 EXCEPT_HDRS += sha256/gcrypt.h endif @@ -3123,6 +3136,8 @@ check: $(GENERATED_H) exit 1; \ fi +COCCI_TEST_RES = $(wildcard contrib/coccinelle/tests/*.res) + %.cocci.patch: %.cocci $(COCCI_SOURCES) $(QUIET_SPATCH) \ if test $(SPATCH_BATCH_SIZE) = 0; then \ @@ -3131,7 +3146,8 @@ check: $(GENERATED_H) limit='-n $(SPATCH_BATCH_SIZE)'; \ fi; \ if ! echo $(COCCI_SOURCES) | xargs $$limit \ - $(SPATCH) --sp-file $< $(SPATCH_FLAGS) \ + $(SPATCH) $(SPATCH_FLAGS) \ + --sp-file $< --patch . \ >$@+ 2>$@.log; \ then \ cat $@.log; \ @@ -3142,13 +3158,43 @@ check: $(GENERATED_H) then \ echo ' ' SPATCH result: $@; \ fi + +COCCI_TEST_RES_GEN = $(addprefix .build/,$(COCCI_TEST_RES)) +$(COCCI_TEST_RES_GEN): .build/%.res : %.c +$(COCCI_TEST_RES_GEN): .build/%.res : %.res +$(COCCI_TEST_RES_GEN): .build/contrib/coccinelle/tests/%.res : contrib/coccinelle/%.cocci + $(call mkdir_p_parent_template) + $(QUIET_SPATCH_T)$(SPATCH) $(SPATCH_FLAGS) \ + --very-quiet --no-show-diff \ + --sp-file $< -o $@ \ + $(@:.build/%.res=%.c) && \ + cmp $(@:.build/%=%) $@ || \ + git -P diff --no-index $(@:.build/%=%) $@ 2>/dev/null; \ + +.PHONY: coccicheck-test +coccicheck-test: $(COCCI_TEST_RES_GEN) + +coccicheck: coccicheck-test coccicheck: $(addsuffix .patch,$(filter-out %.pending.cocci,$(wildcard contrib/coccinelle/*.cocci))) # See contrib/coccinelle/README +coccicheck-pending: coccicheck-test coccicheck-pending: $(addsuffix .patch,$(wildcard contrib/coccinelle/*.pending.cocci)) .PHONY: coccicheck coccicheck-pending +# "Sub"-Makefiles, not really because they can't be run stand-alone, +# only there to contain directory-specific rules and variables +## gitweb/Makefile inclusion: +MAK_DIR_GITWEB = gitweb/ +include gitweb/Makefile + +.PHONY: gitweb +gitweb: $(MAK_DIR_GITWEB_ALL) +ifndef NO_GITWEB +all:: gitweb +endif + ### Installation rules ifneq ($(filter /%,$(firstword $(template_dir))),) @@ -3221,7 +3267,6 @@ ifndef NO_PERL $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perllibdir_SQ)' (cd perl/build/lib && $(TAR) cf - .) | \ (cd '$(DESTDIR_SQ)$(perllibdir_SQ)' && umask 022 && $(TAR) xof -) - $(MAKE) -C gitweb install endif ifndef NO_TCLTK $(MAKE) -C gitk-git install @@ -3276,10 +3321,8 @@ endif cp "$$execdir/git-remote-http$X" "$$execdir/$$p" || exit; } \ done -.PHONY: install-gitweb install-doc install-man install-man-perl install-html install-info install-pdf +.PHONY: install-doc install-man install-man-perl install-html install-info install-pdf .PHONY: quick-install-doc quick-install-man quick-install-html -install-gitweb: - $(MAKE) -C gitweb install install-doc: install-man-perl $(MAKE) -C Documentation install @@ -3403,12 +3446,13 @@ profile-clean: $(RM) $(addsuffix *.gcno,$(addprefix $(PROFILE_DIR)/, $(object_dirs))) cocciclean: + $(RM) -r .build/contrib/coccinelle $(RM) contrib/coccinelle/*.cocci.patch* clean: profile-clean coverage-clean cocciclean $(RM) -r .build $(RM) po/git.pot po/git-core.pot - $(RM) *.res + $(RM) git.res $(RM) $(OBJECTS) $(RM) $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(REFTABLE_TEST_LIB) $(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X @@ -3425,7 +3469,6 @@ clean: profile-clean coverage-clean cocciclean $(MAKE) -C Documentation/ clean $(RM) Documentation/GIT-EXCLUDED-PROGRAMS ifndef NO_PERL - $(MAKE) -C gitweb clean $(RM) -r perl/build/ endif $(MAKE) -C templates/ clean @@ -1 +1 @@ -Documentation/RelNotes/2.37.2.txt
\ No newline at end of file +Documentation/RelNotes/2.38.0.txt
\ No newline at end of file diff --git a/archive-tar.c b/archive-tar.c index 042feb66d2..3d77e0f750 100644 --- a/archive-tar.c +++ b/archive-tar.c @@ -38,11 +38,18 @@ static int write_tar_filter_archive(const struct archiver *ar, #define USTAR_MAX_MTIME 077777777777ULL #endif +static void tar_write_block(const void *buf) +{ + write_or_die(1, buf, BLOCKSIZE); +} + +static void (*write_block)(const void *) = tar_write_block; + /* writes out the whole block, but only if it is full */ static void write_if_needed(void) { if (offset == BLOCKSIZE) { - write_or_die(1, block, BLOCKSIZE); + write_block(block); offset = 0; } } @@ -66,7 +73,7 @@ static void do_write_blocked(const void *data, unsigned long size) write_if_needed(); } while (size >= BLOCKSIZE) { - write_or_die(1, buf, BLOCKSIZE); + write_block(buf); size -= BLOCKSIZE; buf += BLOCKSIZE; } @@ -101,10 +108,10 @@ static void write_trailer(void) { int tail = BLOCKSIZE - offset; memset(block + offset, 0, tail); - write_or_die(1, block, BLOCKSIZE); + write_block(block); if (tail < 2 * RECORDSIZE) { memset(block, 0, offset); - write_or_die(1, block, BLOCKSIZE); + write_block(block); } } @@ -383,8 +390,8 @@ static int tar_filter_config(const char *var, const char *value, void *data) if (!strcmp(type, "command")) { if (!value) return config_error_nonbool(var); - free(ar->data); - ar->data = xstrdup(value); + free(ar->filter_command); + ar->filter_command = xstrdup(value); return 0; } if (!strcmp(type, "remote")) { @@ -425,17 +432,65 @@ static int write_tar_archive(const struct archiver *ar, return err; } +static git_zstream gzstream; +static unsigned char outbuf[16384]; + +static void tgz_deflate(int flush) +{ + while (gzstream.avail_in || flush == Z_FINISH) { + int status = git_deflate(&gzstream, flush); + if (!gzstream.avail_out || status == Z_STREAM_END) { + write_or_die(1, outbuf, gzstream.next_out - outbuf); + gzstream.next_out = outbuf; + gzstream.avail_out = sizeof(outbuf); + if (status == Z_STREAM_END) + break; + } + if (status != Z_OK && status != Z_BUF_ERROR) + die(_("deflate error (%d)"), status); + } +} + +static void tgz_write_block(const void *data) +{ + gzstream.next_in = (void *)data; + gzstream.avail_in = BLOCKSIZE; + tgz_deflate(Z_NO_FLUSH); +} + +static const char internal_gzip_command[] = "git archive gzip"; + static int write_tar_filter_archive(const struct archiver *ar, struct archiver_args *args) { +#if ZLIB_VERNUM >= 0x1221 + struct gz_header_s gzhead = { .os = 3 }; /* Unix, for reproducibility */ +#endif struct strbuf cmd = STRBUF_INIT; struct child_process filter = CHILD_PROCESS_INIT; int r; - if (!ar->data) + if (!ar->filter_command) BUG("tar-filter archiver called with no filter defined"); - strbuf_addstr(&cmd, ar->data); + if (!strcmp(ar->filter_command, internal_gzip_command)) { + write_block = tgz_write_block; + git_deflate_init_gzip(&gzstream, args->compression_level); +#if ZLIB_VERNUM >= 0x1221 + if (deflateSetHeader(&gzstream.z, &gzhead) != Z_OK) + BUG("deflateSetHeader() called too late"); +#endif + gzstream.next_out = outbuf; + gzstream.avail_out = sizeof(outbuf); + + r = write_tar_archive(ar, args); + + tgz_deflate(Z_FINISH); + git_deflate_end(&gzstream); + return r; + } + + strbuf_addstr(&cmd, ar->filter_command); if (args->compression_level >= 0) strbuf_addf(&cmd, " -%d", args->compression_level); @@ -471,14 +526,14 @@ void init_tar_archiver(void) int i; register_archiver(&tar_archiver); - tar_filter_config("tar.tgz.command", "gzip -cn", NULL); + tar_filter_config("tar.tgz.command", internal_gzip_command, NULL); tar_filter_config("tar.tgz.remote", "true", NULL); - tar_filter_config("tar.tar.gz.command", "gzip -cn", NULL); + tar_filter_config("tar.tar.gz.command", internal_gzip_command, NULL); tar_filter_config("tar.tar.gz.remote", "true", NULL); git_config(git_tar_config, NULL); for (i = 0; i < nr_tar_filters; i++) { /* omit any filters that never had a command configured */ - if (tar_filters[i]->data) + if (tar_filters[i]->filter_command) register_archiver(tar_filters[i]); } } @@ -43,7 +43,7 @@ struct archiver { const char *name; int (*write_archive)(const struct archiver *, struct archiver_args *); unsigned flags; - void *data; + char *filter_command; }; void register_archiver(struct archiver *); @@ -10,6 +10,7 @@ #include "worktree.h" #include "submodule-config.h" #include "run-command.h" +#include "strmap.h" struct tracking { struct refspec_item spec; @@ -369,6 +370,70 @@ int validate_branchname(const char *name, struct strbuf *ref) return ref_exists(ref->buf); } +static int initialized_checked_out_branches; +static struct strmap current_checked_out_branches = STRMAP_INIT; + +static void prepare_checked_out_branches(void) +{ + int i = 0; + struct worktree **worktrees; + + if (initialized_checked_out_branches) + return; + initialized_checked_out_branches = 1; + + worktrees = get_worktrees(); + + while (worktrees[i]) { + char *old; + struct wt_status_state state = { 0 }; + struct worktree *wt = worktrees[i++]; + + if (wt->is_bare) + continue; + + if (wt->head_ref) { + old = strmap_put(¤t_checked_out_branches, + wt->head_ref, + xstrdup(wt->path)); + free(old); + } + + if (wt_status_check_rebase(wt, &state) && + (state.rebase_in_progress || state.rebase_interactive_in_progress) && + state.branch) { + struct strbuf ref = STRBUF_INIT; + strbuf_addf(&ref, "refs/heads/%s", state.branch); + old = strmap_put(¤t_checked_out_branches, + ref.buf, + xstrdup(wt->path)); + free(old); + strbuf_release(&ref); + } + wt_status_state_free_buffers(&state); + + if (wt_status_check_bisect(wt, &state) && + state.branch) { + struct strbuf ref = STRBUF_INIT; + strbuf_addf(&ref, "refs/heads/%s", state.branch); + old = strmap_put(¤t_checked_out_branches, + ref.buf, + xstrdup(wt->path)); + free(old); + strbuf_release(&ref); + } + wt_status_state_free_buffers(&state); + } + + free_worktrees(worktrees); +} + +const char *branch_checked_out(const char *refname) +{ + prepare_checked_out_branches(); + return strmap_get(¤t_checked_out_branches, refname); +} + /* * Check if a branch 'name' can be created as a new branch; die otherwise. * 'force' can be used when it is OK for the named branch already exists. @@ -377,9 +442,7 @@ int validate_branchname(const char *name, struct strbuf *ref) */ int validate_new_branchname(const char *name, struct strbuf *ref, int force) { - struct worktree **worktrees; - const struct worktree *wt; - + const char *path; if (!validate_branchname(name, ref)) return 0; @@ -387,13 +450,10 @@ int validate_new_branchname(const char *name, struct strbuf *ref, int force) die(_("a branch named '%s' already exists"), ref->buf + strlen("refs/heads/")); - worktrees = get_worktrees(); - wt = find_shared_symref(worktrees, "HEAD", ref->buf); - if (wt && !wt->is_bare) + if ((path = branch_checked_out(ref->buf))) die(_("cannot force update the branch '%s' " "checked out at '%s'"), - ref->buf + strlen("refs/heads/"), wt->path); - free_worktrees(worktrees); + ref->buf + strlen("refs/heads/"), path); return 1; } @@ -101,6 +101,13 @@ void create_branches_recursively(struct repository *r, const char *name, const char *tracking_name, int force, int reflog, int quiet, enum branch_track track, int dry_run); + +/* + * If the branch at 'refname' is currently checked out in a worktree, + * then return the path to that worktree. + */ +const char *branch_checked_out(const char *refname); + /* * Check if 'name' can be a valid name for a branch; die otherwise. * Return 1 if the named branch already exists; return 0 otherwise. diff --git a/builtin/branch.c b/builtin/branch.c index 5d00d0b8d3..55cd9a6e99 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -204,7 +204,6 @@ static void delete_branch_config(const char *branchname) static int delete_branches(int argc, const char **argv, int force, int kinds, int quiet) { - struct worktree **worktrees; struct commit *head_rev = NULL; struct object_id oid; char *name = NULL; @@ -242,8 +241,6 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, die(_("Couldn't look up commit object for HEAD")); } - worktrees = get_worktrees(); - for (i = 0; i < argc; i++, strbuf_reset(&bname)) { char *target = NULL; int flags = 0; @@ -253,12 +250,11 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, name = mkpathdup(fmt, bname.buf); if (kinds == FILTER_REFS_BRANCHES) { - const struct worktree *wt = - find_shared_symref(worktrees, "HEAD", name); - if (wt) { + const char *path; + if ((path = branch_checked_out(name))) { error(_("Cannot delete branch '%s' " "checked out at '%s'"), - bname.buf, wt->path); + bname.buf, path); ret = 1; continue; } @@ -315,7 +311,6 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, free(name); strbuf_release(&bname); - free_worktrees(worktrees); return ret; } diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 50cf38999d..f42782e955 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -71,6 +71,7 @@ static int stream_blob(const struct object_id *oid) static int cat_one_file(int opt, const char *exp_type, const char *obj_name, int unknown_type) { + int ret; struct object_id oid; enum object_type type; char *buf; @@ -106,7 +107,8 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, if (sb.len) { printf("%s\n", sb.buf); strbuf_release(&sb); - return 0; + ret = 0; + goto cleanup; } break; @@ -115,7 +117,8 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, if (oid_object_info_extended(the_repository, &oid, &oi, flags) < 0) die("git cat-file: could not get object info"); printf("%"PRIuMAX"\n", (uintmax_t)size); - return 0; + ret = 0; + goto cleanup; case 'e': return !has_object_file(&oid); @@ -123,8 +126,10 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, case 'w': if (filter_object(path, obj_context.mode, - &oid, &buf, &size)) - return -1; + &oid, &buf, &size)) { + ret = -1; + goto cleanup; + } break; case 'c': @@ -143,11 +148,14 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, const char *ls_args[3] = { NULL }; ls_args[0] = "ls-tree"; ls_args[1] = obj_name; - return cmd_ls_tree(2, ls_args, NULL); + ret = cmd_ls_tree(2, ls_args, NULL); + goto cleanup; } - if (type == OBJ_BLOB) - return stream_blob(&oid); + if (type == OBJ_BLOB) { + ret = stream_blob(&oid); + goto cleanup; + } buf = read_object_file(&oid, &type, &size); if (!buf) die("Cannot read object %s", obj_name); @@ -172,8 +180,10 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, } else oidcpy(&blob_oid, &oid); - if (oid_object_info(the_repository, &blob_oid, NULL) == OBJ_BLOB) - return stream_blob(&blob_oid); + if (oid_object_info(the_repository, &blob_oid, NULL) == OBJ_BLOB) { + ret = stream_blob(&blob_oid); + goto cleanup; + } /* * we attempted to dereference a tag to a blob * and failed; there may be new dereference @@ -193,9 +203,11 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, die("git cat-file %s: bad file", obj_name); write_or_die(1, buf, size); + ret = 0; +cleanup: free(buf); free(obj_context.path); - return 0; + return ret; } struct expand_data { @@ -655,6 +667,7 @@ static void batch_objects_command(struct batch_options *opt, free_cmds(queued_cmd, &nr); } + free_cmds(queued_cmd, &nr); free(queued_cmd); strbuf_release(&input); } diff --git a/builtin/check-ref-format.c b/builtin/check-ref-format.c index bc67d3f0a8..fd0e5f8683 100644 --- a/builtin/check-ref-format.c +++ b/builtin/check-ref-format.c @@ -57,6 +57,8 @@ int cmd_check_ref_format(int argc, const char **argv, const char *prefix) int normalize = 0; int flags = 0; const char *refname; + char *to_free = NULL; + int ret = 1; if (argc == 2 && !strcmp(argv[1], "-h")) usage(builtin_check_ref_format_usage); @@ -81,11 +83,14 @@ int cmd_check_ref_format(int argc, const char **argv, const char *prefix) refname = argv[i]; if (normalize) - refname = collapse_slashes(refname); + refname = to_free = collapse_slashes(refname); if (check_refname_format(refname, flags)) - return 1; + goto cleanup; if (normalize) printf("%s\n", refname); - return 0; + ret = 0; +cleanup: + free(to_free); + return ret; } diff --git a/builtin/checkout.c b/builtin/checkout.c index 2eefda81d8..1109f1301f 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -710,6 +710,26 @@ static void setup_branch_path(struct branch_info *branch) branch->path = strbuf_detach(&buf, NULL); } +static void init_topts(struct unpack_trees_options *topts, int merge, + int show_progress, int overwrite_ignore, + struct commit *old_commit) +{ + memset(topts, 0, sizeof(*topts)); + topts->head_idx = -1; + topts->src_index = &the_index; + topts->dst_index = &the_index; + + setup_unpack_trees_porcelain(topts, "checkout"); + + topts->initial_checkout = is_cache_unborn(); + topts->update = 1; + topts->merge = 1; + topts->quiet = merge && old_commit; + topts->verbose_update = show_progress; + topts->fn = twoway_merge; + topts->preserve_ignored = !overwrite_ignore; +} + static int merge_working_tree(const struct checkout_opts *opts, struct branch_info *old_branch_info, struct branch_info *new_branch_info, @@ -740,13 +760,6 @@ static int merge_working_tree(const struct checkout_opts *opts, struct unpack_trees_options topts; const struct object_id *old_commit_oid; - memset(&topts, 0, sizeof(topts)); - topts.head_idx = -1; - topts.src_index = &the_index; - topts.dst_index = &the_index; - - setup_unpack_trees_porcelain(&topts, "checkout"); - refresh_cache(REFRESH_QUIET); if (unmerged_cache()) { @@ -755,17 +768,12 @@ static int merge_working_tree(const struct checkout_opts *opts, } /* 2-way merge to the new branch */ - topts.initial_checkout = is_cache_unborn(); - topts.update = 1; - topts.merge = 1; - topts.quiet = opts->merge && old_branch_info->commit; - topts.verbose_update = opts->show_progress; - topts.fn = twoway_merge; + init_topts(&topts, opts->merge, opts->show_progress, + opts->overwrite_ignore, old_branch_info->commit); init_checkout_metadata(&topts.meta, new_branch_info->refname, new_branch_info->commit ? &new_branch_info->commit->object.oid : &new_branch_info->oid, NULL); - topts.preserve_ignored = !opts->overwrite_ignore; old_commit_oid = old_branch_info->commit ? &old_branch_info->commit->object.oid : diff --git a/builtin/clone.c b/builtin/clone.c index 89a91b0017..c4ff4643ec 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -494,6 +494,7 @@ static struct ref *wanted_peer_refs(const struct ref *refs, /* if --branch=tag, pull the requested tag explicitly */ get_fetch_map(remote_head, tag_refspec, &tail, 0); } + free_refs(remote_head); } else { int i; for (i = 0; i < refspec->nr; i++) @@ -606,7 +607,7 @@ static void update_remote_refs(const struct ref *refs, } static void update_head(const struct ref *our, const struct ref *remote, - const char *msg) + const char *unborn, const char *msg) { const char *head; if (our && skip_prefix(our->name, "refs/heads/", &head)) { @@ -632,6 +633,15 @@ static void update_head(const struct ref *our, const struct ref *remote, */ update_ref(msg, "HEAD", &remote->old_oid, NULL, REF_NO_DEREF, UPDATE_REFS_DIE_ON_ERR); + } else if (unborn && skip_prefix(unborn, "refs/heads/", &head)) { + /* + * Unborn head from remote; same as "our" case above except + * that we have no ref to update. + */ + if (create_symref("HEAD", unborn, NULL) < 0) + die(_("unable to update HEAD")); + if (!option_bare) + install_branch_config(0, head, remote_name, unborn); } } @@ -672,7 +682,7 @@ static int checkout(int submodule_progress, int filter_submodules) head = resolve_refdup("HEAD", RESOLVE_REF_READING, &oid, NULL); if (!head) { warning(_("remote HEAD refers to nonexistent ref, " - "unable to checkout.\n")); + "unable to checkout")); return 0; } if (!strcmp(head, "HEAD")) { @@ -876,6 +886,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) const struct ref *refs, *remote_head; struct ref *remote_head_points_at = NULL; const struct ref *our_head_points_at; + char *unborn_head = NULL; struct ref *mapped_refs = NULL; const struct ref *ref; struct strbuf key = STRBUF_INIT; @@ -1266,51 +1277,49 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (transport_fetch_refs(transport, mapped_refs)) die(_("remote transport reported error")); } - - remote_head = find_ref_by_name(refs, "HEAD"); - remote_head_points_at = - guess_remote_head(remote_head, mapped_refs, 0); - - if (option_branch) { - our_head_points_at = - find_remote_branch(mapped_refs, option_branch); - - if (!our_head_points_at) - die(_("Remote branch %s not found in upstream %s"), - option_branch, remote_name); - } - else - our_head_points_at = remote_head_points_at; } - else { - const char *branch; - const char *ref; - char *ref_free = NULL; - if (option_branch) - die(_("Remote branch %s not found in upstream %s"), - option_branch, remote_name); + remote_head = find_ref_by_name(refs, "HEAD"); + remote_head_points_at = guess_remote_head(remote_head, mapped_refs, 0); - warning(_("You appear to have cloned an empty repository.")); + if (option_branch) { + our_head_points_at = find_remote_branch(mapped_refs, option_branch); + if (!our_head_points_at) + die(_("Remote branch %s not found in upstream %s"), + option_branch, remote_name); + } else if (remote_head_points_at) { + our_head_points_at = remote_head_points_at; + } else if (remote_head) { our_head_points_at = NULL; - remote_head_points_at = NULL; - remote_head = NULL; - option_no_checkout = 1; + } else { + const char *branch; + + if (!mapped_refs) { + warning(_("You appear to have cloned an empty repository.")); + option_no_checkout = 1; + } if (transport_ls_refs_options.unborn_head_target && skip_prefix(transport_ls_refs_options.unborn_head_target, "refs/heads/", &branch)) { - ref = transport_ls_refs_options.unborn_head_target; - create_symref("HEAD", ref, reflog_msg.buf); + unborn_head = xstrdup(transport_ls_refs_options.unborn_head_target); } else { branch = git_default_branch_name(0); - ref_free = xstrfmt("refs/heads/%s", branch); - ref = ref_free; + unborn_head = xstrfmt("refs/heads/%s", branch); } - if (!option_bare) - install_branch_config(0, branch, remote_name, ref); - free(ref_free); + /* + * We may have selected a local default branch name "foo", + * and even though the remote's HEAD does not point there, + * it may still have a "foo" branch. If so, set it up so + * that we can follow the usual checkout code later. + * + * Note that for an empty repo we'll already have set + * option_no_checkout above, which would work against us here. + * But for an empty repo, find_remote_branch() can never find + * a match. + */ + our_head_points_at = find_remote_branch(mapped_refs, branch); } write_refspec_config(src_ref_prefix, our_head_points_at, @@ -1330,7 +1339,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) branch_top.buf, reflog_msg.buf, transport, !is_local); - update_head(our_head_points_at, remote_head, reflog_msg.buf); + update_head(our_head_points_at, remote_head, unborn_head, reflog_msg.buf); /* * We want to show progress for recursive submodule clones iff @@ -1357,6 +1366,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) strbuf_release(&key); free_refs(mapped_refs); free_refs(remote_head_points_at); + free(unborn_head); free(dir); free(path); UNLEAK(repo); diff --git a/builtin/fetch.c b/builtin/fetch.c index ac29c2b1ae..fc5cecb483 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -881,11 +881,9 @@ static void format_display(struct strbuf *display, char code, static int update_local_ref(struct ref *ref, struct ref_transaction *transaction, const char *remote, const struct ref *remote_ref, - struct strbuf *display, int summary_width, - struct worktree **worktrees) + struct strbuf *display, int summary_width) { struct commit *current = NULL, *updated; - const struct worktree *wt; const char *pretty_ref = prettify_refname(ref->name); int fast_forward = 0; @@ -900,16 +898,14 @@ static int update_local_ref(struct ref *ref, } if (!update_head_ok && - (wt = find_shared_symref(worktrees, "HEAD", ref->name)) && - !wt->is_bare && !is_null_oid(&ref->old_oid)) { + !is_null_oid(&ref->old_oid) && + branch_checked_out(ref->name)) { /* * If this is the head, and it's not okay to update * the head, and the old value of the head isn't empty... */ format_display(display, '!', _("[rejected]"), - wt->is_current ? - _("can't fetch in current branch") : - _("checked out in another worktree"), + _("can't fetch into checked-out branch"), remote, pretty_ref, summary_width); return 1; } @@ -1110,10 +1106,10 @@ N_("it took %.2f seconds to check forced updates; you can use\n" static int store_updated_refs(const char *raw_url, const char *remote_name, int connectivity_checked, struct ref_transaction *transaction, struct ref *ref_map, - struct fetch_head *fetch_head, struct worktree **worktrees) + struct fetch_head *fetch_head) { int url_len, i, rc = 0; - struct strbuf note = STRBUF_INIT, err = STRBUF_INIT; + struct strbuf note = STRBUF_INIT; const char *what, *kind; struct ref *rm; char *url; @@ -1240,8 +1236,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, strbuf_reset(¬e); if (ref) { rc |= update_local_ref(ref, transaction, what, - rm, ¬e, summary_width, - worktrees); + rm, ¬e, summary_width); free(ref); } else if (write_fetch_head || dry_run) { /* @@ -1281,7 +1276,6 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, abort: strbuf_release(¬e); - strbuf_release(&err); free(url); return rc; } @@ -1332,8 +1326,7 @@ static int check_exist_and_connected(struct ref *ref_map) static int fetch_and_consume_refs(struct transport *transport, struct ref_transaction *transaction, struct ref *ref_map, - struct fetch_head *fetch_head, - struct worktree **worktrees) + struct fetch_head *fetch_head) { int connectivity_checked = 1; int ret; @@ -1356,7 +1349,7 @@ static int fetch_and_consume_refs(struct transport *transport, trace2_region_enter("fetch", "consume_refs", the_repository); ret = store_updated_refs(transport->url, transport->remote->name, connectivity_checked, transaction, ref_map, - fetch_head, worktrees); + fetch_head); trace2_region_leave("fetch", "consume_refs", the_repository); out: @@ -1434,19 +1427,16 @@ cleanup: return result; } -static void check_not_current_branch(struct ref *ref_map, - struct worktree **worktrees) +static void check_not_current_branch(struct ref *ref_map) { - const struct worktree *wt; + const char *path; for (; ref_map; ref_map = ref_map->next) if (ref_map->peer_ref && starts_with(ref_map->peer_ref->name, "refs/heads/") && - (wt = find_shared_symref(worktrees, "HEAD", - ref_map->peer_ref->name)) && - !wt->is_bare) + (path = branch_checked_out(ref_map->peer_ref->name))) die(_("refusing to fetch into branch '%s' " "checked out at '%s'"), - ref_map->peer_ref->name, wt->path); + ref_map->peer_ref->name, path); } static int truncate_fetch_head(void) @@ -1549,8 +1539,7 @@ static struct transport *prepare_transport(struct remote *remote, int deepen) static int backfill_tags(struct transport *transport, struct ref_transaction *transaction, struct ref *ref_map, - struct fetch_head *fetch_head, - struct worktree **worktrees) + struct fetch_head *fetch_head) { int retcode, cannot_reuse; @@ -1571,7 +1560,7 @@ static int backfill_tags(struct transport *transport, transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL); transport_set_option(transport, TRANS_OPT_DEPTH, "0"); transport_set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, NULL); - retcode = fetch_and_consume_refs(transport, transaction, ref_map, fetch_head, worktrees); + retcode = fetch_and_consume_refs(transport, transaction, ref_map, fetch_head); if (gsecondary) { transport_disconnect(gsecondary); @@ -1592,7 +1581,6 @@ static int do_fetch(struct transport *transport, struct transport_ls_refs_options transport_ls_refs_options = TRANSPORT_LS_REFS_OPTIONS_INIT; int must_list_refs = 1; - struct worktree **worktrees = get_worktrees(); struct fetch_head fetch_head = { 0 }; struct strbuf err = STRBUF_INIT; @@ -1650,7 +1638,7 @@ static int do_fetch(struct transport *transport, ref_map = get_ref_map(transport->remote, remote_refs, rs, tags, &autotags); if (!update_head_ok) - check_not_current_branch(ref_map, worktrees); + check_not_current_branch(ref_map); retcode = open_fetch_head(&fetch_head); if (retcode) @@ -1683,7 +1671,7 @@ static int do_fetch(struct transport *transport, retcode = 1; } - if (fetch_and_consume_refs(transport, transaction, ref_map, &fetch_head, worktrees)) { + if (fetch_and_consume_refs(transport, transaction, ref_map, &fetch_head)) { retcode = 1; goto cleanup; } @@ -1706,7 +1694,7 @@ static int do_fetch(struct transport *transport, * the transaction and don't commit anything. */ if (backfill_tags(transport, transaction, tags_ref_map, - &fetch_head, worktrees)) + &fetch_head)) retcode = 1; } @@ -1791,7 +1779,6 @@ cleanup: close_fetch_head(&fetch_head); strbuf_release(&err); free_refs(ref_map); - free_worktrees(worktrees); return retcode; } diff --git a/builtin/fsck.c b/builtin/fsck.c index 9e54892311..6c73092f10 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -19,6 +19,7 @@ #include "decorate.h" #include "packfile.h" #include "object-store.h" +#include "resolve-undo.h" #include "run-command.h" #include "worktree.h" @@ -757,6 +758,43 @@ static int fsck_cache_tree(struct cache_tree *it) return err; } +static int fsck_resolve_undo(struct index_state *istate) +{ + struct string_list_item *item; + struct string_list *resolve_undo = istate->resolve_undo; + + if (!resolve_undo) + return 0; + + for_each_string_list_item(item, resolve_undo) { + const char *path = item->string; + struct resolve_undo_info *ru = item->util; + int i; + + if (!ru) + continue; + for (i = 0; i < 3; i++) { + struct object *obj; + + if (!ru->mode[i] || !S_ISREG(ru->mode[i])) + continue; + + obj = parse_object(the_repository, &ru->oid[i]); + if (!obj) { + error(_("%s: invalid sha1 pointer in resolve-undo"), + oid_to_hex(&ru->oid[i])); + errors_found |= ERROR_REFS; + continue; + } + obj->flags |= USED; + fsck_put_object_name(&fsck_walk_options, &ru->oid[i], + ":(%d):%s", i, path); + mark_object_reachable(obj); + } + } + return 0; +} + static void mark_object_for_connectivity(const struct object_id *oid) { struct object *obj = lookup_unknown_object(the_repository, oid); @@ -938,6 +976,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) } if (active_cache_tree) fsck_cache_tree(active_cache_tree); + fsck_resolve_undo(&the_index); } check_connectivity(); diff --git a/builtin/gc.c b/builtin/gc.c index 021e9256ae..eeff2b760e 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -168,9 +168,15 @@ struct maintenance_run_opts; static int maintenance_task_pack_refs(MAYBE_UNUSED struct maintenance_run_opts *opts) { struct strvec pack_refs_cmd = STRVEC_INIT; + int ret; + strvec_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL); - return run_command_v_opt(pack_refs_cmd.v, RUN_GIT_CMD); + ret = run_command_v_opt(pack_refs_cmd.v, RUN_GIT_CMD); + + strvec_clear(&pack_refs_cmd); + + return ret; } static int too_many_loose_objects(void) diff --git a/builtin/grep.c b/builtin/grep.c index bcb07ea7f7..e6bcdf860c 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -961,6 +961,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_BOOL_F(0, "ext-grep", &external_grep_allowed__ignored, N_("allow calling of grep(1) (ignored by this build)"), PARSE_OPT_NOCOMPLETE), + OPT_INTEGER('m', "max-count", &opt.max_count, + N_("maximum number of results per file")), OPT_END() }; grep_prefix = prefix; @@ -1101,6 +1103,13 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (recurse_submodules && untracked) die(_("--untracked not supported with --recurse-submodules")); + /* + * Optimize out the case where the amount of matches is limited to zero. + * We do this to keep results consistent with GNU grep(1). + */ + if (opt.max_count == 0) + return 1; + if (show_in_pager) { if (num_threads > 1) warning(_("invalid option combination, ignoring --threads")); diff --git a/builtin/merge-file.c b/builtin/merge-file.c index e695867ee5..c923bbf2ab 100644 --- a/builtin/merge-file.c +++ b/builtin/merge-file.c @@ -25,10 +25,10 @@ static int label_cb(const struct option *opt, const char *arg, int unset) int cmd_merge_file(int argc, const char **argv, const char *prefix) { - const char *names[3] = { NULL, NULL, NULL }; - mmfile_t mmfs[3]; - mmbuffer_t result = {NULL, 0}; - xmparam_t xmp = {{0}}; + const char *names[3] = { 0 }; + mmfile_t mmfs[3] = { 0 }; + mmbuffer_t result = { 0 }; + xmparam_t xmp = { 0 }; int ret = 0, i = 0, to_stdout = 0; int quiet = 0; struct option options[] = { @@ -71,21 +71,24 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) for (i = 0; i < 3; i++) { char *fname; - int ret; + mmfile_t *mmf = mmfs + i; if (!names[i]) names[i] = argv[i]; fname = prefix_filename(prefix, argv[i]); - ret = read_mmfile(mmfs + i, fname); + + if (read_mmfile(mmf, fname)) + ret = -1; + else if (mmf->size > MAX_XDIFF_SIZE || + buffer_is_binary(mmf->ptr, mmf->size)) + ret = error("Cannot merge binary files: %s", + argv[i]); + free(fname); if (ret) - return -1; + goto cleanup; - if (mmfs[i].size > MAX_XDIFF_SIZE || - buffer_is_binary(mmfs[i].ptr, mmfs[i].size)) - return error("Cannot merge binary files: %s", - argv[i]); } xmp.ancestor = names[1]; @@ -93,9 +96,6 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) xmp.file2 = names[2]; ret = xdl_merge(mmfs + 1, mmfs + 0, mmfs + 2, &xmp, &result); - for (i = 0; i < 3; i++) - free(mmfs[i].ptr); - if (ret >= 0) { const char *filename = argv[0]; char *fpath = prefix_filename(prefix, argv[0]); @@ -116,5 +116,9 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) if (ret > 127) ret = 127; +cleanup: + for (i = 0; i < 3; i++) + free(mmfs[i].ptr); + return ret; } diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index 5dc94d6f88..ae5782917b 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -2,13 +2,18 @@ #include "builtin.h" #include "tree-walk.h" #include "xdiff-interface.h" +#include "help.h" +#include "commit-reach.h" +#include "merge-ort.h" #include "object-store.h" +#include "parse-options.h" #include "repository.h" #include "blob.h" #include "exec-cmd.h" #include "merge-blobs.h" +#include "quote.h" -static const char merge_tree_usage[] = "git merge-tree <base-tree> <branch1> <branch2>"; +static int line_termination = '\n'; struct merge_list { struct merge_list *next; @@ -28,7 +33,7 @@ static void add_merge_entry(struct merge_list *entry) merge_result_end = &entry->next; } -static void merge_trees(struct tree_desc t[3], const char *base); +static void trivial_merge_trees(struct tree_desc t[3], const char *base); static const char *explanation(struct merge_list *entry) { @@ -225,7 +230,7 @@ static void unresolved_directory(const struct traverse_info *info, buf2 = fill_tree_descriptor(r, t + 2, ENTRY_OID(n + 2)); #undef ENTRY_OID - merge_trees(t, newbase); + trivial_merge_trees(t, newbase); free(buf0); free(buf1); @@ -342,7 +347,7 @@ static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, s return mask; } -static void merge_trees(struct tree_desc t[3], const char *base) +static void trivial_merge_trees(struct tree_desc t[3], const char *base) { struct traverse_info info; @@ -366,19 +371,18 @@ static void *get_tree_descriptor(struct repository *r, return buf; } -int cmd_merge_tree(int argc, const char **argv, const char *prefix) +static int trivial_merge(const char *base, + const char *branch1, + const char *branch2) { struct repository *r = the_repository; struct tree_desc t[3]; void *buf1, *buf2, *buf3; - if (argc != 4) - usage(merge_tree_usage); - - buf1 = get_tree_descriptor(r, t+0, argv[1]); - buf2 = get_tree_descriptor(r, t+1, argv[2]); - buf3 = get_tree_descriptor(r, t+2, argv[3]); - merge_trees(t, ""); + buf1 = get_tree_descriptor(r, t+0, base); + buf2 = get_tree_descriptor(r, t+1, branch1); + buf3 = get_tree_descriptor(r, t+2, branch2); + trivial_merge_trees(t, ""); free(buf1); free(buf2); free(buf3); @@ -386,3 +390,162 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix) show_result(); return 0; } + +enum mode { + MODE_UNKNOWN, + MODE_TRIVIAL, + MODE_REAL, +}; + +struct merge_tree_options { + int mode; + int allow_unrelated_histories; + int show_messages; + int name_only; +}; + +static int real_merge(struct merge_tree_options *o, + const char *branch1, const char *branch2, + const char *prefix) +{ + struct commit *parent1, *parent2; + struct commit_list *merge_bases = NULL; + struct merge_options opt; + struct merge_result result = { 0 }; + + parent1 = get_merge_parent(branch1); + if (!parent1) + help_unknown_ref(branch1, "merge-tree", + _("not something we can merge")); + + parent2 = get_merge_parent(branch2); + if (!parent2) + help_unknown_ref(branch2, "merge-tree", + _("not something we can merge")); + + init_merge_options(&opt, the_repository); + + opt.show_rename_progress = 0; + + opt.branch1 = branch1; + opt.branch2 = branch2; + + /* + * Get the merge bases, in reverse order; see comment above + * merge_incore_recursive in merge-ort.h + */ + merge_bases = get_merge_bases(parent1, parent2); + if (!merge_bases && !o->allow_unrelated_histories) + die(_("refusing to merge unrelated histories")); + merge_bases = reverse_commit_list(merge_bases); + + merge_incore_recursive(&opt, merge_bases, parent1, parent2, &result); + if (result.clean < 0) + die(_("failure to merge")); + + if (o->show_messages == -1) + o->show_messages = !result.clean; + + printf("%s%c", oid_to_hex(&result.tree->object.oid), line_termination); + if (!result.clean) { + struct string_list conflicted_files = STRING_LIST_INIT_NODUP; + const char *last = NULL; + int i; + + merge_get_conflicted_files(&result, &conflicted_files); + for (i = 0; i < conflicted_files.nr; i++) { + const char *name = conflicted_files.items[i].string; + struct stage_info *c = conflicted_files.items[i].util; + if (!o->name_only) + printf("%06o %s %d\t", + c->mode, oid_to_hex(&c->oid), c->stage); + else if (last && !strcmp(last, name)) + continue; + write_name_quoted_relative( + name, prefix, stdout, line_termination); + last = name; + } + string_list_clear(&conflicted_files, 1); + } + if (o->show_messages) { + putchar(line_termination); + merge_display_update_messages(&opt, line_termination == '\0', + &result); + } + merge_finalize(&opt, &result); + return !result.clean; /* result.clean < 0 handled above */ +} + +int cmd_merge_tree(int argc, const char **argv, const char *prefix) +{ + struct merge_tree_options o = { .show_messages = -1 }; + int expected_remaining_argc; + int original_argc; + + const char * const merge_tree_usage[] = { + N_("git merge-tree [--write-tree] [<options>] <branch1> <branch2>"), + N_("git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>"), + NULL + }; + struct option mt_options[] = { + OPT_CMDMODE(0, "write-tree", &o.mode, + N_("do a real merge instead of a trivial merge"), + MODE_REAL), + OPT_CMDMODE(0, "trivial-merge", &o.mode, + N_("do a trivial merge only"), MODE_TRIVIAL), + OPT_BOOL(0, "messages", &o.show_messages, + N_("also show informational/conflict messages")), + OPT_SET_INT('z', NULL, &line_termination, + N_("separate paths with the NUL character"), '\0'), + OPT_BOOL_F(0, "name-only", + &o.name_only, + N_("list filenames without modes/oids/stages"), + PARSE_OPT_NONEG), + OPT_BOOL_F(0, "allow-unrelated-histories", + &o.allow_unrelated_histories, + N_("allow merging unrelated histories"), + PARSE_OPT_NONEG), + OPT_END() + }; + + /* Parse arguments */ + original_argc = argc - 1; /* ignoring argv[0] */ + argc = parse_options(argc, argv, prefix, mt_options, + merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION); + switch (o.mode) { + default: + BUG("unexpected command mode %d", o.mode); + case MODE_UNKNOWN: + switch (argc) { + default: + usage_with_options(merge_tree_usage, mt_options); + case 2: + o.mode = MODE_REAL; + break; + case 3: + o.mode = MODE_TRIVIAL; + break; + } + expected_remaining_argc = argc; + break; + case MODE_REAL: + expected_remaining_argc = 2; + break; + case MODE_TRIVIAL: + expected_remaining_argc = 3; + /* Removal of `--trivial-merge` is expected */ + original_argc--; + break; + } + if (o.mode == MODE_TRIVIAL && argc < original_argc) + die(_("--trivial-merge is incompatible with all other options")); + + if (argc != expected_remaining_argc) + usage_with_options(merge_tree_usage, mt_options); + + /* Do the relevant type of merge */ + if (o.mode == MODE_REAL) + return real_merge(&o, argv[0], argv[1], prefix); + else + return trivial_merge(argv[0], argv[1], argv[2]); +} diff --git a/builtin/merge.c b/builtin/merge.c index d9784d4891..23170f2d2a 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -375,7 +375,6 @@ static void reset_hard(const struct object_id *oid, int verbose) static void restore_state(const struct object_id *head, const struct object_id *stash) { - struct strbuf sb = STRBUF_INIT; const char *args[] = { "stash", "apply", NULL, NULL }; if (is_null_oid(stash)) @@ -391,7 +390,6 @@ static void restore_state(const struct object_id *head, */ run_command_v_opt(args, RUN_GIT_CMD); - strbuf_release(&sb); refresh_cache(REFRESH_QUIET); } @@ -502,7 +500,6 @@ static void merge_name(const char *remote, struct strbuf *msg) { struct commit *remote_head; struct object_id branch_head; - struct strbuf buf = STRBUF_INIT; struct strbuf bname = STRBUF_INIT; struct merge_remote_desc *desc; const char *ptr; @@ -590,7 +587,6 @@ static void merge_name(const char *remote, struct strbuf *msg) oid_to_hex(&remote_head->object.oid), remote); cleanup: free(found_ref); - strbuf_release(&buf); strbuf_release(&bname); } diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c index 5edbb7fe86..8f24d59a75 100644 --- a/builtin/multi-pack-index.c +++ b/builtin/multi-pack-index.c @@ -134,7 +134,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv) opts.flags |= MIDX_PROGRESS; argc = parse_options(argc, argv, NULL, options, builtin_multi_pack_index_write_usage, - PARSE_OPT_KEEP_UNKNOWN); + 0); if (argc) usage_with_options(builtin_multi_pack_index_write_usage, options); @@ -176,7 +176,7 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv) opts.flags |= MIDX_PROGRESS; argc = parse_options(argc, argv, NULL, options, builtin_multi_pack_index_verify_usage, - PARSE_OPT_KEEP_UNKNOWN); + 0); if (argc) usage_with_options(builtin_multi_pack_index_verify_usage, options); @@ -202,7 +202,7 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv) opts.flags |= MIDX_PROGRESS; argc = parse_options(argc, argv, NULL, options, builtin_multi_pack_index_expire_usage, - PARSE_OPT_KEEP_UNKNOWN); + 0); if (argc) usage_with_options(builtin_multi_pack_index_expire_usage, options); @@ -232,7 +232,7 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv) argc = parse_options(argc, argv, NULL, options, builtin_multi_pack_index_repack_usage, - PARSE_OPT_KEEP_UNKNOWN); + 0); if (argc) usage_with_options(builtin_multi_pack_index_repack_usage, options); diff --git a/builtin/mv.c b/builtin/mv.c index 83a465ba83..4729bb1a1a 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -13,12 +13,21 @@ #include "string-list.h" #include "parse-options.h" #include "submodule.h" +#include "entry.h" static const char * const builtin_mv_usage[] = { N_("git mv [<options>] <source>... <destination>"), NULL }; +enum update_mode { + BOTH = 0, + WORKING_DIRECTORY = (1 << 1), + INDEX = (1 << 2), + SPARSE = (1 << 3), + SKIP_WORKTREE_DIR = (1 << 4), +}; + #define DUP_BASENAME 1 #define KEEP_TRAILING_SLASH 2 @@ -115,6 +124,36 @@ static int index_range_of_same_dir(const char *src, int length, return last - first; } +/* + * Check if an out-of-cone directory should be in the index. Imagine this case + * that all the files under a directory are marked with 'CE_SKIP_WORKTREE' bit + * and thus the directory is sparsified. + * + * Return 0 if such directory exist (i.e. with any of its contained files not + * marked with CE_SKIP_WORKTREE, the directory would be present in working tree). + * Return 1 otherwise. + */ +static int check_dir_in_index(const char *name) +{ + const char *with_slash = add_slash(name); + int length = strlen(with_slash); + + int pos = cache_name_pos(with_slash, length); + const struct cache_entry *ce; + + if (pos < 0) { + pos = -pos - 1; + if (pos >= the_index.cache_nr) + return 1; + ce = active_cache[pos]; + if (strncmp(with_slash, ce->name, length)) + return 1; + if (ce_skip_worktree(ce)) + return 0; + } + return 1; +} + int cmd_mv(int argc, const char **argv, const char *prefix) { int i, flags, gitmodules_modified = 0; @@ -129,7 +168,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) OPT_END(), }; const char **source, **destination, **dest_path, **submodule_gitfile; - enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX, SPARSE } *modes; + enum update_mode *modes; struct stat st; struct string_list src_for_dst = STRING_LIST_INIT_NODUP; struct lock_file lock_file = LOCK_INIT; @@ -148,7 +187,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix) die(_("index file corrupt")); source = internal_prefix_pathspec(prefix, argv, argc, 0); - modes = xcalloc(argc, sizeof(enum update_mode)); + CALLOC_ARRAY(modes, argc); + /* * Keep trailing slash, needed to let * "git mv file no-such-dir/" error out, except in the case @@ -176,7 +216,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) /* Checking */ for (i = 0; i < argc; i++) { const char *src = source[i], *dst = destination[i]; - int length, src_is_dir; + int length; const char *bad = NULL; int skip_sparse = 0; @@ -185,54 +225,103 @@ int cmd_mv(int argc, const char **argv, const char *prefix) length = strlen(src); if (lstat(src, &st) < 0) { - /* only error if existence is expected. */ - if (modes[i] != SPARSE) + int pos; + const struct cache_entry *ce; + + pos = cache_name_pos(src, length); + if (pos < 0) { + const char *src_w_slash = add_slash(src); + if (!path_in_sparse_checkout(src_w_slash, &the_index) && + !check_dir_in_index(src)) { + modes[i] |= SKIP_WORKTREE_DIR; + goto dir_check; + } + /* only error if existence is expected. */ + if (!(modes[i] & SPARSE)) + bad = _("bad source"); + goto act_on_entry; + } + ce = active_cache[pos]; + if (!ce_skip_worktree(ce)) { bad = _("bad source"); - } else if (!strncmp(src, dst, length) && - (dst[length] == 0 || dst[length] == '/')) { + goto act_on_entry; + } + if (!ignore_sparse) { + string_list_append(&only_match_skip_worktree, src); + goto act_on_entry; + } + /* Check if dst exists in index */ + if (cache_name_pos(dst, strlen(dst)) < 0) { + modes[i] |= SPARSE; + goto act_on_entry; + } + if (!force) { + bad = _("destination exists"); + goto act_on_entry; + } + modes[i] |= SPARSE; + goto act_on_entry; + } + if (!strncmp(src, dst, length) && + (dst[length] == 0 || dst[length] == '/')) { bad = _("can not move directory into itself"); - } else if ((src_is_dir = S_ISDIR(st.st_mode)) - && lstat(dst, &st) == 0) + goto act_on_entry; + } + if (S_ISDIR(st.st_mode) + && lstat(dst, &st) == 0) { bad = _("cannot move directory over file"); - else if (src_is_dir) { + goto act_on_entry; + } + +dir_check: + if (S_ISDIR(st.st_mode)) { + int j, dst_len, n; int first = cache_name_pos(src, length), last; - if (first >= 0) + if (first >= 0) { prepare_move_submodule(src, first, submodule_gitfile + i); - else if (index_range_of_same_dir(src, length, - &first, &last) < 1) + goto act_on_entry; + } else if (index_range_of_same_dir(src, length, + &first, &last) < 1) { bad = _("source directory is empty"); - else { /* last - first >= 1 */ - int j, dst_len, n; - - modes[i] = WORKING_DIRECTORY; - n = argc + last - first; - REALLOC_ARRAY(source, n); - REALLOC_ARRAY(destination, n); - REALLOC_ARRAY(modes, n); - REALLOC_ARRAY(submodule_gitfile, n); - - dst = add_slash(dst); - dst_len = strlen(dst); - - for (j = 0; j < last - first; j++) { - const struct cache_entry *ce = active_cache[first + j]; - const char *path = ce->name; - source[argc + j] = path; - destination[argc + j] = - prefix_path(dst, dst_len, path + length + 1); - modes[argc + j] = ce_skip_worktree(ce) ? SPARSE : INDEX; - submodule_gitfile[argc + j] = NULL; - } - argc += last - first; + goto act_on_entry; } - } else if (!(ce = cache_file_exists(src, length, 0))) { + + /* last - first >= 1 */ + modes[i] |= WORKING_DIRECTORY; + n = argc + last - first; + REALLOC_ARRAY(source, n); + REALLOC_ARRAY(destination, n); + REALLOC_ARRAY(modes, n); + REALLOC_ARRAY(submodule_gitfile, n); + + dst = add_slash(dst); + dst_len = strlen(dst); + + for (j = 0; j < last - first; j++) { + const struct cache_entry *ce = active_cache[first + j]; + const char *path = ce->name; + source[argc + j] = path; + destination[argc + j] = + prefix_path(dst, dst_len, path + length + 1); + memset(modes + argc + j, 0, sizeof(enum update_mode)); + modes[argc + j] |= ce_skip_worktree(ce) ? SPARSE : INDEX; + submodule_gitfile[argc + j] = NULL; + } + argc += last - first; + goto act_on_entry; + } + if (!(ce = cache_file_exists(src, length, 0))) { bad = _("not under version control"); - } else if (ce_stage(ce)) { + goto act_on_entry; + } + if (ce_stage(ce)) { bad = _("conflicted"); - } else if (lstat(dst, &st) == 0 && - (!ignore_case || strcasecmp(src, dst))) { + goto act_on_entry; + } + if (lstat(dst, &st) == 0 && + (!ignore_case || strcasecmp(src, dst))) { bad = _("destination exists"); if (force) { /* @@ -246,34 +335,40 @@ int cmd_mv(int argc, const char **argv, const char *prefix) } else bad = _("Cannot overwrite"); } - } else if (string_list_has_string(&src_for_dst, dst)) + goto act_on_entry; + } + if (string_list_has_string(&src_for_dst, dst)) { bad = _("multiple sources for the same target"); - else if (is_dir_sep(dst[strlen(dst) - 1])) + goto act_on_entry; + } + if (is_dir_sep(dst[strlen(dst) - 1])) { bad = _("destination directory does not exist"); - else { - /* - * We check if the paths are in the sparse-checkout - * definition as a very final check, since that - * allows us to point the user to the --sparse - * option as a way to have a successful run. - */ - if (!ignore_sparse && - !path_in_sparse_checkout(src, &the_index)) { - string_list_append(&only_match_skip_worktree, src); - skip_sparse = 1; - } - if (!ignore_sparse && - !path_in_sparse_checkout(dst, &the_index)) { - string_list_append(&only_match_skip_worktree, dst); - skip_sparse = 1; - } - - if (skip_sparse) - goto remove_entry; + goto act_on_entry; + } - string_list_insert(&src_for_dst, dst); + /* + * We check if the paths are in the sparse-checkout + * definition as a very final check, since that + * allows us to point the user to the --sparse + * option as a way to have a successful run. + */ + if (!ignore_sparse && + !path_in_sparse_checkout(src, &the_index)) { + string_list_append(&only_match_skip_worktree, src); + skip_sparse = 1; } + if (!ignore_sparse && + !path_in_sparse_checkout(dst, &the_index)) { + string_list_append(&only_match_skip_worktree, dst); + skip_sparse = 1; + } + + if (skip_sparse) + goto remove_entry; + + string_list_insert(&src_for_dst, dst); +act_on_entry: if (!bad) continue; if (!ignore_errors) @@ -282,14 +377,11 @@ int cmd_mv(int argc, const char **argv, const char *prefix) remove_entry: if (--argc > 0) { int n = argc - i; - memmove(source + i, source + i + 1, - n * sizeof(char *)); - memmove(destination + i, destination + i + 1, - n * sizeof(char *)); - memmove(modes + i, modes + i + 1, - n * sizeof(enum update_mode)); - memmove(submodule_gitfile + i, submodule_gitfile + i + 1, - n * sizeof(char *)); + MOVE_ARRAY(source + i, source + i + 1, n); + MOVE_ARRAY(destination + i, destination + i + 1, n); + MOVE_ARRAY(modes + i, modes + i + 1, n); + MOVE_ARRAY(submodule_gitfile + i, + submodule_gitfile + i + 1, n); i--; } } @@ -304,11 +396,17 @@ remove_entry: const char *src = source[i], *dst = destination[i]; enum update_mode mode = modes[i]; int pos; + struct checkout state = CHECKOUT_INIT; + state.istate = &the_index; + + if (force) + state.force = 1; if (show_only || verbose) printf(_("Renaming %s to %s\n"), src, dst); if (show_only) continue; - if (mode != INDEX && mode != SPARSE && rename(src, dst) < 0) { + if (!(mode & (INDEX | SPARSE | SKIP_WORKTREE_DIR)) && + rename(src, dst) < 0) { if (ignore_errors) continue; die_errno(_("renaming '%s' failed"), src); @@ -322,12 +420,23 @@ remove_entry: 1); } - if (mode == WORKING_DIRECTORY) + if (mode & (WORKING_DIRECTORY | SKIP_WORKTREE_DIR)) continue; pos = cache_name_pos(src, strlen(src)); assert(pos >= 0); rename_cache_entry_at(pos, dst); + + if ((mode & SPARSE) && + (path_in_sparse_checkout(dst, &the_index))) { + int dst_pos; + + dst_pos = cache_name_pos(dst, strlen(dst)); + active_cache[dst_pos]->ce_flags &= ~CE_SKIP_WORKTREE; + + if (checkout_entry(active_cache[dst_pos], &state, NULL, NULL)) + die(_("cannot checkout %s"), active_cache[dst_pos]->name); + } } if (gitmodules_modified) diff --git a/builtin/pull.c b/builtin/pull.c index 01155ba67b..403a24d7ca 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -990,6 +990,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix) int rebase_unspecified = 0; int can_ff; int divergent; + int ret; if (!getenv("GIT_REFLOG_ACTION")) set_reflog_message(argc, argv); @@ -1100,7 +1101,8 @@ int cmd_pull(int argc, const char **argv, const char *prefix) if (is_null_oid(&orig_head)) { if (merge_heads.nr > 1) die(_("Cannot merge multiple branches into empty head.")); - return pull_into_void(merge_heads.oid, &curr_head); + ret = pull_into_void(merge_heads.oid, &curr_head); + goto cleanup; } if (merge_heads.nr > 1) { if (opt_rebase) @@ -1125,8 +1127,6 @@ int cmd_pull(int argc, const char **argv, const char *prefix) } if (opt_rebase) { - int ret = 0; - struct object_id newbase; struct object_id upstream; get_rebase_newbase_and_upstream(&newbase, &upstream, &curr_head, @@ -1149,12 +1149,16 @@ int cmd_pull(int argc, const char **argv, const char *prefix) recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND)) ret = rebase_submodules(); - return ret; + goto cleanup; } else { - int ret = run_merge(); + ret = run_merge(); if (!ret && (recurse_submodules == RECURSE_SUBMODULES_ON || recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND)) ret = update_submodules(); - return ret; + goto cleanup; } + +cleanup: + oid_array_clear(&merge_heads); + return ret; } diff --git a/builtin/remote.c b/builtin/remote.c index d4b69fe778..d9b8746cb3 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -344,12 +344,13 @@ static void read_branches(void) struct ref_states { struct remote *remote; - struct string_list new_refs, stale, tracked, heads, push; + struct string_list new_refs, skipped, stale, tracked, heads, push; int queried; }; #define REF_STATES_INIT { \ .new_refs = STRING_LIST_INIT_DUP, \ + .skipped = STRING_LIST_INIT_DUP, \ .stale = STRING_LIST_INIT_DUP, \ .tracked = STRING_LIST_INIT_DUP, \ .heads = STRING_LIST_INIT_DUP, \ @@ -368,7 +369,9 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat states->remote->fetch.raw[i]); for (ref = fetch_map; ref; ref = ref->next) { - if (!ref->peer_ref || !ref_exists(ref->peer_ref->name)) + if (omit_name_by_refspec(ref->name, &states->remote->fetch)) + string_list_append(&states->skipped, abbrev_branch(ref->name)); + else if (!ref->peer_ref || !ref_exists(ref->peer_ref->name)) string_list_append(&states->new_refs, abbrev_branch(ref->name)); else string_list_append(&states->tracked, abbrev_branch(ref->name)); @@ -383,6 +386,7 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat free_refs(fetch_map); string_list_sort(&states->new_refs); + string_list_sort(&states->skipped); string_list_sort(&states->tracked); string_list_sort(&states->stale); @@ -941,6 +945,7 @@ static void clear_push_info(void *util, const char *string) static void free_remote_ref_states(struct ref_states *states) { string_list_clear(&states->new_refs, 0); + string_list_clear(&states->skipped, 0); string_list_clear(&states->stale, 1); string_list_clear(&states->tracked, 0); string_list_clear(&states->heads, 0); @@ -1035,6 +1040,8 @@ static int show_remote_info_item(struct string_list_item *item, void *cb_data) arg = states->remote->name; } else if (string_list_has_string(&states->tracked, name)) arg = _(" tracked"); + else if (string_list_has_string(&states->skipped, name)) + arg = _(" skipped"); else if (string_list_has_string(&states->stale, name)) arg = _(" stale (use 'git remote prune' to remove)"); else @@ -1308,6 +1315,7 @@ static int show(int argc, const char **argv) /* remote branch info */ info.width = 0; for_each_string_list(&info.states.new_refs, add_remote_to_show_info, &info); + for_each_string_list(&info.states.skipped, add_remote_to_show_info, &info); for_each_string_list(&info.states.tracked, add_remote_to_show_info, &info); for_each_string_list(&info.states.stale, add_remote_to_show_info, &info); if (info.list.nr) diff --git a/builtin/repack.c b/builtin/repack.c index 4a7ae4cf48..482b66f57d 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -727,7 +727,6 @@ int cmd_repack(int argc, const char **argv, const char *prefix) struct child_process cmd = CHILD_PROCESS_INIT; struct string_list_item *item; struct string_list names = STRING_LIST_INIT_DUP; - struct string_list rollback = STRING_LIST_INIT_NODUP; struct string_list existing_nonkept_packs = STRING_LIST_INIT_DUP; struct string_list existing_kept_packs = STRING_LIST_INIT_DUP; struct pack_geometry *geometry = NULL; @@ -1117,7 +1116,6 @@ int cmd_repack(int argc, const char **argv, const char *prefix) } string_list_clear(&names, 0); - string_list_clear(&rollback, 0); string_list_clear(&existing_nonkept_packs, 0); string_list_clear(&existing_kept_packs, 0); clear_pack_geometry(geometry); diff --git a/builtin/revert.c b/builtin/revert.c index f84c253f4c..2554f9099c 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -246,6 +246,9 @@ int cmd_revert(int argc, const char **argv, const char *prefix) res = run_sequencer(argc, argv, &opts); if (res < 0) die(_("revert failed")); + if (opts.revs) + release_revisions(opts.revs); + free(opts.revs); return res; } diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index c597df7528..fac52ade5e 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -118,10 +118,11 @@ static int resolve_relative_url_test(int argc, const char **argv, const char *pr return 0; } -static char *do_get_submodule_displaypath(const char *path, - const char *prefix, - const char *super_prefix) +/* the result should be freed by the caller. */ +static char *get_submodule_displaypath(const char *path, const char *prefix) { + const char *super_prefix = get_super_prefix(); + if (prefix && super_prefix) { BUG("cannot have prefix '%s' and superprefix '%s'", prefix, super_prefix); @@ -137,13 +138,6 @@ static char *do_get_submodule_displaypath(const char *path, } } -/* the result should be freed by the caller. */ -static char *get_submodule_displaypath(const char *path, const char *prefix) -{ - const char *super_prefix = get_super_prefix(); - return do_get_submodule_displaypath(path, prefix, super_prefix); -} - static char *compute_rev_name(const char *sub_path, const char* object_id) { struct strbuf sb = STRBUF_INIT; @@ -444,7 +438,7 @@ static int module_foreach(int argc, const char **argv, const char *prefix) }; const char *const git_submodule_helper_usage[] = { - N_("git submodule--helper foreach [--quiet] [--recursive] [--] <command>"), + N_("git submodule foreach [--quiet] [--recursive] [--] <command>"), NULL }; @@ -477,22 +471,18 @@ static int starts_with_dot_dot_slash(const char *const path) struct init_cb { const char *prefix; - const char *superprefix; unsigned int flags; }; #define INIT_CB_INIT { 0 } static void init_submodule(const char *path, const char *prefix, - const char *superprefix, unsigned int flags) + unsigned int flags) { const struct submodule *sub; struct strbuf sb = STRBUF_INIT; char *upd = NULL, *url = NULL, *displaypath; - /* try superprefix from the environment, if it is not passed explicitly */ - if (!superprefix) - superprefix = get_super_prefix(); - displaypath = do_get_submodule_displaypath(path, prefix, superprefix); + displaypath = get_submodule_displaypath(path, prefix); sub = submodule_from_path(the_repository, null_oid(), path); @@ -566,7 +556,7 @@ static void init_submodule(const char *path, const char *prefix, static void init_submodule_cb(const struct cache_entry *list_item, void *cb_data) { struct init_cb *info = cb_data; - init_submodule(list_item->name, info->prefix, info->superprefix, info->flags); + init_submodule(list_item->name, info->prefix, info->flags); } static int module_init(int argc, const char **argv, const char *prefix) @@ -582,7 +572,7 @@ static int module_init(int argc, const char **argv, const char *prefix) }; const char *const git_submodule_helper_usage[] = { - N_("git submodule--helper init [<options>] [<path>]"), + N_("git submodule init [<options>] [<path>]"), NULL }; @@ -1185,7 +1175,7 @@ static int module_summary(int argc, const char **argv, const char *prefix) }; const char *const git_submodule_helper_usage[] = { - N_("git submodule--helper summary [<options>] [<commit>] [--] [<path>]"), + N_("git submodule summary [<options>] [<commit>] [--] [<path>]"), NULL }; @@ -1349,7 +1339,7 @@ static int module_sync(int argc, const char **argv, const char *prefix) }; const char *const git_submodule_helper_usage[] = { - N_("git submodule--helper sync [--quiet] [--recursive] [<path>]"), + N_("git submodule sync [--quiet] [--recursive] [<path>]"), NULL }; @@ -1818,7 +1808,7 @@ static int module_clone(int argc, const char **argv, const char *prefix) static void determine_submodule_update_strategy(struct repository *r, int just_cloned, const char *path, - const char *update, + enum submodule_update_type update, struct submodule_update_strategy *out) { const struct submodule *sub = submodule_from_path(r, null_oid(), path); @@ -1828,9 +1818,7 @@ static void determine_submodule_update_strategy(struct repository *r, key = xstrfmt("submodule.%s.update", sub->name); if (update) { - if (parse_submodule_update_strategy(update, out) < 0) - die(_("Invalid update mode '%s' for submodule path '%s'"), - update, path); + out->type = update; } else if (!repo_config_get_string_tmp(r, key, &val)) { if (parse_submodule_update_strategy(val, out) < 0) die(_("Invalid update mode '%s' configured for submodule path '%s'"), @@ -1880,9 +1868,8 @@ struct submodule_update_clone { struct update_data { const char *prefix; - const char *recursive_prefix; const char *displaypath; - const char *update_default; + enum submodule_update_type update_default; struct object_id suboid; struct string_list references; struct submodule_update_strategy update_strategy; @@ -1949,30 +1936,20 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, const char *update_string; enum submodule_update_type update_type; char *key; - struct strbuf displaypath_sb = STRBUF_INIT; + struct update_data *ud = suc->update_data; + char *displaypath = get_submodule_displaypath(ce->name, ud->prefix); struct strbuf sb = STRBUF_INIT; - const char *displaypath = NULL; int needs_cloning = 0; int need_free_url = 0; if (ce_stage(ce)) { - if (suc->update_data->recursive_prefix) - strbuf_addf(&sb, "%s/%s", suc->update_data->recursive_prefix, ce->name); - else - strbuf_addstr(&sb, ce->name); - strbuf_addf(out, _("Skipping unmerged submodule %s"), sb.buf); + strbuf_addf(out, _("Skipping unmerged submodule %s"), displaypath); strbuf_addch(out, '\n'); goto cleanup; } sub = submodule_from_path(the_repository, null_oid(), ce->name); - if (suc->update_data->recursive_prefix) - displaypath = relative_path(suc->update_data->recursive_prefix, - ce->name, &displaypath_sb); - else - displaypath = ce->name; - if (!sub) { next_submodule_warn_missing(suc, out, displaypath); goto cleanup; @@ -2062,7 +2039,7 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, "--no-single-branch"); cleanup: - strbuf_release(&displaypath_sb); + free(displaypath); strbuf_release(&sb); if (need_free_url) free((void*)url); @@ -2405,13 +2382,33 @@ static void ensure_core_worktree(const char *path) } } +static const char *submodule_update_type_to_label(enum submodule_update_type type) +{ + switch (type) { + case SM_UPDATE_CHECKOUT: + return "checkout"; + case SM_UPDATE_MERGE: + return "merge"; + case SM_UPDATE_REBASE: + return "rebase"; + case SM_UPDATE_UNSPECIFIED: + case SM_UPDATE_NONE: + case SM_UPDATE_COMMAND: + break; + } + BUG("unreachable with type %d", type); +} + static void update_data_to_args(struct update_data *update_data, struct strvec *args) { + enum submodule_update_type update_type = update_data->update_default; + + if (update_data->displaypath) { + strvec_push(args, "--super-prefix"); + strvec_pushf(args, "%s/", update_data->displaypath); + } strvec_pushl(args, "submodule--helper", "update", "--recursive", NULL); strvec_pushf(args, "--jobs=%d", update_data->max_jobs); - if (update_data->recursive_prefix) - strvec_pushl(args, "--recursive-prefix", - update_data->recursive_prefix, NULL); if (update_data->quiet) strvec_push(args, "--quiet"); if (update_data->force) @@ -2430,8 +2427,10 @@ static void update_data_to_args(struct update_data *update_data, struct strvec * strvec_push(args, "--require-init"); if (update_data->depth) strvec_pushf(args, "--depth=%d", update_data->depth); - if (update_data->update_default) - strvec_pushl(args, "--update", update_data->update_default, NULL); + if (update_type != SM_UPDATE_UNSPECIFIED) + strvec_pushf(args, "--%s", + submodule_update_type_to_label(update_type)); + if (update_data->references.nr) { struct string_list_item *item; for_each_string_list_item(item, &update_data->references) @@ -2453,19 +2452,10 @@ static void update_data_to_args(struct update_data *update_data, struct strvec * static int update_submodule(struct update_data *update_data) { - char *prefixed_path; - ensure_core_worktree(update_data->sm_path); - if (update_data->recursive_prefix) - prefixed_path = xstrfmt("%s%s", update_data->recursive_prefix, - update_data->sm_path); - else - prefixed_path = xstrdup(update_data->sm_path); - - update_data->displaypath = get_submodule_displaypath(prefixed_path, - update_data->prefix); - free(prefixed_path); + update_data->displaypath = get_submodule_displaypath( + update_data->sm_path, update_data->prefix); determine_submodule_update_strategy(the_repository, update_data->just_cloned, update_data->sm_path, update_data->update_default, @@ -2505,14 +2495,6 @@ static int update_submodule(struct update_data *update_data) struct update_data next = *update_data; int res; - if (update_data->recursive_prefix) - prefixed_path = xstrfmt("%s%s/", update_data->recursive_prefix, - update_data->sm_path); - else - prefixed_path = xstrfmt("%s/", update_data->sm_path); - - next.recursive_prefix = get_submodule_displaypath(prefixed_path, - update_data->prefix); next.prefix = NULL; oidcpy(&next.oid, null_oid()); oidcpy(&next.suboid, null_oid()); @@ -2597,13 +2579,15 @@ static int module_update(int argc, const char **argv, const char *prefix) OPT_STRING(0, "prefix", &opt.prefix, N_("path"), N_("path into the working tree")), - OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix, - N_("path"), - N_("path into the working tree, across nested " - "submodule boundaries")), - OPT_STRING(0, "update", &opt.update_default, - N_("string"), - N_("rebase, merge, checkout or none")), + OPT_SET_INT(0, "checkout", &opt.update_default, + N_("use the 'checkout' update strategy (default)"), + SM_UPDATE_CHECKOUT), + OPT_SET_INT('m', "merge", &opt.update_default, + N_("use the 'merge' update strategy"), + SM_UPDATE_MERGE), + OPT_SET_INT('r', "rebase", &opt.update_default, + N_("use the 'rebase' update strategy"), + SM_UPDATE_REBASE), OPT_STRING_LIST(0, "reference", &opt.references, N_("repo"), N_("reference repository")), OPT_BOOL(0, "dissociate", &opt.dissociate, @@ -2619,7 +2603,7 @@ static int module_update(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "progress", &opt.progress, N_("force cloning progress")), OPT_BOOL(0, "require-init", &opt.require_init, - N_("disallow cloning into non-empty directory")), + N_("disallow cloning into non-empty directory, implies --init")), OPT_BOOL(0, "single-branch", &opt.single_branch, N_("clone only one branch, HEAD or --branch")), OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), @@ -2643,6 +2627,9 @@ static int module_update(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, module_update_options, git_submodule_helper_usage, 0); + if (opt.require_init) + opt.init = 1; + if (filter_options.choice && !opt.init) { usage_with_options(git_submodule_helper_usage, module_update_options); @@ -2651,9 +2638,7 @@ static int module_update(int argc, const char **argv, const char *prefix) opt.filter_options = &filter_options; if (opt.update_default) - if (parse_submodule_update_strategy(opt.update_default, - &opt.update_strategy) < 0) - die(_("bad value for update parameter")); + opt.update_strategy.type = opt.update_default; if (module_list_compute(argc, argv, prefix, &pathspec, &opt.list) < 0) { list_objects_filter_release(&filter_options); @@ -2679,7 +2664,6 @@ static int module_update(int argc, const char **argv, const char *prefix) module_list_active(&list); info.prefix = opt.prefix; - info.superprefix = opt.recursive_prefix; if (opt.quiet) info.flags |= OPT_QUIET; @@ -2785,7 +2769,7 @@ static int absorb_git_dirs(int argc, const char **argv, const char *prefix) }; const char *const git_submodule_helper_usage[] = { - N_("git submodule--helper absorb-git-dirs [<options>] [<path>...]"), + N_("git submodule absorbgitdirs [<options>] [<path>...]"), NULL }; @@ -2890,7 +2874,7 @@ static int module_set_url(int argc, const char **argv, const char *prefix) OPT_END() }; const char *const usage[] = { - N_("git submodule--helper set-url [--quiet] <path> <newurl>"), + N_("git submodule set-url [--quiet] <path> <newurl>"), NULL }; @@ -2929,8 +2913,8 @@ static int module_set_branch(int argc, const char **argv, const char *prefix) OPT_END() }; const char *const usage[] = { - N_("git submodule--helper set-branch [-q|--quiet] (-d|--default) <path>"), - N_("git submodule--helper set-branch [-q|--quiet] (-b|--branch) <branch> <path>"), + N_("git submodule set-branch [-q|--quiet] (-d|--default) <path>"), + N_("git submodule set-branch [-q|--quiet] (-b|--branch) <branch> <path>"), NULL }; @@ -3274,7 +3258,7 @@ static int module_add(int argc, const char **argv, const char *prefix) }; const char *const usage[] = { - N_("git submodule--helper add [<options>] [--] <repository> [<path>]"), + N_("git submodule add [<options>] [--] <repository> [<path>]"), NULL }; @@ -3376,18 +3360,18 @@ struct cmd_struct { static struct cmd_struct commands[] = { {"list", module_list, 0}, {"name", module_name, 0}, - {"clone", module_clone, 0}, - {"add", module_add, SUPPORT_SUPER_PREFIX}, - {"update", module_update, 0}, + {"clone", module_clone, SUPPORT_SUPER_PREFIX}, + {"add", module_add, 0}, + {"update", module_update, SUPPORT_SUPER_PREFIX}, {"resolve-relative-url-test", resolve_relative_url_test, 0}, {"foreach", module_foreach, SUPPORT_SUPER_PREFIX}, - {"init", module_init, SUPPORT_SUPER_PREFIX}, + {"init", module_init, 0}, {"status", module_status, SUPPORT_SUPER_PREFIX}, {"sync", module_sync, SUPPORT_SUPER_PREFIX}, {"deinit", module_deinit, 0}, - {"summary", module_summary, SUPPORT_SUPER_PREFIX}, + {"summary", module_summary, 0}, {"push-check", push_check, 0}, - {"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX}, + {"absorbgitdirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX}, {"is-active", is_active, 0}, {"check-name", check_name, 0}, {"config", module_config, 0}, diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index 56d05e2725..43789b8ef2 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -97,15 +97,27 @@ static void use(int bytes) display_throughput(progress, consumed_bytes); } +/* + * Decompress zstream from the standard input into a newly + * allocated buffer of specified size and return the buffer. + * The caller is responsible to free the returned buffer. + * + * But for dry_run mode, "get_data()" is only used to check the + * integrity of data, and the returned buffer is not used at all. + * Therefore, in dry_run mode, "get_data()" will release the small + * allocated buffer which is reused to hold temporary zstream output + * and return NULL instead of returning garbage data. + */ static void *get_data(unsigned long size) { git_zstream stream; - void *buf = xmallocz(size); + unsigned long bufsize = dry_run && size > 8192 ? 8192 : size; + void *buf = xmallocz(bufsize); memset(&stream, 0, sizeof(stream)); stream.next_out = buf; - stream.avail_out = size; + stream.avail_out = bufsize; stream.next_in = fill(1); stream.avail_in = len; git_inflate_init(&stream); @@ -125,8 +137,17 @@ static void *get_data(unsigned long size) } stream.next_in = fill(1); stream.avail_in = len; + if (dry_run) { + /* reuse the buffer in dry_run mode */ + stream.next_out = buf; + stream.avail_out = bufsize > size - stream.total_out ? + size - stream.total_out : + bufsize; + } } git_inflate_end(&stream); + if (dry_run) + FREE_AND_NULL(buf); return buf; } @@ -326,10 +347,70 @@ static void unpack_non_delta_entry(enum object_type type, unsigned long size, { void *buf = get_data(size); - if (!dry_run && buf) + if (buf) write_object(nr, type, buf, size); - else - free(buf); +} + +struct input_zstream_data { + git_zstream *zstream; + unsigned char buf[8192]; + int status; +}; + +static const void *feed_input_zstream(struct input_stream *in_stream, + unsigned long *readlen) +{ + struct input_zstream_data *data = in_stream->data; + git_zstream *zstream = data->zstream; + void *in = fill(1); + + if (in_stream->is_finished) { + *readlen = 0; + return NULL; + } + + zstream->next_out = data->buf; + zstream->avail_out = sizeof(data->buf); + zstream->next_in = in; + zstream->avail_in = len; + + data->status = git_inflate(zstream, 0); + + in_stream->is_finished = data->status != Z_OK; + use(len - zstream->avail_in); + *readlen = sizeof(data->buf) - zstream->avail_out; + + return data->buf; +} + +static void stream_blob(unsigned long size, unsigned nr) +{ + git_zstream zstream = { 0 }; + struct input_zstream_data data = { 0 }; + struct input_stream in_stream = { + .read = feed_input_zstream, + .data = &data, + }; + struct obj_info *info = &obj_list[nr]; + + data.zstream = &zstream; + git_inflate_init(&zstream); + + if (stream_loose_object(&in_stream, size, &info->oid)) + die(_("failed to write object in stream")); + + if (data.status != Z_STREAM_END) + die(_("inflate returned (%d)"), data.status); + git_inflate_end(&zstream); + + if (strict) { + struct blob *blob = lookup_blob(the_repository, &info->oid); + + if (!blob) + die(_("invalid blob object from stream")); + blob->object.flags |= FLAG_WRITTEN; + } + info->obj = NULL; } static int resolve_against_held(unsigned nr, const struct object_id *base, @@ -359,10 +440,8 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size, oidread(&base_oid, fill(the_hash_algo->rawsz)); use(the_hash_algo->rawsz); delta_data = get_data(delta_size); - if (dry_run || !delta_data) { - free(delta_data); + if (!delta_data) return; - } if (has_object_file(&base_oid)) ; /* Ok we have this one */ else if (resolve_against_held(nr, &base_oid, @@ -398,10 +477,8 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size, die("offset value out of bound for delta base object"); delta_data = get_data(delta_size); - if (dry_run || !delta_data) { - free(delta_data); + if (!delta_data) return; - } lo = 0; hi = nr; while (lo < hi) { @@ -468,9 +545,14 @@ static void unpack_one(unsigned nr) } switch (type) { + case OBJ_BLOB: + if (!dry_run && size > big_file_threshold) { + stream_blob(size, nr); + return; + } + /* fallthrough */ case OBJ_COMMIT: case OBJ_TREE: - case OBJ_BLOB: case OBJ_TAG: unpack_non_delta_entry(type, size, nr); return; diff --git a/commit-graph.c b/commit-graph.c index 92d4503336..cc3c966596 100644 --- a/commit-graph.c +++ b/commit-graph.c @@ -252,7 +252,8 @@ struct commit_graph *load_commit_graph_one_fd_st(struct repository *r, } graph_map = xmmap(NULL, graph_size, PROT_READ, MAP_PRIVATE, fd, 0); close(fd); - ret = parse_commit_graph(r, graph_map, graph_size); + prepare_repo_settings(r); + ret = parse_commit_graph(&r->settings, graph_map, graph_size); if (ret) ret->odb = odb; @@ -321,7 +322,7 @@ static int graph_read_bloom_data(const unsigned char *chunk_start, return 0; } -struct commit_graph *parse_commit_graph(struct repository *r, +struct commit_graph *parse_commit_graph(struct repo_settings *s, void *graph_map, size_t graph_size) { const unsigned char *data; @@ -359,8 +360,6 @@ struct commit_graph *parse_commit_graph(struct repository *r, return NULL; } - prepare_repo_settings(r); - graph = alloc_commit_graph(); graph->hash_len = the_hash_algo->rawsz; @@ -390,7 +389,7 @@ struct commit_graph *parse_commit_graph(struct repository *r, pair_chunk(cf, GRAPH_CHUNKID_EXTRAEDGES, &graph->chunk_extra_edges); pair_chunk(cf, GRAPH_CHUNKID_BASE, &graph->chunk_base_graphs); - if (get_configured_generation_version(r) >= 2) { + if (s->commit_graph_generation_version >= 2) { pair_chunk(cf, GRAPH_CHUNKID_GENERATION_DATA, &graph->chunk_generation_data); pair_chunk(cf, GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW, @@ -400,7 +399,7 @@ struct commit_graph *parse_commit_graph(struct repository *r, graph->read_generation_data = 1; } - if (r->settings.commit_graph_read_changed_paths) { + if (s->commit_graph_read_changed_paths) { pair_chunk(cf, GRAPH_CHUNKID_BLOOMINDEXES, &graph->chunk_bloom_indexes); read_chunk(cf, GRAPH_CHUNKID_BLOOMDATA, @@ -898,7 +897,7 @@ struct commit *lookup_commit_in_graph(struct repository *repo, const struct obje return NULL; if (!search_commit_pos_in_graph(id, repo->objects->commit_graph, &pos)) return NULL; - if (!repo_has_object_file(repo, id)) + if (!has_object(repo, id, 0)) return NULL; commit = lookup_commit(repo, id); diff --git a/commit-graph.h b/commit-graph.h index 2e3ac35237..25cdec127a 100644 --- a/commit-graph.h +++ b/commit-graph.h @@ -93,7 +93,12 @@ struct commit_graph *load_commit_graph_one_fd_st(struct repository *r, struct object_directory *odb); struct commit_graph *read_commit_graph_one(struct repository *r, struct object_directory *odb); -struct commit_graph *parse_commit_graph(struct repository *r, + +/* + * Callers should initialize the repo_settings with prepare_repo_settings() + * prior to calling parse_commit_graph(). + */ +struct commit_graph *parse_commit_graph(struct repo_settings *s, void *graph_map, size_t graph_size); /* diff --git a/compat/mingw.c b/compat/mingw.c index 2607de93af..b5502997e2 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -1059,10 +1059,7 @@ char *mingw_mktemp(char *template) int mkstemp(char *template) { - char *filename = mktemp(template); - if (!filename) - return -1; - return open(filename, O_RDWR | O_CREAT, 0600); + return git_mkstemp_mode(template, 0600); } int gettimeofday(struct timeval *tv, void *tz) diff --git a/compat/win32/syslog.c b/compat/win32/syslog.c index 1f8d8934cc..0af18d8882 100644 --- a/compat/win32/syslog.c +++ b/compat/win32/syslog.c @@ -44,6 +44,7 @@ void syslog(int priority, const char *fmt, ...) while ((pos = strstr(str, "%1")) != NULL) { size_t offset = pos - str; + char *new_pos; char *oldstr = str; str = realloc(str, st_add(++str_len, 1)); if (!str) { @@ -51,9 +52,9 @@ void syslog(int priority, const char *fmt, ...) warning_errno("realloc failed"); return; } - pos = str + offset; - memmove(pos + 2, pos + 1, strlen(pos)); - pos[1] = ' '; + new_pos = str + offset; + memmove(new_pos + 2, new_pos + 1, strlen(new_pos)); + new_pos[1] = ' '; } switch (priority) { @@ -81,6 +81,17 @@ static enum config_scope current_parsing_scope; static int pack_compression_seen; static int zlib_compression_seen; +/* + * Config that comes from trusted scopes, namely: + * - CONFIG_SCOPE_SYSTEM (e.g. /etc/gitconfig) + * - CONFIG_SCOPE_GLOBAL (e.g. $HOME/.gitconfig, $XDG_CONFIG_HOME/git) + * - CONFIG_SCOPE_COMMAND (e.g. "-c" option, environment variables) + * + * This is declared here for code cleanliness, but unlike the other + * static variables, this does not hold config parser state. + */ +static struct config_set protected_config; + static int config_file_fgetc(struct config_source *conf) { return getc_unlocked(conf->u.file); @@ -2378,6 +2389,11 @@ int git_configset_add_file(struct config_set *cs, const char *filename) return git_config_from_file(config_set_callback, filename, cs); } +int git_configset_add_parameters(struct config_set *cs) +{ + return git_config_from_parameters(config_set_callback, cs); +} + int git_configset_get_value(struct config_set *cs, const char *key, const char **value) { const struct string_list *values = NULL; @@ -2619,6 +2635,33 @@ int repo_config_get_pathname(struct repository *repo, return ret; } +/* Read values into protected_config. */ +static void read_protected_config(void) +{ + char *xdg_config = NULL, *user_config = NULL, *system_config = NULL; + + git_configset_init(&protected_config); + + system_config = git_system_config(); + git_global_config(&user_config, &xdg_config); + + git_configset_add_file(&protected_config, system_config); + git_configset_add_file(&protected_config, xdg_config); + git_configset_add_file(&protected_config, user_config); + git_configset_add_parameters(&protected_config); + + free(system_config); + free(xdg_config); + free(user_config); +} + +void git_protected_config(config_fn_t fn, void *data) +{ + if (!protected_config.hash_initialized) + read_protected_config(); + configset_iter(&protected_config, fn, data); +} + /* Functions used historically to read configuration from 'the_repository' */ void git_config(config_fn_t fn, void *data) { @@ -447,6 +447,15 @@ void git_configset_init(struct config_set *cs); int git_configset_add_file(struct config_set *cs, const char *filename); /** + * Parses command line options and environment variables, and adds the + * variable-value pairs to the `config_set`. Returns 0 on success, or -1 + * if there is an error in parsing. The caller decides whether to free + * the incomplete configset or continue using it when the function + * returns -1. + */ +int git_configset_add_parameters(struct config_set *cs); + +/** * Finds and returns the value list, sorted in order of increasing priority * for the configuration variable `key` and config set `cs`. When the * configuration variable `key` is not found, returns NULL. The caller @@ -505,6 +514,13 @@ int repo_config_get_maybe_bool(struct repository *repo, int repo_config_get_pathname(struct repository *repo, const char *key, const char **dest); +/* + * Functions for reading protected config. By definition, protected + * config ignores repository config, so these do not take a `struct + * repository` parameter. + */ +void git_protected_config(config_fn_t fn, void *data); + /** * Querying For Specific Variables * ------------------------------- diff --git a/contrib/coccinelle/array.cocci b/contrib/coccinelle/array.cocci index 9a4f00cb1b..aa75937950 100644 --- a/contrib/coccinelle/array.cocci +++ b/contrib/coccinelle/array.cocci @@ -1,60 +1,58 @@ @@ -expression dst, src, n, E; +type T; +T *dst_ptr; +T *src_ptr; +expression n; @@ - memcpy(dst, src, n * sizeof( -- E[...] -+ *(E) - )) +- memcpy(dst_ptr, src_ptr, (n) * \( sizeof(T) +- \| sizeof(*(dst_ptr)) +- \| sizeof(*(src_ptr)) +- \| sizeof(dst_ptr[...]) +- \| sizeof(src_ptr[...]) +- \) ) ++ COPY_ARRAY(dst_ptr, src_ptr, n) @@ type T; -T *ptr; -T[] arr; -expression E, n; +T *dst_ptr; +T[] src_arr; +expression n; @@ -( - memcpy(ptr, E, -- n * sizeof(*(ptr)) -+ n * sizeof(T) - ) -| - memcpy(arr, E, -- n * sizeof(*(arr)) -+ n * sizeof(T) - ) -| - memcpy(E, ptr, -- n * sizeof(*(ptr)) -+ n * sizeof(T) - ) -| - memcpy(E, arr, -- n * sizeof(*(arr)) -+ n * sizeof(T) - ) -) +- memcpy(dst_ptr, src_arr, (n) * \( sizeof(T) +- \| sizeof(*(dst_ptr)) +- \| sizeof(*(src_arr)) +- \| sizeof(dst_ptr[...]) +- \| sizeof(src_arr[...]) +- \) ) ++ COPY_ARRAY(dst_ptr, src_arr, n) @@ type T; -T *dst_ptr; +T[] dst_arr; T *src_ptr; +expression n; +@@ +- memcpy(dst_arr, src_ptr, (n) * \( sizeof(T) +- \| sizeof(*(dst_arr)) +- \| sizeof(*(src_ptr)) +- \| sizeof(dst_arr[...]) +- \| sizeof(src_ptr[...]) +- \) ) ++ COPY_ARRAY(dst_arr, src_ptr, n) + +@@ +type T; T[] dst_arr; T[] src_arr; expression n; @@ -( -- memcpy(dst_ptr, src_ptr, (n) * sizeof(T)) -+ COPY_ARRAY(dst_ptr, src_ptr, n) -| -- memcpy(dst_ptr, src_arr, (n) * sizeof(T)) -+ COPY_ARRAY(dst_ptr, src_arr, n) -| -- memcpy(dst_arr, src_ptr, (n) * sizeof(T)) -+ COPY_ARRAY(dst_arr, src_ptr, n) -| -- memcpy(dst_arr, src_arr, (n) * sizeof(T)) +- memcpy(dst_arr, src_arr, (n) * \( sizeof(T) +- \| sizeof(*(dst_arr)) +- \| sizeof(*(src_arr)) +- \| sizeof(dst_arr[...]) +- \| sizeof(src_arr[...]) +- \) ) + COPY_ARRAY(dst_arr, src_arr, n) -) @@ type T; diff --git a/contrib/coccinelle/tests/free.c b/contrib/coccinelle/tests/free.c new file mode 100644 index 0000000000..96d4abc0c7 --- /dev/null +++ b/contrib/coccinelle/tests/free.c @@ -0,0 +1,11 @@ +int use_FREE_AND_NULL(int *v) +{ + free(*v); + *v = NULL; +} + +int need_no_if(int *v) +{ + if (v) + free(v); +} diff --git a/contrib/coccinelle/tests/free.res b/contrib/coccinelle/tests/free.res new file mode 100644 index 0000000000..f90fd9f48e --- /dev/null +++ b/contrib/coccinelle/tests/free.res @@ -0,0 +1,9 @@ +int use_FREE_AND_NULL(int *v) +{ + FREE_AND_NULL(*v); +} + +int need_no_if(int *v) +{ + free(v); +} diff --git a/contrib/coccinelle/tests/unused.c b/contrib/coccinelle/tests/unused.c new file mode 100644 index 0000000000..8294d734ba --- /dev/null +++ b/contrib/coccinelle/tests/unused.c @@ -0,0 +1,82 @@ +void test_strbuf(void) +{ + struct strbuf sb1 = STRBUF_INIT; + struct strbuf sb2 = STRBUF_INIT; + struct strbuf sb3 = STRBUF_INIT; + struct strbuf sb4 = STRBUF_INIT; + struct strbuf sb5; + struct strbuf sb6 = { 0 }; + struct strbuf sb7 = STRBUF_INIT; + struct strbuf sb8 = STRBUF_INIT; + struct strbuf *sp1; + struct strbuf *sp2; + struct strbuf *sp3; + struct strbuf *sp4 = xmalloc(sizeof(struct strbuf)); + struct strbuf *sp5 = xmalloc(sizeof(struct strbuf)); + struct strbuf *sp6 = xmalloc(sizeof(struct strbuf)); + struct strbuf *sp7; + + strbuf_init(&sb5, 0); + strbuf_init(sp1, 0); + strbuf_init(sp2, 0); + strbuf_init(sp3, 0); + strbuf_init(sp4, 0); + strbuf_init(sp5, 0); + strbuf_init(sp6, 0); + strbuf_init(sp7, 0); + sp7 = xmalloc(sizeof(struct strbuf)); + + use_before(&sb3); + use_as_str("%s", sb7.buf); + use_as_str("%s", sp1->buf); + use_as_str("%s", sp6->buf); + pass_pp(&sp3); + + strbuf_release(&sb1); + strbuf_reset(&sb2); + strbuf_release(&sb3); + strbuf_release(&sb4); + strbuf_release(&sb5); + strbuf_release(&sb6); + strbuf_release(&sb7); + strbuf_release(sp1); + strbuf_release(sp2); + strbuf_release(sp3); + strbuf_release(sp4); + strbuf_release(sp5); + strbuf_release(sp6); + strbuf_release(sp7); + + use_after(&sb4); + + if (when_strict()) + return; + strbuf_release(&sb8); +} + +void test_other(void) +{ + struct string_list l = STRING_LIST_INIT_DUP; + struct strbuf sb = STRBUF_INIT; + + string_list_clear(&l, 0); + string_list_clear(&sb, 0); +} + +void test_worktrees(void) +{ + struct worktree **w1 = get_worktrees(); + struct worktree **w2 = get_worktrees(); + struct worktree **w3; + struct worktree **w4; + + w3 = get_worktrees(); + w4 = get_worktrees(); + + use_it(w4); + + free_worktrees(w1); + free_worktrees(w2); + free_worktrees(w3); + free_worktrees(w4); +} diff --git a/contrib/coccinelle/tests/unused.res b/contrib/coccinelle/tests/unused.res new file mode 100644 index 0000000000..6d3e745683 --- /dev/null +++ b/contrib/coccinelle/tests/unused.res @@ -0,0 +1,45 @@ +void test_strbuf(void) +{ + struct strbuf sb3 = STRBUF_INIT; + struct strbuf sb4 = STRBUF_INIT; + struct strbuf sb7 = STRBUF_INIT; + struct strbuf *sp1; + struct strbuf *sp3; + struct strbuf *sp6 = xmalloc(sizeof(struct strbuf)); + strbuf_init(sp1, 0); + strbuf_init(sp3, 0); + strbuf_init(sp6, 0); + + use_before(&sb3); + use_as_str("%s", sb7.buf); + use_as_str("%s", sp1->buf); + use_as_str("%s", sp6->buf); + pass_pp(&sp3); + + strbuf_release(&sb3); + strbuf_release(&sb4); + strbuf_release(&sb7); + strbuf_release(sp1); + strbuf_release(sp3); + strbuf_release(sp6); + + use_after(&sb4); + + if (when_strict()) + return; +} + +void test_other(void) +{ +} + +void test_worktrees(void) +{ + struct worktree **w4; + + w4 = get_worktrees(); + + use_it(w4); + + free_worktrees(w4); +} diff --git a/contrib/coccinelle/unused.cocci b/contrib/coccinelle/unused.cocci new file mode 100644 index 0000000000..d84046f82e --- /dev/null +++ b/contrib/coccinelle/unused.cocci @@ -0,0 +1,43 @@ +// This rule finds sequences of "unused" declerations and uses of a +// variable, where "unused" is defined to include only calling the +// equivalent of alloc, init & free functions on the variable. +@@ +type T; +identifier I; +// STRBUF_INIT, but also e.g. STRING_LIST_INIT_DUP (so no anchoring) +constant INIT_MACRO =~ "_INIT"; +identifier MALLOC1 =~ "^x?[mc]alloc$"; +identifier INIT_ASSIGN1 =~ "^get_worktrees$"; +identifier INIT_CALL1 =~ "^[a-z_]*_init$"; +identifier REL1 =~ "^[a-z_]*_(release|reset|clear|free)$"; +identifier REL2 =~ "^(release|clear|free)_[a-z_]*$"; +@@ + +( +- T I; +| +- T I = { 0 }; +| +- T I = INIT_MACRO; +| +- T I = MALLOC1(...); +| +- T I = INIT_ASSIGN1(...); +) + +<... when != \( I \| &I \) +( +- \( INIT_CALL1 \)( \( I \| &I \), ...); +| +- I = \( INIT_ASSIGN1 \)(...); +| +- I = MALLOC1(...); +) +...> + +( +- \( REL1 \| REL2 \)( \( I \| &I \), ...); +| +- \( REL1 \| REL2 \)( \( &I \| I \) ); +) + ... when != \( I \| &I \) diff --git a/contrib/credential/osxkeychain/git-credential-osxkeychain.c b/contrib/credential/osxkeychain/git-credential-osxkeychain.c index 0b44a9b7cc..bf77748d60 100644 --- a/contrib/credential/osxkeychain/git-credential-osxkeychain.c +++ b/contrib/credential/osxkeychain/git-credential-osxkeychain.c @@ -168,7 +168,7 @@ int main(int argc, const char **argv) "usage: git credential-osxkeychain <get|store|erase>"; if (!argv[1]) - die(usage); + die("%s", usage); read_credential(); diff --git a/contrib/scalar/README.md b/contrib/scalar/README.md deleted file mode 100644 index 634b5771ed..0000000000 --- a/contrib/scalar/README.md +++ /dev/null @@ -1,82 +0,0 @@ -# Scalar - an opinionated repository management tool - -Scalar is an add-on to Git that helps users take advantage of advanced -performance features in Git. Originally implemented in C# using .NET Core, -based on the learnings from the VFS for Git project, most of the techniques -developed by the Scalar project have been integrated into core Git already: - -* partial clone, -* commit graphs, -* multi-pack index, -* sparse checkout (cone mode), -* scheduled background maintenance, -* etc - -This directory contains the remaining parts of Scalar that are not (yet) in -core Git. - -## Roadmap - -The idea is to populate this directory via incremental patch series and -eventually move to a top-level directory next to `gitk-git/` and to `git-gui/`. The -current plan involves the following patch series: - -- `scalar-the-beginning`: The initial patch series which sets up - `contrib/scalar/` and populates it with a minimal `scalar` command that - demonstrates the fundamental ideas. - -- `scalar-c-and-C`: The `scalar` command learns about two options that can be - specified before the command, `-c <key>=<value>` and `-C <directory>`. - -- `scalar-diagnose`: The `scalar` command is taught the `diagnose` subcommand. - -- `scalar-and-builtin-fsmonitor`: The built-in FSMonitor is enabled in `scalar - register` and in `scalar clone`, for an enormous performance boost when - working in large worktrees. This patch series necessarily depends on Jeff - Hostetler's FSMonitor patch series to be integrated into Git. - -- `scalar-gentler-config-locking`: Scalar enlistments are registered in the - user's Git config. This usually does not represent any problem because it is - rare for a user to register an enlistment. However, in Scalar's functional - tests, Scalar enlistments are created galore, and in parallel, which can lead - to lock contention. This patch series works around that problem by re-trying - to lock the config file in a gentle fashion. - -- `scalar-extra-docs`: Add some extensive documentation that has been written - in the original Scalar project (all subject to discussion, of course). - -- `optionally-install-scalar`: Now that Scalar is feature (and documentation) - complete and is verified in CI builds, let's offer to install it. - -- `move-scalar-to-toplevel`: Now that Scalar is complete, let's move it next to - `gitk-git/` and to `git-gui/`, making it a top-level command. - -The following two patch series exist in Microsoft's fork of Git and are -publicly available. There is no current plan to upstream them, not because I -want to withhold these patches, but because I don't think the Git community is -interested in these patches. - -There are some interesting ideas there, but the implementation is too specific -to Azure Repos and/or VFS for Git to be of much help in general (and also: my -colleagues tried to upstream some patches already and the enthusiasm for -integrating things related to Azure Repos and VFS for Git can be summarized in -very, very few words). - -These still exist mainly because the GVFS protocol is what Azure Repos has -instead of partial clone, while Git is focused on improving partial clone: - -- `scalar-with-gvfs`: The primary purpose of this patch series is to support - existing Scalar users whose repositories are hosted in Azure Repos (which - does not support Git's partial clones, but supports its predecessor, the GVFS - protocol, which is used by Scalar to emulate the partial clone). - - Since the GVFS protocol will never be supported by core Git, this patch - series will remain in Microsoft's fork of Git. - -- `run-scalar-functional-tests`: The Scalar project developed a quite - comprehensive set of integration tests (or, "Functional Tests"). They are the - sole remaining part of the original C#-based Scalar project, and this patch - adds a GitHub workflow that runs them all. - - Since the tests partially depend on features that are only provided in the - `scalar-with-gvfs` patch series, this patch cannot be upstreamed. diff --git a/contrib/scalar/scalar.c b/contrib/scalar/scalar.c index 28176914e5..97e71fe19c 100644 --- a/contrib/scalar/scalar.c +++ b/contrib/scalar/scalar.c @@ -687,7 +687,7 @@ static int cmd_diagnose(int argc, const char **argv) int stdout_fd = -1, archiver_fd = -1; time_t now = time(NULL); struct tm tm; - struct strbuf path = STRBUF_INIT, buf = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT; int res = 0; argc = parse_options(argc, argv, NULL, options, @@ -779,7 +779,6 @@ diagnose_cleanup: free(argv_copy); strvec_clear(&archiver_args); strbuf_release(&zip_path); - strbuf_release(&path); strbuf_release(&buf); return res; diff --git a/contrib/scalar/scalar.txt b/contrib/scalar/scalar.txt index c0425e0653..1a12dc4507 100644 --- a/contrib/scalar/scalar.txt +++ b/contrib/scalar/scalar.txt @@ -3,7 +3,7 @@ scalar(1) NAME ---- -scalar - an opinionated repository management tool +scalar - A tool for managing large Git repositories SYNOPSIS -------- @@ -20,10 +20,9 @@ scalar delete <enlistment> DESCRIPTION ----------- -Scalar is an opinionated repository management tool. By creating new -repositories or registering existing repositories with Scalar, your Git -experience will speed up. Scalar sets advanced Git config settings, -maintains your repositories in the background, and helps reduce data sent +Scalar is a repository management tool that optimizes Git for use in large +repositories. Scalar improves performance by configuring advanced Git settings, +maintaining repositories in the background, and helping to reduce data sent across the network. An important Scalar concept is the enlistment: this is the top-level directory diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh index 1af1d9653e..7562a395c2 100755 --- a/contrib/subtree/git-subtree.sh +++ b/contrib/subtree/git-subtree.sh @@ -50,6 +50,14 @@ m,message= use the given message as the commit message for the merge commit indent=0 +# Usage: say [MSG...] +say () { + if test -z "$arg_quiet" + then + printf '%s\n' "$*" + fi +} + # Usage: debug [MSG...] debug () { if test -n "$arg_debug" @@ -60,7 +68,7 @@ debug () { # Usage: progress [MSG...] progress () { - if test -z "$GIT_QUIET" + if test -z "$arg_quiet" then if test -z "$arg_debug" then @@ -146,6 +154,7 @@ main () { eval "$set_args" # Begin "real" flag parsing. + arg_quiet= arg_debug= arg_prefix= arg_split_branch= @@ -161,7 +170,7 @@ main () { case "$opt" in -q) - GIT_QUIET=1 + arg_quiet=1 ;; -d) arg_debug=1 @@ -252,7 +261,7 @@ main () { dir="$(dirname "$arg_prefix/.")" debug "command: {$arg_command}" - debug "quiet: {$GIT_QUIET}" + debug "quiet: {$arg_quiet}" debug "dir: {$dir}" debug "opts: {$*}" debug @@ -279,7 +279,7 @@ static const char *path_ok(const char *directory, struct hostinfo *hi) /* The validation is done on the paths after enter_repo * appends optional {.git,.git/.git} and friends, but * it does not use getcwd(). So if your /pub is - * a symlink to /mnt/pub, you can whitelist /pub and + * a symlink to /mnt/pub, you can include /pub and * do not have to say /mnt/pub. * Do not say /pub/. */ @@ -298,7 +298,7 @@ static const char *path_ok(const char *directory, struct hostinfo *hi) return path; } - logerror("'%s': not in whitelist", path); + logerror("'%s': not in directory list", path); return NULL; /* Fallthrough. Deny by default */ } @@ -403,7 +403,7 @@ static int run_service(const char *dir, struct daemon_service *service, * a "git-daemon-export-ok" flag that says that the other side * is ok with us doing this. * - * path_ok() uses enter_repo() and does whitelist checking. + * path_ok() uses enter_repo() and checks for included directories. * We only need to make sure the repository is exported. */ @@ -1444,7 +1444,7 @@ int cmd_main(int argc, const char **argv) cred = prepare_credentials(user_name, group_name); if (strict_paths && (!ok_paths || !*ok_paths)) - die("option --strict-paths requires a whitelist"); + die("option --strict-paths requires '<directory>' arguments"); if (base_path && !is_directory(base_path)) die("base-path '%s' does not exist or is not a directory", @@ -1289,7 +1289,6 @@ static void emit_diff_symbol_from_struct(struct diff_options *o, { static const char *nneof = " No newline at end of file\n"; const char *context, *reset, *set, *set_sign, *meta, *fraginfo; - struct strbuf sb = STRBUF_INIT; enum diff_symbol s = eds->s; const char *line = eds->line; @@ -1521,7 +1520,6 @@ static void emit_diff_symbol_from_struct(struct diff_options *o, default: BUG("unknown diff symbol"); } - strbuf_release(&sb); } static void emit_diff_symbol(struct diff_options *o, enum diff_symbol s, @@ -3362,23 +3360,23 @@ struct userdiff_driver *get_textconv(struct repository *r, return userdiff_get_textconv(r, one->driver); } -static struct strbuf *additional_headers(struct diff_options *o, - const char *path) +static struct string_list *additional_headers(struct diff_options *o, + const char *path) { if (!o->additional_path_headers) return NULL; return strmap_get(o->additional_path_headers, path); } -static void add_formatted_headers(struct strbuf *msg, - struct strbuf *more_headers, +static void add_formatted_header(struct strbuf *msg, + const char *header, const char *line_prefix, const char *meta, const char *reset) { - char *next, *newline; + const char *next, *newline; - for (next = more_headers->buf; *next; next = newline) { + for (next = header; *next; next = newline) { newline = strchrnul(next, '\n'); strbuf_addf(msg, "%s%s%.*s%s\n", line_prefix, meta, (int)(newline - next), next, reset); @@ -3387,6 +3385,19 @@ static void add_formatted_headers(struct strbuf *msg, } } +static void add_formatted_headers(struct strbuf *msg, + struct string_list *more_headers, + const char *line_prefix, + const char *meta, + const char *reset) +{ + int i; + + for (i = 0; i < more_headers->nr; i++) + add_formatted_header(msg, more_headers->items[i].string, + line_prefix, meta, reset); +} + static void builtin_diff(const char *name_a, const char *name_b, struct diff_filespec *one, @@ -4314,7 +4325,7 @@ static void fill_metainfo(struct strbuf *msg, const char *set = diff_get_color(use_color, DIFF_METAINFO); const char *reset = diff_get_color(use_color, DIFF_RESET); const char *line_prefix = diff_line_prefix(o); - struct strbuf *more_headers = NULL; + struct string_list *more_headers = NULL; *must_show_header = 1; strbuf_init(msg, PATH_MAX * 2 + 300); diff --git a/fuzz-commit-graph.c b/fuzz-commit-graph.c index e7cf6d5b0f..914026f5d8 100644 --- a/fuzz-commit-graph.c +++ b/fuzz-commit-graph.c @@ -1,7 +1,7 @@ #include "commit-graph.h" #include "repository.h" -struct commit_graph *parse_commit_graph(struct repository *r, +struct commit_graph *parse_commit_graph(struct repo_settings *s, void *graph_map, size_t graph_size); int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); @@ -11,7 +11,15 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) struct commit_graph *g; initialize_the_repository(); - g = parse_commit_graph(the_repository, (void *)data, size); + /* + * Initialize the_repository with commit-graph settings that would + * normally be read from the repository's gitdir. We want to avoid + * touching the disk to keep the individual fuzz-test cases as fast as + * possible. + */ + the_repository->settings.commit_graph_generation_version = 2; + the_repository->settings.commit_graph_read_changed_paths = 1; + g = parse_commit_graph(&the_repository->settings, (void *)data, size); repo_clear(the_repository); free_commit_graph(g); diff --git a/git-cvsserver.perl b/git-cvsserver.perl index 4c8118010a..7b757360e2 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -152,7 +152,7 @@ $state->{allowed_roots} = [ @ARGV ]; # don't export the whole system unless the users requests it if ($state->{'export-all'} && !@{$state->{allowed_roots}}) { - die "--export-all can only be used together with an explicit whitelist\n"; + die "--export-all can only be used together with an explicit '<directory>...' list\n"; } # Environment handling for running under git-shell diff --git a/git-instaweb.sh b/git-instaweb.sh index 4349566c89..c68f49454c 100755 --- a/git-instaweb.sh +++ b/git-instaweb.sh @@ -102,7 +102,7 @@ resolve_full_httpd () { start_httpd () { if test -f "$fqgitdir/pid"; then - say "Instance already running. Restarting..." + echo "Instance already running. Restarting..." stop_httpd fi @@ -2226,7 +2226,7 @@ class P4Submit(Command, P4UserMap): raw=True): if regexp.search(line): if verbose: - print("got keyword match on %s in %s in %s" % (regex.pattern, line, file)) + print("got keyword match on %s in %s in %s" % (regexp.pattern, line, file)) kwfiles[file] = regexp break @@ -3148,7 +3148,7 @@ class P4Sync(Command, P4UserMap): raise e else: if p4_version_string().find('/NT') >= 0: - text = text.replace(b'\r\n', b'\n') + text = text.replace(b'\x0d\x00\x0a\x00', b'\x0a\x00') contents = [text] if type_base == "apple": @@ -4369,19 +4369,16 @@ class P4Unshelve(Command): def renameBranch(self, branch_name): """Rename the existing branch to branch_name.N .""" - found = True for i in range(0, 1000): backup_branch_name = "{0}.{1}".format(branch_name, i) if not gitBranchExists(backup_branch_name): # Copy ref to backup gitUpdateRef(backup_branch_name, branch_name) gitDeleteRef(branch_name) - found = True print("renamed old unshelve branch to {0}".format(backup_branch_name)) break - - if not found: - sys.exit("gave up trying to rename existing branch {0}".format(sync.branch)) + else: + sys.exit("gave up trying to rename existing branch {0}".format(branch_name)) def findLastP4Revision(self, starting_point): """Look back from starting_point for the first commit created by git-p4 diff --git a/git-sh-setup.sh b/git-sh-setup.sh index d92df37e99..ce273fe0e4 100644 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -57,15 +57,6 @@ die_with_status () { exit "$status" } -GIT_QUIET= - -say () { - if test -z "$GIT_QUIET" - then - printf '%s\n' "$*" - fi -} - if test -n "$OPTIONS_SPEC"; then usage() { "$0" -h @@ -285,13 +276,6 @@ get_author_ident_from_commit () { parse_ident_from_commit author AUTHOR } -# Clear repo-local GIT_* environment variables. Useful when switching to -# another repository (e.g. when entering a submodule). See also the env -# list in git_connect() -clear_local_git_env() { - unset $(git rev-parse --local-env-vars) -} - # Generate a virtual base file for a two-file merge. Uses git apply to # remove lines from $1 that are not in $2, leaving only common lines. create_virtual_base() { diff --git a/git-submodule.sh b/git-submodule.sh index fd0b4a2c94..5e5d21c010 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -30,6 +30,7 @@ GIT_PROTOCOL_FROM_USER=0 export GIT_PROTOCOL_FROM_USER command= +quiet= branch= force= reference= @@ -40,8 +41,9 @@ require_init= files= remote= nofetch= -update= -prefix= +rebase= +merge= +checkout= custom_name= depth= progress= @@ -56,17 +58,6 @@ isnumber() n=$(($1 + 0)) 2>/dev/null && test "$n" = "$1" } -# Sanitize the local git environment for use within a submodule. We -# can't simply use clear_local_git_env since we want to preserve some -# of the settings from GIT_CONFIG_PARAMETERS. -sanitize_submodule_env() -{ - save_config=$GIT_CONFIG_PARAMETERS - clear_local_git_env - GIT_CONFIG_PARAMETERS=$save_config - export GIT_CONFIG_PARAMETERS -} - # # Add a new submodule to the working tree, .gitmodules and the index # @@ -90,7 +81,7 @@ cmd_add() force=$1 ;; -q|--quiet) - GIT_QUIET=1 + quiet=1 ;; --progress) progress=1 @@ -138,7 +129,7 @@ cmd_add() usage fi - git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper add ${GIT_QUIET:+--quiet} ${force:+--force} ${progress:+"--progress"} ${branch:+--branch "$branch"} ${reference_path:+--reference "$reference_path"} ${dissociate:+--dissociate} ${custom_name:+--name "$custom_name"} ${depth:+"$depth"} -- "$@" + git ${wt_prefix:+-C "$wt_prefix"} submodule--helper add ${quiet:+--quiet} ${force:+--force} ${progress:+"--progress"} ${branch:+--branch "$branch"} ${reference_path:+--reference "$reference_path"} ${dissociate:+--dissociate} ${custom_name:+--name "$custom_name"} ${depth:+"$depth"} -- "$@" } # @@ -154,7 +145,7 @@ cmd_foreach() do case "$1" in -q|--quiet) - GIT_QUIET=1 + quiet=1 ;; --recursive) recursive=1 @@ -169,7 +160,7 @@ cmd_foreach() shift done - git ${wt_prefix:+-C "$wt_prefix"} submodule--helper foreach ${GIT_QUIET:+--quiet} ${recursive:+--recursive} -- "$@" + git ${wt_prefix:+-C "$wt_prefix"} submodule--helper foreach ${quiet:+--quiet} ${recursive:+--recursive} -- "$@" } # @@ -184,7 +175,7 @@ cmd_init() do case "$1" in -q|--quiet) - GIT_QUIET=1 + quiet=1 ;; --) shift @@ -200,7 +191,7 @@ cmd_init() shift done - git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper init ${GIT_QUIET:+--quiet} -- "$@" + git ${wt_prefix:+-C "$wt_prefix"} submodule--helper init ${quiet:+--quiet} -- "$@" } # @@ -217,7 +208,7 @@ cmd_deinit() force=$1 ;; -q|--quiet) - GIT_QUIET=1 + quiet=1 ;; --all) deinit_all=t @@ -236,7 +227,7 @@ cmd_deinit() shift done - git ${wt_prefix:+-C "$wt_prefix"} submodule--helper deinit ${GIT_QUIET:+--quiet} ${force:+--force} ${deinit_all:+--all} -- "$@" + git ${wt_prefix:+-C "$wt_prefix"} submodule--helper deinit ${quiet:+--quiet} ${force:+--force} ${deinit_all:+--all} -- "$@" } # @@ -251,10 +242,7 @@ cmd_update() do case "$1" in -q|--quiet) - GIT_QUIET=1 - ;; - -v) - unset GIT_QUIET + quiet=1 ;; --progress) progress=1 @@ -263,7 +251,6 @@ cmd_update() init=1 ;; --require-init) - init=1 require_init=1 ;; --remote) @@ -276,7 +263,7 @@ cmd_update() force=$1 ;; -r|--rebase) - update="rebase" + rebase=1 ;; --reference) case "$2" in '') usage ;; esac @@ -290,13 +277,13 @@ cmd_update() dissociate=1 ;; -m|--merge) - update="merge" + merge=1 ;; --recursive) recursive=1 ;; --checkout) - update="checkout" + checkout=1 ;; --recommend-shallow) recommend_shallow="--recommend-shallow" @@ -349,7 +336,7 @@ cmd_update() done git ${wt_prefix:+-C "$wt_prefix"} submodule--helper update \ - ${GIT_QUIET:+--quiet} \ + ${quiet:+--quiet} \ ${force:+--force} \ ${progress:+"--progress"} \ ${remote:+--remote} \ @@ -357,8 +344,9 @@ cmd_update() ${init:+--init} \ ${nofetch:+--no-fetch} \ ${wt_prefix:+--prefix "$wt_prefix"} \ - ${prefix:+--recursive-prefix "$prefix"} \ - ${update:+--update "$update"} \ + ${rebase:+--rebase} \ + ${merge:+--merge} \ + ${checkout:+--checkout} \ ${reference:+"$reference"} \ ${dissociate:+"--dissociate"} \ ${depth:+"$depth"} \ @@ -409,7 +397,7 @@ cmd_set_branch() { shift done - git ${wt_prefix:+-C "$wt_prefix"} submodule--helper set-branch ${GIT_QUIET:+--quiet} ${branch:+--branch "$branch"} ${default:+--default} -- "$@" + git ${wt_prefix:+-C "$wt_prefix"} submodule--helper set-branch ${quiet:+--quiet} ${branch:+--branch "$branch"} ${default:+--default} -- "$@" } # @@ -422,7 +410,7 @@ cmd_set_url() { do case "$1" in -q|--quiet) - GIT_QUIET=1 + quiet=1 ;; --) shift @@ -438,7 +426,7 @@ cmd_set_url() { shift done - git ${wt_prefix:+-C "$wt_prefix"} submodule--helper set-url ${GIT_QUIET:+--quiet} -- "$@" + git ${wt_prefix:+-C "$wt_prefix"} submodule--helper set-url ${quiet:+--quiet} -- "$@" } # @@ -459,7 +447,7 @@ cmd_summary() { do case "$1" in --cached) - cached="$1" + cached=1 ;; --files) files="$1" @@ -509,7 +497,7 @@ cmd_status() do case "$1" in -q|--quiet) - GIT_QUIET=1 + quiet=1 ;; --cached) cached=1 @@ -531,7 +519,7 @@ cmd_status() shift done - git ${wt_prefix:+-C "$wt_prefix"} submodule--helper status ${GIT_QUIET:+--quiet} ${cached:+--cached} ${recursive:+--recursive} -- "$@" + git ${wt_prefix:+-C "$wt_prefix"} submodule--helper status ${quiet:+--quiet} ${cached:+--cached} ${recursive:+--recursive} -- "$@" } # # Sync remote urls for submodules @@ -544,7 +532,7 @@ cmd_sync() do case "$1" in -q|--quiet) - GIT_QUIET=1 + quiet=1 shift ;; --recursive) @@ -564,12 +552,12 @@ cmd_sync() esac done - git ${wt_prefix:+-C "$wt_prefix"} submodule--helper sync ${GIT_QUIET:+--quiet} ${recursive:+--recursive} -- "$@" + git ${wt_prefix:+-C "$wt_prefix"} submodule--helper sync ${quiet:+--quiet} ${recursive:+--recursive} -- "$@" } cmd_absorbgitdirs() { - git submodule--helper absorb-git-dirs --prefix "$wt_prefix" "$@" + git submodule--helper absorbgitdirs --prefix "$wt_prefix" "$@" } # This loop parses the command line arguments to find the @@ -585,18 +573,10 @@ do command=$1 ;; -q|--quiet) - GIT_QUIET=1 - ;; - -b|--branch) - case "$2" in - '') - usage - ;; - esac - branch="$2"; shift + quiet=1 ;; --cached) - cached="$1" + cached=1 ;; --) break @@ -622,12 +602,6 @@ then fi fi -# "-b branch" is accepted only by "add" and "set-branch" -if test -n "$branch" && (test "$command" != add || test "$command" != set-branch) -then - usage -fi - # "--cached" is accepted only by "status" and "summary" if test -n "$cached" && test "$command" != status && test "$command" != summary then @@ -565,7 +565,7 @@ static struct cmd_struct commands[] = { { "merge-recursive-ours", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT }, { "merge-recursive-theirs", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT }, { "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT }, - { "merge-tree", cmd_merge_tree, RUN_SETUP | NO_PARSEOPT }, + { "merge-tree", cmd_merge_tree, RUN_SETUP }, { "mktag", cmd_mktag, RUN_SETUP | NO_PARSEOPT }, { "mktree", cmd_mktree, RUN_SETUP }, { "multi-pack-index", cmd_multi_pack_index, RUN_SETUP }, diff --git a/gitweb/Makefile b/gitweb/Makefile index f13e23c4de..3b68ab2d67 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -1,8 +1,8 @@ -# The default target of this Makefile is... -all:: +ifndef MAK_DIR_GITWEB +$(error do not run gitweb/Makefile stand-alone anymore. The "gitweb" and \ +"install-gitweb" targets now live in the top-level Makefile) +endif -# Define V=1 to have a more verbose compile. -# # Define JSMIN to point to JavaScript minifier that functions as # a filter to have static/gitweb.js minified. # @@ -10,13 +10,6 @@ all:: # version of static/gitweb.css # -prefix ?= $(HOME) -bindir ?= $(prefix)/bin -gitwebdir ?= /var/www/cgi-bin - -RM ?= rm -f -INSTALL ?= install - # default configuration for gitweb GITWEB_CONFIG = gitweb_config.perl GITWEB_CONFIG_SYSTEM = /etc/gitweb.conf @@ -30,89 +23,45 @@ GITWEB_STRICT_EXPORT = GITWEB_BASE_URL = GITWEB_LIST = GITWEB_HOMETEXT = indextext.html -GITWEB_CSS = static/gitweb.css +GITWEB_CSS_IN = static/gitweb.css +GITWEB_CSS = $(GITWEB_CSS_IN) GITWEB_LOGO = static/git-logo.png GITWEB_FAVICON = static/git-favicon.png -GITWEB_JS = static/gitweb.js +GITWEB_JS_IN = static/gitweb.js +GITWEB_JS = $(GITWEB_JS_IN) GITWEB_SITE_HTML_HEAD_STRING = GITWEB_SITE_HEADER = GITWEB_SITE_FOOTER = HIGHLIGHT_BIN = highlight -# include user config --include ../config.mak.autogen --include ../config.mak --include config.mak - -# determine version -../GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE - $(QUIET_SUBDIR0)../ $(QUIET_SUBDIR1) GIT-VERSION-FILE - -ifneq ($(MAKECMDGOALS),clean) --include ../GIT-VERSION-FILE -endif - -### Build rules - -SHELL_PATH ?= $(SHELL) -PERL_PATH ?= /usr/bin/perl +# What targets we'll add to 'all' for "make gitweb" +GITWEB_ALL = +GITWEB_ALL += gitweb.cgi +GITWEB_ALL += $(GITWEB_JS) -# Shell quote; -bindir_SQ = $(subst ','\'',$(bindir))#' -gitwebdir_SQ = $(subst ','\'',$(gitwebdir))#' -gitwebstaticdir_SQ = $(subst ','\'',$(gitwebdir)/static)#' -SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))#' -PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))#' -DESTDIR_SQ = $(subst ','\'',$(DESTDIR))#' - -# Quiet generation (unless V=1) -QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir -QUIET_SUBDIR1 = - -ifneq ($(findstring $(MAKEFLAGS),w),w) -PRINT_DIR = --no-print-directory -else # "make -w" -NO_SUBDIR = : -endif - -ifneq ($(findstring $(MAKEFLAGS),s),s) -ifndef V - QUIET = @ - QUIET_GEN = $(QUIET)echo ' ' GEN $@; - QUIET_SUBDIR0 = +@subdir= - QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ - $(MAKE) $(PRINT_DIR) -C $$subdir - export V - export QUIET - export QUIET_GEN - export QUIET_SUBDIR0 - export QUIET_SUBDIR1 -endif -endif - -all:: gitweb.cgi static/gitweb.js +MAK_DIR_GITWEB_ALL = $(addprefix $(MAK_DIR_GITWEB),$(GITWEB_ALL)) GITWEB_PROGRAMS = gitweb.cgi +GITWEB_JS_MIN = static/gitweb.min.js ifdef JSMIN -GITWEB_FILES += static/gitweb.min.js -GITWEB_JS = static/gitweb.min.js -all:: static/gitweb.min.js -static/gitweb.min.js: static/gitweb.js GITWEB-BUILD-OPTIONS +GITWEB_JS = $(GITWEB_JS_MIN) +GITWEB_ALL += $(MAK_DIR_GITWEB)$(GITWEB_JS_MIN) +$(MAK_DIR_GITWEB)$(GITWEB_JS_MIN): $(MAK_DIR_GITWEB)GITWEB-BUILD-OPTIONS +$(MAK_DIR_GITWEB)$(GITWEB_JS_MIN): $(MAK_DIR_GITWEB)$(GITWEB_JS_IN) $(QUIET_GEN)$(JSMIN) <$< >$@ -else -GITWEB_FILES += static/gitweb.js endif +GITWEB_FILES += $(GITWEB_JS) +GITWEB_CSS_MIN = static/gitweb.min.css ifdef CSSMIN -GITWEB_FILES += static/gitweb.min.css -GITWEB_CSS = static/gitweb.min.css -all:: static/gitweb.min.css -static/gitweb.min.css: static/gitweb.css GITWEB-BUILD-OPTIONS +GITWEB_CSS = $(GITWEB_CSS_MIN) +GITWEB_ALL += $(MAK_DIR_GITWEB)$(GITWEB_CSS_MIN) +$(MAK_DIR_GITWEB)$(GITWEB_CSS_MIN): $(MAK_DIR_GITWEB)GITWEB-BUILD-OPTIONS +$(MAK_DIR_GITWEB)$(GITWEB_CSS_MIN): $(MAK_DIR_GITWEB)$(GITWEB_CSS_IN) $(QUIET_GEN)$(CSSMIN) <$< >$@ -else -GITWEB_FILES += static/gitweb.css endif +GITWEB_FILES += $(GITWEB_CSS) GITWEB_FILES += static/git-logo.png static/git-favicon.png @@ -120,6 +69,7 @@ GITWEB_FILES += static/git-logo.png static/git-favicon.png # # js/lib/common-lib.js should be always first, then js/lib/*.js, # then the rest of files; js/gitweb.js should be last (if it exists) +GITWEB_JSLIB_FILES = GITWEB_JSLIB_FILES += static/js/lib/common-lib.js GITWEB_JSLIB_FILES += static/js/lib/datetime.js GITWEB_JSLIB_FILES += static/js/lib/cookies.js @@ -152,46 +102,45 @@ GITWEB_REPLACE = \ -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \ -e 's|++HIGHLIGHT_BIN++|$(HIGHLIGHT_BIN)|g' -GITWEB-BUILD-OPTIONS: FORCE +.PHONY: FORCE +$(MAK_DIR_GITWEB)GITWEB-BUILD-OPTIONS: FORCE @rm -f $@+ @echo "x" '$(PERL_PATH_SQ)' $(GITWEB_REPLACE) "$(JSMIN)|$(CSSMIN)" >$@+ @cmp -s $@+ $@ && rm -f $@+ || mv -f $@+ $@ -gitweb.cgi: gitweb.perl GITWEB-BUILD-OPTIONS +$(MAK_DIR_GITWEB)gitweb.cgi: $(MAK_DIR_GITWEB)GITWEB-BUILD-OPTIONS +$(MAK_DIR_GITWEB)gitweb.cgi: $(MAK_DIR_GITWEB)gitweb.perl $(QUIET_GEN)$(RM) $@ $@+ && \ sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \ $(GITWEB_REPLACE) $< >$@+ && \ chmod +x $@+ && \ mv $@+ $@ -static/gitweb.js: $(GITWEB_JSLIB_FILES) +$(MAK_DIR_GITWEB)static/gitweb.js: $(addprefix $(MAK_DIR_GITWEB),$(GITWEB_JSLIB_FILES)) $(QUIET_GEN)$(RM) $@ $@+ && \ cat $^ >$@+ && \ mv $@+ $@ -### Testing rules - -test: - $(MAKE) -C ../t gitweb-test - -test-installed: - GITWEB_TEST_INSTALLED='$(DESTDIR_SQ)$(gitwebdir_SQ)' \ - $(MAKE) -C ../t gitweb-test - ### Installation rules -install: all +.PHONY: install-gitweb +install-gitweb: $(MAK_DIR_GITWEB_ALL) $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitwebdir_SQ)' - $(INSTALL) -m 755 $(GITWEB_PROGRAMS) '$(DESTDIR_SQ)$(gitwebdir_SQ)' + $(INSTALL) -m 755 $(addprefix $(MAK_DIR_GITWEB),$(GITWEB_PROGRAMS)) '$(DESTDIR_SQ)$(gitwebdir_SQ)' $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitwebstaticdir_SQ)' - $(INSTALL) -m 644 $(GITWEB_FILES) '$(DESTDIR_SQ)$(gitwebstaticdir_SQ)' + $(INSTALL) -m 644 $(addprefix $(MAK_DIR_GITWEB),$(GITWEB_FILES)) \ + '$(DESTDIR_SQ)$(gitwebstaticdir_SQ)' +ifndef NO_GITWEB +ifndef NO_PERL +install: install-gitweb +endif +endif ### Cleaning rules -clean: - $(RM) gitweb.cgi static/gitweb.js \ - static/gitweb.min.js static/gitweb.min.css \ - GITWEB-BUILD-OPTIONS - -.PHONY: all clean install test test-installed .FORCE-GIT-VERSION-FILE FORCE - +.PHONY: gitweb-clean +gitweb-clean: + $(RM) $(addprefix $(MAK_DIR_GITWEB),gitweb.cgi $(GITWEB_JS_IN) \ + $(GITWEB_JS_MIN) $(GITWEB_CSS_MIN) \ + GITWEB-BUILD-OPTIONS) +clean: gitweb-clean diff --git a/gpg-interface.c b/gpg-interface.c index 947b58ad4d..6dff241460 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -165,15 +165,17 @@ static struct { { 0, "TRUST_", GPG_STATUS_TRUST_LEVEL }, }; -static struct { +/* Keep the order same as enum signature_trust_level */ +static struct sigcheck_gpg_trust_level { const char *key; + const char *display_key; enum signature_trust_level value; } sigcheck_gpg_trust_level[] = { - { "UNDEFINED", TRUST_UNDEFINED }, - { "NEVER", TRUST_NEVER }, - { "MARGINAL", TRUST_MARGINAL }, - { "FULLY", TRUST_FULLY }, - { "ULTIMATE", TRUST_ULTIMATE }, + { "UNDEFINED", "undefined", TRUST_UNDEFINED }, + { "NEVER", "never", TRUST_NEVER }, + { "MARGINAL", "marginal", TRUST_MARGINAL }, + { "FULLY", "fully", TRUST_FULLY }, + { "ULTIMATE", "ultimate", TRUST_ULTIMATE }, }; static void replace_cstring(char **field, const char *line, const char *next) @@ -905,6 +907,20 @@ const char *get_signing_key(void) return git_committer_info(IDENT_STRICT | IDENT_NO_DATE); } +const char *gpg_trust_level_to_str(enum signature_trust_level level) +{ + struct sigcheck_gpg_trust_level *trust; + + if (level < 0 || level >= ARRAY_SIZE(sigcheck_gpg_trust_level)) + BUG("invalid trust level requested %d", level); + + trust = &sigcheck_gpg_trust_level[level]; + if (trust->value != level) + BUG("sigcheck_gpg_trust_level[] unsorted"); + + return sigcheck_gpg_trust_level[level].display_key; +} + int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key) { return use_format->sign_buffer(buffer, signature, signing_key); diff --git a/gpg-interface.h b/gpg-interface.h index b30cbdcd3d..8a9ef41779 100644 --- a/gpg-interface.h +++ b/gpg-interface.h @@ -71,6 +71,14 @@ size_t parse_signed_buffer(const char *buf, size_t size); int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key); + +/* + * Returns corresponding string in lowercase for a given member of + * enum signature_trust_level. For example, `TRUST_ULTIMATE` will + * return "ultimate". + */ +const char *gpg_trust_level_to_str(enum signature_trust_level level); + int git_gpg_config(const char *, const char *, void *); void set_signing_key(const char *); const char *get_signing_key(void); @@ -1615,7 +1615,7 @@ static int grep_source_1(struct grep_opt *opt, struct grep_source *gs, int colle return 0; goto next_line; } - if (hit) { + if (hit && (opt->max_count < 0 || count < opt->max_count)) { count++; if (opt->status_only) return 1; @@ -171,6 +171,7 @@ struct grep_opt { int show_hunk_mark; int file_break; int heading; + int max_count; void *priv; void (*output)(struct grep_opt *opt, const void *data, size_t size); @@ -181,6 +182,7 @@ struct grep_opt { .relative = 1, \ .pathname = 1, \ .max_depth = -1, \ + .max_count = -1, \ .pattern_type_option = GREP_PATTERN_TYPE_UNSPECIFIED, \ .colors = { \ [GREP_COLOR_CONTEXT] = "", \ @@ -16,7 +16,9 @@ #include "block-sha1/sha1.h" #endif -#if defined(SHA256_GCRYPT) +#if defined(SHA256_NETTLE) +#include "sha256/nettle.h" +#elif defined(SHA256_GCRYPT) #define SHA256_NEEDS_CLONE_HELPER #include "sha256/gcrypt.h" #elif defined(SHA256_OPENSSL) @@ -1775,7 +1775,7 @@ static void write_accept_language(struct strbuf *buf) * LANGUAGE= LANG=en_US.UTF-8 -> "Accept-Language: en-US, *; q=0.1" * LANGUAGE= LANG=C -> "" */ -static const char *get_accept_language(void) +const char *http_get_accept_language_header(void) { if (!cached_accept_language) { struct strbuf buf = STRBUF_INIT; @@ -1829,7 +1829,7 @@ static int http_request(const char *url, fwrite_buffer); } - accept_language = get_accept_language(); + accept_language = http_get_accept_language_header(); if (accept_language) headers = curl_slist_append(headers, accept_language); @@ -178,6 +178,9 @@ int http_fetch_ref(const char *base, struct ref *ref); int http_get_info_packs(const char *base_url, struct packed_git **packs_head); +/* Helper for getting Accept-Language header */ +const char *http_get_accept_language_header(void); + struct http_pack_request { char *url; diff --git a/merge-ort.c b/merge-ort.c index 8c4927f0e1..8b7de0fbd8 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -349,13 +349,15 @@ struct merge_options_internal { struct mem_pool pool; /* - * output: special messages and conflict notices for various paths + * conflicts: logical conflicts and messages stored by _primary_ path * * This is a map of pathnames (a subset of the keys in "paths" above) - * to strbufs. It gathers various warning/conflict/notice messages - * for later processing. + * to struct string_list, with each item's `util` containing a + * `struct logical_conflict_info`. Note, though, that for each path, + * it only stores the logical conflicts for which that path is the + * primary path; the path might be part of additional conflicts. */ - struct strmap output; + struct strmap conflicts; /* * renames: various data relating to rename detection @@ -481,6 +483,100 @@ struct conflict_info { unsigned match_mask:3; }; +enum conflict_and_info_types { + /* "Simple" conflicts and informational messages */ + INFO_AUTO_MERGING = 0, + CONFLICT_CONTENTS, /* text file that failed to merge */ + CONFLICT_BINARY, + CONFLICT_FILE_DIRECTORY, + CONFLICT_DISTINCT_MODES, + CONFLICT_MODIFY_DELETE, + CONFLICT_PRESENT_DESPITE_SKIPPED, + + /* Regular rename */ + CONFLICT_RENAME_RENAME, /* same file renamed differently */ + CONFLICT_RENAME_COLLIDES, /* rename/add or two files renamed to 1 */ + CONFLICT_RENAME_DELETE, + + /* Basic directory rename */ + CONFLICT_DIR_RENAME_SUGGESTED, + INFO_DIR_RENAME_APPLIED, + + /* Special directory rename cases */ + INFO_DIR_RENAME_SKIPPED_DUE_TO_RERENAME, + CONFLICT_DIR_RENAME_FILE_IN_WAY, + CONFLICT_DIR_RENAME_COLLISION, + CONFLICT_DIR_RENAME_SPLIT, + + /* Basic submodule */ + INFO_SUBMODULE_FAST_FORWARDING, + CONFLICT_SUBMODULE_FAILED_TO_MERGE, + + /* Special submodule cases broken out from FAILED_TO_MERGE */ + CONFLICT_SUBMODULE_FAILED_TO_MERGE_BUT_POSSIBLE_RESOLUTION, + CONFLICT_SUBMODULE_NOT_INITIALIZED, + CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE, + CONFLICT_SUBMODULE_MAY_HAVE_REWINDS, + + /* Keep this entry _last_ in the list */ + NB_CONFLICT_TYPES, +}; + +/* + * Short description of conflict type, relied upon by external tools. + * + * We can add more entries, but DO NOT change any of these strings. Also, + * Order MUST match conflict_info_and_types. + */ +static const char *type_short_descriptions[] = { + /*** "Simple" conflicts and informational messages ***/ + [INFO_AUTO_MERGING] = "Auto-merging", + [CONFLICT_CONTENTS] = "CONFLICT (contents)", + [CONFLICT_BINARY] = "CONFLICT (binary)", + [CONFLICT_FILE_DIRECTORY] = "CONFLICT (file/directory)", + [CONFLICT_DISTINCT_MODES] = "CONFLICT (distinct modes)", + [CONFLICT_MODIFY_DELETE] = "CONFLICT (modify/delete)", + [CONFLICT_PRESENT_DESPITE_SKIPPED] = + "CONFLICT (upgrade your version of git)", + + /*** Regular rename ***/ + [CONFLICT_RENAME_RENAME] = "CONFLICT (rename/rename)", + [CONFLICT_RENAME_COLLIDES] = "CONFLICT (rename involved in collision)", + [CONFLICT_RENAME_DELETE] = "CONFLICT (rename/delete)", + + /*** Basic directory rename ***/ + [CONFLICT_DIR_RENAME_SUGGESTED] = + "CONFLICT (directory rename suggested)", + [INFO_DIR_RENAME_APPLIED] = "Path updated due to directory rename", + + /*** Special directory rename cases ***/ + [INFO_DIR_RENAME_SKIPPED_DUE_TO_RERENAME] = + "Directory rename skipped since directory was renamed on both sides", + [CONFLICT_DIR_RENAME_FILE_IN_WAY] = + "CONFLICT (file in way of directory rename)", + [CONFLICT_DIR_RENAME_COLLISION] = "CONFLICT(directory rename collision)", + [CONFLICT_DIR_RENAME_SPLIT] = "CONFLICT(directory rename unclear split)", + + /*** Basic submodule ***/ + [INFO_SUBMODULE_FAST_FORWARDING] = "Fast forwarding submodule", + [CONFLICT_SUBMODULE_FAILED_TO_MERGE] = "CONFLICT (submodule)", + + /*** Special submodule cases broken out from FAILED_TO_MERGE ***/ + [CONFLICT_SUBMODULE_FAILED_TO_MERGE_BUT_POSSIBLE_RESOLUTION] = + "CONFLICT (submodule with possible resolution)", + [CONFLICT_SUBMODULE_NOT_INITIALIZED] = + "CONFLICT (submodule not initialized)", + [CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE] = + "CONFLICT (submodule history not available)", + [CONFLICT_SUBMODULE_MAY_HAVE_REWINDS] = + "CONFLICT (submodule may have rewinds)", +}; + +struct logical_conflict_info { + enum conflict_and_info_types type; + struct strvec paths; +}; + /*** Function Grouping: various utility functions ***/ /* @@ -567,20 +663,25 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti, struct strmap_entry *e; /* Release and free each strbuf found in output */ - strmap_for_each_entry(&opti->output, &iter, e) { - struct strbuf *sb = e->value; - strbuf_release(sb); + strmap_for_each_entry(&opti->conflicts, &iter, e) { + struct string_list *list = e->value; + for (int i = 0; i < list->nr; i++) { + struct logical_conflict_info *info = + list->items[i].util; + strvec_clear(&info->paths); + } /* - * While strictly speaking we don't need to free(sb) - * here because we could pass free_values=1 when - * calling strmap_clear() on opti->output, that would - * require strmap_clear to do another - * strmap_for_each_entry() loop, so we just free it - * while we're iterating anyway. + * While strictly speaking we don't need to + * free(conflicts) here because we could pass + * free_values=1 when calling strmap_clear() on + * opti->conflicts, that would require strmap_clear + * to do another strmap_for_each_entry() loop, so we + * just free it while we're iterating anyway. */ - free(sb); + string_list_clear(list, 1); + free(list); } - strmap_clear(&opti->output, 0); + strmap_clear(&opti->conflicts, 0); } mem_pool_discard(&opti->pool, 0); @@ -627,29 +728,58 @@ static void format_commit(struct strbuf *sb, strbuf_addch(sb, '\n'); } -__attribute__((format (printf, 4, 5))) +__attribute__((format (printf, 8, 9))) static void path_msg(struct merge_options *opt, - const char *path, + enum conflict_and_info_types type, int omittable_hint, /* skippable under --remerge-diff */ + const char *primary_path, + const char *other_path_1, /* may be NULL */ + const char *other_path_2, /* may be NULL */ + struct string_list *other_paths, /* may be NULL */ const char *fmt, ...) { va_list ap; - struct strbuf *sb, *dest; + struct string_list *path_conflicts; + struct logical_conflict_info *info; + struct strbuf buf = STRBUF_INIT; + struct strbuf *dest; struct strbuf tmp = STRBUF_INIT; + /* Sanity checks */ + assert(omittable_hint == + !starts_with(type_short_descriptions[type], "CONFLICT") || + type == CONFLICT_DIR_RENAME_SUGGESTED || + type == CONFLICT_PRESENT_DESPITE_SKIPPED); if (opt->record_conflict_msgs_as_headers && omittable_hint) return; /* Do not record mere hints in headers */ if (opt->priv->call_depth && opt->verbosity < 5) return; /* Ignore messages from inner merges */ - sb = strmap_get(&opt->priv->output, path); - if (!sb) { - sb = xmalloc(sizeof(*sb)); - strbuf_init(sb, 0); - strmap_put(&opt->priv->output, path, sb); + /* Ensure path_conflicts (ptr to array of logical_conflict) allocated */ + path_conflicts = strmap_get(&opt->priv->conflicts, primary_path); + if (!path_conflicts) { + path_conflicts = xmalloc(sizeof(*path_conflicts)); + string_list_init_dup(path_conflicts); + strmap_put(&opt->priv->conflicts, primary_path, path_conflicts); } - dest = (opt->record_conflict_msgs_as_headers ? &tmp : sb); + /* Add a logical_conflict at the end to store info from this call */ + info = xcalloc(1, sizeof(*info)); + info->type = type; + strvec_init(&info->paths); + + /* Handle the list of paths */ + strvec_push(&info->paths, primary_path); + if (other_path_1) + strvec_push(&info->paths, other_path_1); + if (other_path_2) + strvec_push(&info->paths, other_path_2); + if (other_paths) + for (int i = 0; i < other_paths->nr; i++) + strvec_push(&info->paths, other_paths->items[i].string); + + /* Handle message and its format, in normal case */ + dest = (opt->record_conflict_msgs_as_headers ? &tmp : &buf); va_start(ap, fmt); if (opt->priv->call_depth) { @@ -660,32 +790,32 @@ static void path_msg(struct merge_options *opt, strbuf_vaddf(dest, fmt, ap); va_end(ap); + /* Handle specialized formatting of message under --remerge-diff */ if (opt->record_conflict_msgs_as_headers) { int i_sb = 0, i_tmp = 0; /* Start with the specified prefix */ if (opt->msg_header_prefix) - strbuf_addf(sb, "%s ", opt->msg_header_prefix); + strbuf_addf(&buf, "%s ", opt->msg_header_prefix); /* Copy tmp to sb, adding spaces after newlines */ - strbuf_grow(sb, sb->len + 2*tmp.len); /* more than sufficient */ + strbuf_grow(&buf, buf.len + 2*tmp.len); /* more than sufficient */ for (; i_tmp < tmp.len; i_tmp++, i_sb++) { /* Copy next character from tmp to sb */ - sb->buf[sb->len + i_sb] = tmp.buf[i_tmp]; + buf.buf[buf.len + i_sb] = tmp.buf[i_tmp]; /* If we copied a newline, add a space */ if (tmp.buf[i_tmp] == '\n') - sb->buf[++i_sb] = ' '; + buf.buf[++i_sb] = ' '; } /* Update length and ensure it's NUL-terminated */ - sb->len += i_sb; - sb->buf[sb->len] = '\0'; + buf.len += i_sb; + buf.buf[buf.len] = '\0'; strbuf_release(&tmp); } - - /* Add final newline character to sb */ - strbuf_addch(sb, '\n'); + string_list_append_nodup(path_conflicts, strbuf_detach(&buf, NULL)) + ->util = info; } static struct diff_filespec *pool_alloc_filespec(struct mem_pool *pool, @@ -1627,16 +1757,18 @@ static int merge_submodule(struct merge_options *opt, return 0; if (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) { - path_msg(opt, path, 0, - _("Failed to merge submodule %s (not checked out)"), - path); + path_msg(opt, CONFLICT_SUBMODULE_NOT_INITIALIZED, 0, + path, NULL, NULL, NULL, + _("Failed to merge submodule %s (not checked out)"), + path); return 0; } if (!(commit_o = lookup_commit_reference(&subrepo, o)) || !(commit_a = lookup_commit_reference(&subrepo, a)) || !(commit_b = lookup_commit_reference(&subrepo, b))) { - path_msg(opt, path, 0, + path_msg(opt, CONFLICT_SUBMODULE_HISTORY_NOT_AVAILABLE, 0, + path, NULL, NULL, NULL, _("Failed to merge submodule %s (commits not present)"), path); goto cleanup; @@ -1645,7 +1777,8 @@ static int merge_submodule(struct merge_options *opt, /* check whether both changes are forward */ if (!repo_in_merge_bases(&subrepo, commit_o, commit_a) || !repo_in_merge_bases(&subrepo, commit_o, commit_b)) { - path_msg(opt, path, 0, + path_msg(opt, CONFLICT_SUBMODULE_MAY_HAVE_REWINDS, 0, + path, NULL, NULL, NULL, _("Failed to merge submodule %s " "(commits don't follow merge-base)"), path); @@ -1655,7 +1788,8 @@ static int merge_submodule(struct merge_options *opt, /* Case #1: a is contained in b or vice versa */ if (repo_in_merge_bases(&subrepo, commit_a, commit_b)) { oidcpy(result, b); - path_msg(opt, path, 1, + path_msg(opt, INFO_SUBMODULE_FAST_FORWARDING, 1, + path, NULL, NULL, NULL, _("Note: Fast-forwarding submodule %s to %s"), path, oid_to_hex(b)); ret = 1; @@ -1663,7 +1797,8 @@ static int merge_submodule(struct merge_options *opt, } if (repo_in_merge_bases(&subrepo, commit_b, commit_a)) { oidcpy(result, a); - path_msg(opt, path, 1, + path_msg(opt, INFO_SUBMODULE_FAST_FORWARDING, 1, + path, NULL, NULL, NULL, _("Note: Fast-forwarding submodule %s to %s"), path, oid_to_hex(a)); ret = 1; @@ -1686,30 +1821,27 @@ static int merge_submodule(struct merge_options *opt, &merges); switch (parent_count) { case 0: - path_msg(opt, path, 0, _("Failed to merge submodule %s"), path); + path_msg(opt, CONFLICT_SUBMODULE_FAILED_TO_MERGE, 0, + path, NULL, NULL, NULL, + _("Failed to merge submodule %s"), path); break; case 1: format_commit(&sb, 4, &subrepo, (struct commit *)merges.objects[0].item); - path_msg(opt, path, 0, + path_msg(opt, CONFLICT_SUBMODULE_FAILED_TO_MERGE_BUT_POSSIBLE_RESOLUTION, 0, + path, NULL, NULL, NULL, _("Failed to merge submodule %s, but a possible merge " - "resolution exists:\n%s\n"), + "resolution exists: %s"), path, sb.buf); - path_msg(opt, path, 1, - _("If this is correct simply add it to the index " - "for example\n" - "by using:\n\n" - " git update-index --cacheinfo 160000 %s \"%s\"\n\n" - "which will accept this suggestion.\n"), - oid_to_hex(&merges.objects[0].item->oid), path); strbuf_release(&sb); break; default: for (i = 0; i < merges.nr; i++) format_commit(&sb, 4, &subrepo, (struct commit *)merges.objects[i].item); - path_msg(opt, path, 0, + path_msg(opt, CONFLICT_SUBMODULE_FAILED_TO_MERGE_BUT_POSSIBLE_RESOLUTION, 0, + path, NULL, NULL, NULL, _("Failed to merge submodule %s, but multiple " "possible merges exist:\n%s"), path, sb.buf); strbuf_release(&sb); @@ -1835,7 +1967,8 @@ static int merge_3way(struct merge_options *opt, &src1, name1, &src2, name2, &opt->priv->attr_index, &ll_opts); if (merge_status == LL_MERGE_BINARY_CONFLICT) - path_msg(opt, path, 0, + path_msg(opt, CONFLICT_BINARY, 0, + path, NULL, NULL, NULL, "warning: Cannot merge binary files: %s (%s vs. %s)", path, name1, name2); @@ -1947,7 +2080,8 @@ static int handle_content_merge(struct merge_options *opt, if (ret) return -1; clean &= (merge_status == 0); - path_msg(opt, path, 1, _("Auto-merging %s"), path); + path_msg(opt, INFO_AUTO_MERGING, 1, path, NULL, NULL, NULL, + _("Auto-merging %s"), path); } else if (S_ISGITLINK(a->mode)) { int two_way = ((S_IFMT & o->mode) != (S_IFMT & a->mode)); clean = merge_submodule(opt, pathnames[0], @@ -2085,21 +2219,24 @@ static char *handle_path_level_conflicts(struct merge_options *opt, c_info->reported_already = 1; strbuf_add_separated_string_list(&collision_paths, ", ", &c_info->source_files); - path_msg(opt, new_path, 0, - _("CONFLICT (implicit dir rename): Existing file/dir " - "at %s in the way of implicit directory rename(s) " - "putting the following path(s) there: %s."), - new_path, collision_paths.buf); + path_msg(opt, CONFLICT_DIR_RENAME_FILE_IN_WAY, 0, + new_path, NULL, NULL, &c_info->source_files, + _("CONFLICT (implicit dir rename): Existing " + "file/dir at %s in the way of implicit " + "directory rename(s) putting the following " + "path(s) there: %s."), + new_path, collision_paths.buf); clean = 0; } else if (c_info->source_files.nr > 1) { c_info->reported_already = 1; strbuf_add_separated_string_list(&collision_paths, ", ", &c_info->source_files); - path_msg(opt, new_path, 0, - _("CONFLICT (implicit dir rename): Cannot map more " - "than one path to %s; implicit directory renames " - "tried to put these paths there: %s"), - new_path, collision_paths.buf); + path_msg(opt, CONFLICT_DIR_RENAME_COLLISION, 0, + new_path, NULL, NULL, &c_info->source_files, + _("CONFLICT (implicit dir rename): Cannot map " + "more than one path to %s; implicit directory " + "renames tried to put these paths there: %s"), + new_path, collision_paths.buf); clean = 0; } @@ -2153,13 +2290,14 @@ static void get_provisional_directory_renames(struct merge_options *opt, continue; if (bad_max == max) { - path_msg(opt, source_dir, 0, - _("CONFLICT (directory rename split): " - "Unclear where to rename %s to; it was " - "renamed to multiple other directories, with " - "no destination getting a majority of the " - "files."), - source_dir); + path_msg(opt, CONFLICT_DIR_RENAME_SPLIT, 0, + source_dir, NULL, NULL, NULL, + _("CONFLICT (directory rename split): " + "Unclear where to rename %s to; it was " + "renamed to multiple other directories, " + "with no destination getting a majority of " + "the files."), + source_dir); *clean = 0; } else { strmap_put(&renames->dir_renames[side], @@ -2334,7 +2472,8 @@ static char *check_for_directory_rename(struct merge_options *opt, new_dir = rename_info->value; /* old_dir = rename_info->key; */ otherinfo = strmap_get_entry(dir_rename_exclusions, new_dir); if (otherinfo) { - path_msg(opt, rename_info->key, 1, + path_msg(opt, INFO_DIR_RENAME_SKIPPED_DUE_TO_RERENAME, 1, + rename_info->key, path, new_dir, NULL, _("WARNING: Avoiding applying %s -> %s rename " "to %s, because %s itself was renamed."), rename_info->key, new_dir, path, new_dir); @@ -2475,14 +2614,16 @@ static void apply_directory_rename_modifications(struct merge_options *opt, if (opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_TRUE) { /* Notify user of updated path */ if (pair->status == 'A') - path_msg(opt, new_path, 1, + path_msg(opt, INFO_DIR_RENAME_APPLIED, 1, + new_path, old_path, NULL, NULL, _("Path updated: %s added in %s inside a " "directory that was renamed in %s; moving " "it to %s."), old_path, branch_with_new_path, branch_with_dir_rename, new_path); else - path_msg(opt, new_path, 1, + path_msg(opt, INFO_DIR_RENAME_APPLIED, 1, + new_path, old_path, NULL, NULL, _("Path updated: %s renamed to %s in %s, " "inside a directory that was renamed in %s; " "moving it to %s."), @@ -2495,7 +2636,8 @@ static void apply_directory_rename_modifications(struct merge_options *opt, */ ci->path_conflict = 1; if (pair->status == 'A') - path_msg(opt, new_path, 1, + path_msg(opt, CONFLICT_DIR_RENAME_SUGGESTED, 1, + new_path, old_path, NULL, NULL, _("CONFLICT (file location): %s added in %s " "inside a directory that was renamed in %s, " "suggesting it should perhaps be moved to " @@ -2503,7 +2645,8 @@ static void apply_directory_rename_modifications(struct merge_options *opt, old_path, branch_with_new_path, branch_with_dir_rename, new_path); else - path_msg(opt, new_path, 1, + path_msg(opt, CONFLICT_DIR_RENAME_SUGGESTED, 1, + new_path, old_path, NULL, NULL, _("CONFLICT (file location): %s renamed to %s " "in %s, inside a directory that was renamed " "in %s, suggesting it should perhaps be " @@ -2659,7 +2802,8 @@ static int process_renames(struct merge_options *opt, * and remove the setting of base->path_conflict to 1. */ base->path_conflict = 1; - path_msg(opt, oldpath, 0, + path_msg(opt, CONFLICT_RENAME_RENAME, 0, + pathnames[0], pathnames[1], pathnames[2], NULL, _("CONFLICT (rename/rename): %s renamed to " "%s in %s and to %s in %s."), pathnames[0], @@ -2754,7 +2898,8 @@ static int process_renames(struct merge_options *opt, memcpy(&newinfo->stages[target_index], &merged, sizeof(merged)); if (!clean) { - path_msg(opt, newpath, 0, + path_msg(opt, CONFLICT_RENAME_COLLIDES, 0, + newpath, oldpath, NULL, NULL, _("CONFLICT (rename involved in " "collision): rename of %s -> %s has " "content conflicts AND collides " @@ -2773,7 +2918,8 @@ static int process_renames(struct merge_options *opt, */ newinfo->path_conflict = 1; - path_msg(opt, newpath, 0, + path_msg(opt, CONFLICT_RENAME_DELETE, 0, + newpath, oldpath, NULL, NULL, _("CONFLICT (rename/delete): %s renamed " "to %s in %s, but deleted in %s."), oldpath, newpath, rename_branch, delete_branch); @@ -2797,7 +2943,8 @@ static int process_renames(struct merge_options *opt, } else if (source_deleted) { /* rename/delete */ newinfo->path_conflict = 1; - path_msg(opt, newpath, 0, + path_msg(opt, CONFLICT_RENAME_DELETE, 0, + newpath, oldpath, NULL, NULL, _("CONFLICT (rename/delete): %s renamed" " to %s in %s, but deleted in %s."), oldpath, newpath, @@ -3712,7 +3859,8 @@ static void process_entry(struct merge_options *opt, path = unique_path(opt, path, branch); strmap_put(&opt->priv->paths, path, new_ci); - path_msg(opt, path, 0, + path_msg(opt, CONFLICT_FILE_DIRECTORY, 0, + path, old_path, NULL, NULL, _("CONFLICT (file/directory): directory in the way " "of %s from %s; moving it to %s instead."), old_path, branch, path); @@ -3788,15 +3936,23 @@ static void process_entry(struct merge_options *opt, rename_b = 1; } + if (rename_a) + a_path = unique_path(opt, path, opt->branch1); + if (rename_b) + b_path = unique_path(opt, path, opt->branch2); + if (rename_a && rename_b) { - path_msg(opt, path, 0, + path_msg(opt, CONFLICT_DISTINCT_MODES, 0, + path, a_path, b_path, NULL, _("CONFLICT (distinct types): %s had " "different types on each side; " "renamed both of them so each can " "be recorded somewhere."), path); } else { - path_msg(opt, path, 0, + path_msg(opt, CONFLICT_DISTINCT_MODES, 0, + path, rename_a ? a_path : b_path, + NULL, NULL, _("CONFLICT (distinct types): %s had " "different types on each side; " "renamed one of them so each can be " @@ -3833,14 +3989,10 @@ static void process_entry(struct merge_options *opt, /* Insert entries into opt->priv_paths */ assert(rename_a || rename_b); - if (rename_a) { - a_path = unique_path(opt, path, opt->branch1); + if (rename_a) strmap_put(&opt->priv->paths, a_path, ci); - } - if (rename_b) - b_path = unique_path(opt, path, opt->branch2); - else + if (!rename_b) b_path = path; strmap_put(&opt->priv->paths, b_path, new_ci); @@ -3891,7 +4043,8 @@ static void process_entry(struct merge_options *opt, reason = _("add/add"); if (S_ISGITLINK(merged_file.mode)) reason = _("submodule"); - path_msg(opt, path, 0, + path_msg(opt, CONFLICT_CONTENTS, 0, + path, NULL, NULL, NULL, _("CONFLICT (%s): Merge conflict in %s"), reason, path); } @@ -3935,7 +4088,8 @@ static void process_entry(struct merge_options *opt, * since the contents were not modified. */ } else { - path_msg(opt, path, 0, + path_msg(opt, CONFLICT_MODIFY_DELETE, 0, + path, NULL, NULL, NULL, _("CONFLICT (modify/delete): %s deleted in %s " "and modified in %s. Version %s of %s left " "in tree."), @@ -4231,7 +4385,8 @@ static int record_conflicted_index_entries(struct merge_options *opt) path, "cruft"); - path_msg(opt, path, 1, + path_msg(opt, CONFLICT_PRESENT_DESPITE_SKIPPED, 1, + path, NULL, NULL, NULL, _("Note: %s not up to date and in way of checking out conflicted version; old copy renamed to %s"), path, new_name); errs |= rename(path, new_name); @@ -4279,6 +4434,93 @@ static int record_conflicted_index_entries(struct merge_options *opt) return errs; } +void merge_display_update_messages(struct merge_options *opt, + int detailed, + struct merge_result *result) +{ + struct merge_options_internal *opti = result->priv; + struct hashmap_iter iter; + struct strmap_entry *e; + struct string_list olist = STRING_LIST_INIT_NODUP; + + if (opt->record_conflict_msgs_as_headers) + BUG("Either display conflict messages or record them as headers, not both"); + + trace2_region_enter("merge", "display messages", opt->repo); + + /* Hack to pre-allocate olist to the desired size */ + ALLOC_GROW(olist.items, strmap_get_size(&opti->conflicts), + olist.alloc); + + /* Put every entry from output into olist, then sort */ + strmap_for_each_entry(&opti->conflicts, &iter, e) { + string_list_append(&olist, e->key)->util = e->value; + } + string_list_sort(&olist); + + /* Iterate over the items, printing them */ + for (int path_nr = 0; path_nr < olist.nr; ++path_nr) { + struct string_list *conflicts = olist.items[path_nr].util; + for (int i = 0; i < conflicts->nr; i++) { + struct logical_conflict_info *info = + conflicts->items[i].util; + + if (detailed) { + printf("%lu", (unsigned long)info->paths.nr); + putchar('\0'); + for (int n = 0; n < info->paths.nr; n++) { + fputs(info->paths.v[n], stdout); + putchar('\0'); + } + fputs(type_short_descriptions[info->type], + stdout); + putchar('\0'); + } + puts(conflicts->items[i].string); + if (detailed) + putchar('\0'); + } + } + string_list_clear(&olist, 0); + + /* Also include needed rename limit adjustment now */ + diff_warn_rename_limit("merge.renamelimit", + opti->renames.needed_limit, 0); + + trace2_region_leave("merge", "display messages", opt->repo); +} + +void merge_get_conflicted_files(struct merge_result *result, + struct string_list *conflicted_files) +{ + struct hashmap_iter iter; + struct strmap_entry *e; + struct merge_options_internal *opti = result->priv; + + strmap_for_each_entry(&opti->conflicted, &iter, e) { + const char *path = e->key; + struct conflict_info *ci = e->value; + int i; + + VERIFY_CI(ci); + + for (i = MERGE_BASE; i <= MERGE_SIDE2; i++) { + struct stage_info *si; + + if (!(ci->filemask & (1ul << i))) + continue; + + si = xmalloc(sizeof(*si)); + si->stage = i+1; + si->mode = ci->stages[i].mode; + oidcpy(&si->oid, &ci->stages[i].oid); + string_list_append(conflicted_files, path)->util = si; + } + } + /* string_list_sort() uses a stable sort, so we're good */ + string_list_sort(conflicted_files); +} + void merge_switch_to_result(struct merge_options *opt, struct tree *head, struct merge_result *result, @@ -4316,43 +4558,8 @@ void merge_switch_to_result(struct merge_options *opt, fclose(fp); trace2_region_leave("merge", "write_auto_merge", opt->repo); } - - if (display_update_msgs) { - struct merge_options_internal *opti = result->priv; - struct hashmap_iter iter; - struct strmap_entry *e; - struct string_list olist = STRING_LIST_INIT_NODUP; - int i; - - if (opt->record_conflict_msgs_as_headers) - BUG("Either display conflict messages or record them as headers, not both"); - - trace2_region_enter("merge", "display messages", opt->repo); - - /* Hack to pre-allocate olist to the desired size */ - ALLOC_GROW(olist.items, strmap_get_size(&opti->output), - olist.alloc); - - /* Put every entry from output into olist, then sort */ - strmap_for_each_entry(&opti->output, &iter, e) { - string_list_append(&olist, e->key)->util = e->value; - } - string_list_sort(&olist); - - /* Iterate over the items, printing them */ - for (i = 0; i < olist.nr; ++i) { - struct strbuf *sb = olist.items[i].util; - - printf("%s", sb->buf); - } - string_list_clear(&olist, 0); - - /* Also include needed rename limit adjustment now */ - diff_warn_rename_limit("merge.renamelimit", - opti->renames.needed_limit, 0); - - trace2_region_leave("merge", "display messages", opt->repo); - } + if (display_update_msgs) + merge_display_update_messages(opt, /* detailed */ 0, result); merge_finalize(opt, result); } @@ -4526,11 +4733,11 @@ static void merge_start(struct merge_options *opt, struct merge_result *result) strmap_init_with_options(&opt->priv->conflicted, pool, 0); /* - * keys & strbufs in output will sometimes need to outlive "paths", - * so it will have a copy of relevant keys. It's probably a small - * subset of the overall paths that have special output. + * keys & string_lists in conflicts will sometimes need to outlive + * "paths", so it will have a copy of relevant keys. It's probably + * a small subset of the overall paths that have special output. */ - strmap_init(&opt->priv->output); + strmap_init(&opt->priv->conflicts); trace2_region_leave("merge", "allocate/init", opt->repo); } @@ -4631,7 +4838,8 @@ redo: trace2_region_leave("merge", "process_entries", opt->repo); /* Set return values */ - result->path_messages = &opt->priv->output; + result->path_messages = &opt->priv->conflicts; + result->tree = parse_tree_indirect(&working_tree_oid); /* existence of conflicted entries implies unclean */ result->clean &= strmap_empty(&opt->priv->conflicted); diff --git a/merge-ort.h b/merge-ort.h index fe599b8786..a994c9a5fc 100644 --- a/merge-ort.h +++ b/merge-ort.h @@ -2,6 +2,7 @@ #define MERGE_ORT_H #include "merge-recursive.h" +#include "hash.h" struct commit; struct tree; @@ -27,7 +28,7 @@ struct merge_result { /* * Special messages and conflict notices for various paths * - * This is a map of pathnames to strbufs. It contains various + * This is a map of pathnames to a string_list. It contains various * warning/conflict/notice messages (possibly multiple per path) * that callers may want to use. */ @@ -80,6 +81,35 @@ void merge_switch_to_result(struct merge_options *opt, int update_worktree_and_index, int display_update_msgs); +/* + * Display messages about conflicts and which files were 3-way merged. + * Automatically called by merge_switch_to_result() with stream == stdout, + * so only call this when bypassing merge_switch_to_result(). + */ +void merge_display_update_messages(struct merge_options *opt, + int detailed, + struct merge_result *result); + +struct stage_info { + struct object_id oid; + int mode; + int stage; +}; + +/* + * Provide a list of path -> {struct stage_info*} mappings for + * all conflicted files. Note that each path could appear up to three + * times in the list, corresponding to 3 different stage entries. In short, + * this basically provides the info that would be printed by `ls-files -u`. + * + * result should have been populated by a call to + * one of the merge_incore_[non]recursive() functions. + * + * conflicted_files should be empty before calling this function. + */ +void merge_get_conflicted_files(struct merge_result *result, + struct string_list *conflicted_files); + /* Do needed cleanup when not calling merge_switch_to_result() */ void merge_finalize(struct merge_options *opt, struct merge_result *result); diff --git a/object-file.c b/object-file.c index 6c8e3b1660..5b270f046d 100644 --- a/object-file.c +++ b/object-file.c @@ -1951,6 +1951,96 @@ static int create_tmpfile(struct strbuf *tmp, const char *filename) return fd; } +/** + * Common steps for loose object writers to start writing loose + * objects: + * + * - Create tmpfile for the loose object. + * - Setup zlib stream for compression. + * - Start to feed header to zlib stream. + * + * Returns a "fd", which should later be provided to + * end_loose_object_common(). + */ +static int start_loose_object_common(struct strbuf *tmp_file, + const char *filename, unsigned flags, + git_zstream *stream, + unsigned char *buf, size_t buflen, + git_hash_ctx *c, + char *hdr, int hdrlen) +{ + int fd; + + fd = create_tmpfile(tmp_file, filename); + if (fd < 0) { + if (flags & HASH_SILENT) + return -1; + else if (errno == EACCES) + return error(_("insufficient permission for adding " + "an object to repository database %s"), + get_object_directory()); + else + return error_errno( + _("unable to create temporary file")); + } + + /* Setup zlib stream for compression */ + git_deflate_init(stream, zlib_compression_level); + stream->next_out = buf; + stream->avail_out = buflen; + the_hash_algo->init_fn(c); + + /* Start to feed header to zlib stream */ + stream->next_in = (unsigned char *)hdr; + stream->avail_in = hdrlen; + while (git_deflate(stream, 0) == Z_OK) + ; /* nothing */ + the_hash_algo->update_fn(c, hdr, hdrlen); + + return fd; +} + +/** + * Common steps for the inner git_deflate() loop for writing loose + * objects. Returns what git_deflate() returns. + */ +static int write_loose_object_common(git_hash_ctx *c, + git_zstream *stream, const int flush, + unsigned char *in0, const int fd, + unsigned char *compressed, + const size_t compressed_len) +{ + int ret; + + ret = git_deflate(stream, flush ? Z_FINISH : 0); + the_hash_algo->update_fn(c, in0, stream->next_in - in0); + if (write_buffer(fd, compressed, stream->next_out - compressed) < 0) + die(_("unable to write loose object file")); + stream->next_out = compressed; + stream->avail_out = compressed_len; + + return ret; +} + +/** + * Common steps for loose object writers to end writing loose objects: + * + * - End the compression of zlib stream. + * - Get the calculated oid to "oid". + */ +static int end_loose_object_common(git_hash_ctx *c, git_zstream *stream, + struct object_id *oid) +{ + int ret; + + ret = git_deflate_end_gently(stream); + if (ret != Z_OK) + return ret; + the_hash_algo->final_oid_fn(oid, c); + + return Z_OK; +} + static int write_loose_object(const struct object_id *oid, char *hdr, int hdrlen, const void *buf, unsigned long len, time_t mtime, unsigned flags) @@ -1968,50 +2058,29 @@ static int write_loose_object(const struct object_id *oid, char *hdr, loose_object_path(the_repository, &filename, oid); - fd = create_tmpfile(&tmp_file, filename.buf); - if (fd < 0) { - if (flags & HASH_SILENT) - return -1; - else if (errno == EACCES) - return error(_("insufficient permission for adding an object to repository database %s"), get_object_directory()); - else - return error_errno(_("unable to create temporary file")); - } - - /* Set it up */ - git_deflate_init(&stream, zlib_compression_level); - stream.next_out = compressed; - stream.avail_out = sizeof(compressed); - the_hash_algo->init_fn(&c); - - /* First header.. */ - stream.next_in = (unsigned char *)hdr; - stream.avail_in = hdrlen; - while (git_deflate(&stream, 0) == Z_OK) - ; /* nothing */ - the_hash_algo->update_fn(&c, hdr, hdrlen); + fd = start_loose_object_common(&tmp_file, filename.buf, flags, + &stream, compressed, sizeof(compressed), + &c, hdr, hdrlen); + if (fd < 0) + return -1; /* Then the data itself.. */ stream.next_in = (void *)buf; stream.avail_in = len; do { unsigned char *in0 = stream.next_in; - ret = git_deflate(&stream, Z_FINISH); - the_hash_algo->update_fn(&c, in0, stream.next_in - in0); - if (write_buffer(fd, compressed, stream.next_out - compressed) < 0) - die(_("unable to write loose object file")); - stream.next_out = compressed; - stream.avail_out = sizeof(compressed); + + ret = write_loose_object_common(&c, &stream, 1, in0, fd, + compressed, sizeof(compressed)); } while (ret == Z_OK); if (ret != Z_STREAM_END) die(_("unable to deflate new object %s (%d)"), oid_to_hex(oid), ret); - ret = git_deflate_end_gently(&stream); + ret = end_loose_object_common(&c, &stream, ¶no_oid); if (ret != Z_OK) die(_("deflateEnd on object %s failed (%d)"), oid_to_hex(oid), ret); - the_hash_algo->final_oid_fn(¶no_oid, &c); if (!oideq(oid, ¶no_oid)) die(_("confused by unstable object source data for %s"), oid_to_hex(oid)); @@ -2050,6 +2119,110 @@ static int freshen_packed_object(const struct object_id *oid) return 1; } +int stream_loose_object(struct input_stream *in_stream, size_t len, + struct object_id *oid) +{ + int fd, ret, err = 0, flush = 0; + unsigned char compressed[4096]; + git_zstream stream; + git_hash_ctx c; + struct strbuf tmp_file = STRBUF_INIT; + struct strbuf filename = STRBUF_INIT; + int dirlen; + char hdr[MAX_HEADER_LEN]; + int hdrlen; + + if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT)) + prepare_loose_object_bulk_checkin(); + + /* Since oid is not determined, save tmp file to odb path. */ + strbuf_addf(&filename, "%s/", get_object_directory()); + hdrlen = format_object_header(hdr, sizeof(hdr), OBJ_BLOB, len); + + /* + * Common steps for write_loose_object and stream_loose_object to + * start writing loose objects: + * + * - Create tmpfile for the loose object. + * - Setup zlib stream for compression. + * - Start to feed header to zlib stream. + */ + fd = start_loose_object_common(&tmp_file, filename.buf, 0, + &stream, compressed, sizeof(compressed), + &c, hdr, hdrlen); + if (fd < 0) { + err = -1; + goto cleanup; + } + + /* Then the data itself.. */ + do { + unsigned char *in0 = stream.next_in; + + if (!stream.avail_in && !in_stream->is_finished) { + const void *in = in_stream->read(in_stream, &stream.avail_in); + stream.next_in = (void *)in; + in0 = (unsigned char *)in; + /* All data has been read. */ + if (in_stream->is_finished) + flush = 1; + } + ret = write_loose_object_common(&c, &stream, flush, in0, fd, + compressed, sizeof(compressed)); + /* + * Unlike write_loose_object(), we do not have the entire + * buffer. If we get Z_BUF_ERROR due to too few input bytes, + * then we'll replenish them in the next input_stream->read() + * call when we loop. + */ + } while (ret == Z_OK || ret == Z_BUF_ERROR); + + if (stream.total_in != len + hdrlen) + die(_("write stream object %ld != %"PRIuMAX), stream.total_in, + (uintmax_t)len + hdrlen); + + /* + * Common steps for write_loose_object and stream_loose_object to + * end writing loose oject: + * + * - End the compression of zlib stream. + * - Get the calculated oid. + */ + if (ret != Z_STREAM_END) + die(_("unable to stream deflate new object (%d)"), ret); + ret = end_loose_object_common(&c, &stream, oid); + if (ret != Z_OK) + die(_("deflateEnd on stream object failed (%d)"), ret); + close_loose_object(fd, tmp_file.buf); + + if (freshen_packed_object(oid) || freshen_loose_object(oid)) { + unlink_or_warn(tmp_file.buf); + goto cleanup; + } + + loose_object_path(the_repository, &filename, oid); + + /* We finally know the object path, and create the missing dir. */ + dirlen = directory_size(filename.buf); + if (dirlen) { + struct strbuf dir = STRBUF_INIT; + strbuf_add(&dir, filename.buf, dirlen); + + if (mkdir_in_gitdir(dir.buf) && errno != EEXIST) { + err = error_errno(_("unable to create directory %s"), dir.buf); + strbuf_release(&dir); + goto cleanup; + } + strbuf_release(&dir); + } + + err = finalize_object_file(tmp_file.buf, filename.buf); +cleanup: + strbuf_release(&tmp_file); + strbuf_release(&filename); + return err; +} + int write_object_file_flags(const void *buf, unsigned long len, enum object_type type, struct object_id *oid, unsigned flags) diff --git a/object-store.h b/object-store.h index 539ea43904..5222ee5460 100644 --- a/object-store.h +++ b/object-store.h @@ -46,6 +46,12 @@ struct object_directory { char *path; }; +struct input_stream { + const void *(*read)(struct input_stream *, unsigned long *len); + void *data; + int is_finished; +}; + KHASH_INIT(odb_path_map, const char * /* key: odb_path */, struct object_directory *, 1, fspathhash, fspatheq) @@ -269,6 +275,8 @@ static inline int write_object_file(const void *buf, unsigned long len, int write_object_file_literally(const void *buf, unsigned long len, const char *type, struct object_id *oid, unsigned flags); +int stream_loose_object(struct input_stream *in_stream, size_t len, + struct object_id *oid); /* * Add an object file to the in-memory object store, without writing it diff --git a/pack-bitmap.c b/pack-bitmap.c index 36134222d7..ef580be9e3 100644 --- a/pack-bitmap.c +++ b/pack-bitmap.c @@ -1,5 +1,6 @@ #include "cache.h" #include "commit.h" +#include "strbuf.h" #include "tag.h" #include "diff.h" #include "revision.h" @@ -138,7 +139,7 @@ static struct ewah_bitmap *read_bitmap_1(struct bitmap_index *index) index->map_size - index->map_pos); if (bitmap_size < 0) { - error("Failed to load bitmap index (corrupted?)"); + error(_("failed to load bitmap index (corrupted?)")); ewah_pool_free(b); return NULL; } @@ -160,14 +161,14 @@ static int load_bitmap_header(struct bitmap_index *index) size_t header_size = sizeof(*header) - GIT_MAX_RAWSZ + the_hash_algo->rawsz; if (index->map_size < header_size + the_hash_algo->rawsz) - return error("Corrupted bitmap index (too small)"); + return error(_("corrupted bitmap index (too small)")); if (memcmp(header->magic, BITMAP_IDX_SIGNATURE, sizeof(BITMAP_IDX_SIGNATURE)) != 0) - return error("Corrupted bitmap index file (wrong header)"); + return error(_("corrupted bitmap index file (wrong header)")); index->version = ntohs(header->version); if (index->version != 1) - return error("Unsupported version for bitmap index file (%d)", index->version); + return error(_("unsupported version '%d' for bitmap index file"), index->version); /* Parse known bitmap format options */ { @@ -176,12 +177,12 @@ static int load_bitmap_header(struct bitmap_index *index) unsigned char *index_end = index->map + index->map_size - the_hash_algo->rawsz; if ((flags & BITMAP_OPT_FULL_DAG) == 0) - return error("Unsupported options for bitmap index file " + BUG("unsupported options for bitmap index file " "(Git requires BITMAP_OPT_FULL_DAG)"); if (flags & BITMAP_OPT_HASH_CACHE) { if (cache_size > index_end - index->map - header_size) - return error("corrupted bitmap index file (too short to fit hash cache)"); + return error(_("corrupted bitmap index file (too short to fit hash cache)")); index->hashes = (void *)(index_end - cache_size); index_end -= cache_size; } @@ -215,7 +216,7 @@ static struct stored_bitmap *store_bitmap(struct bitmap_index *index, * because the SHA1 already existed on the map. this is bad, there * shouldn't be duplicated commits in the index */ if (ret == 0) { - error("Duplicate entry in bitmap index: %s", oid_to_hex(oid)); + error(_("duplicate entry in bitmap index: '%s'"), oid_to_hex(oid)); return NULL; } @@ -259,14 +260,14 @@ static int load_bitmap_entries_v1(struct bitmap_index *index) struct object_id oid; if (index->map_size - index->map_pos < 6) - return error("corrupt ewah bitmap: truncated header for entry %d", i); + return error(_("corrupt ewah bitmap: truncated header for entry %d"), i); commit_idx_pos = read_be32(index->map, &index->map_pos); xor_offset = read_u8(index->map, &index->map_pos); flags = read_u8(index->map, &index->map_pos); if (nth_bitmap_object_oid(index, &oid, commit_idx_pos) < 0) - return error("corrupt ewah bitmap: commit index %u out of range", + return error(_("corrupt ewah bitmap: commit index %u out of range"), (unsigned)commit_idx_pos); bitmap = read_bitmap_1(index); @@ -274,13 +275,13 @@ static int load_bitmap_entries_v1(struct bitmap_index *index) return -1; if (xor_offset > MAX_XOR_OFFSET || xor_offset > i) - return error("Corrupted bitmap pack index"); + return error(_("corrupted bitmap pack index")); if (xor_offset > 0) { xor_bitmap = recent_bitmaps[(i - xor_offset) % MAX_XOR_OFFSET]; if (!xor_bitmap) - return error("Invalid XOR offset in bitmap pack index"); + return error(_("invalid XOR offset in bitmap pack index")); } recent_bitmaps[i % MAX_XOR_OFFSET] = store_bitmap( @@ -313,17 +314,21 @@ static int open_midx_bitmap_1(struct bitmap_index *bitmap_git, struct multi_pack_index *midx) { struct stat st; - char *idx_name = midx_bitmap_filename(midx); - int fd = git_open(idx_name); + char *bitmap_name = midx_bitmap_filename(midx); + int fd = git_open(bitmap_name); uint32_t i; struct packed_git *preferred; - free(idx_name); - - if (fd < 0) + if (fd < 0) { + if (errno != ENOENT) + warning_errno("cannot open '%s'", bitmap_name); + free(bitmap_name); return -1; + } + free(bitmap_name); if (fstat(fd, &st)) { + error_errno(_("cannot fstat bitmap file")); close(fd); return -1; } @@ -332,7 +337,7 @@ static int open_midx_bitmap_1(struct bitmap_index *bitmap_git, struct strbuf buf = STRBUF_INIT; get_midx_filename(&buf, midx->object_dir); /* ignore extra bitmap file; we can only handle one */ - warning("ignoring extra bitmap file: %s", buf.buf); + warning(_("ignoring extra bitmap file: '%s'"), buf.buf); close(fd); strbuf_release(&buf); return -1; @@ -348,8 +353,10 @@ static int open_midx_bitmap_1(struct bitmap_index *bitmap_git, if (load_bitmap_header(bitmap_git) < 0) goto cleanup; - if (!hasheq(get_midx_checksum(bitmap_git->midx), bitmap_git->checksum)) + if (!hasheq(get_midx_checksum(bitmap_git->midx), bitmap_git->checksum)) { + error(_("checksum doesn't match in MIDX and bitmap")); goto cleanup; + } if (load_midx_revindex(bitmap_git->midx) < 0) { warning(_("multi-pack bitmap is missing required reverse index")); @@ -384,26 +391,31 @@ static int open_pack_bitmap_1(struct bitmap_index *bitmap_git, struct packed_git { int fd; struct stat st; - char *idx_name; + char *bitmap_name; if (open_pack_index(packfile)) return -1; - idx_name = pack_bitmap_filename(packfile); - fd = git_open(idx_name); - free(idx_name); + bitmap_name = pack_bitmap_filename(packfile); + fd = git_open(bitmap_name); - if (fd < 0) + if (fd < 0) { + if (errno != ENOENT) + warning_errno("cannot open '%s'", bitmap_name); + free(bitmap_name); return -1; + } + free(bitmap_name); if (fstat(fd, &st)) { + error_errno(_("cannot fstat bitmap file")); close(fd); return -1; } if (bitmap_git->pack || bitmap_git->midx) { /* ignore extra bitmap file; we can only handle one */ - warning("ignoring extra bitmap file: %s", packfile->pack_name); + warning(_("ignoring extra bitmap file: '%s'"), packfile->pack_name); close(fd); return -1; } @@ -508,15 +520,16 @@ static int open_pack_bitmap(struct repository *r, static int open_midx_bitmap(struct repository *r, struct bitmap_index *bitmap_git) { + int ret = -1; struct multi_pack_index *midx; assert(!bitmap_git->map); for (midx = get_multi_pack_index(r); midx; midx = midx->next) { if (!open_midx_bitmap_1(bitmap_git, midx)) - return 0; + ret = 0; } - return -1; + return ret; } static int open_bitmap(struct repository *r, @@ -831,7 +844,7 @@ static struct bitmap *find_objects(struct bitmap_index *bitmap_git, revs->include_check_data = &incdata; if (prepare_revision_walk(revs)) - die("revision walk setup failed"); + die(_("revision walk setup failed")); show_data.bitmap_git = bitmap_git; show_data.base = base; @@ -1640,15 +1653,15 @@ static void test_bitmap_type(struct bitmap_test_data *tdata, } if (bitmap_type == OBJ_NONE) - die("object %s not found in type bitmaps", + die(_("object '%s' not found in type bitmaps"), oid_to_hex(&obj->oid)); if (bitmaps_nr > 1) - die("object %s does not have a unique type", + die(_("object '%s' does not have a unique type"), oid_to_hex(&obj->oid)); if (bitmap_type != obj->type) - die("object %s: real type %s, expected: %s", + die(_("object '%s': real type '%s', expected: '%s'"), oid_to_hex(&obj->oid), type_name(obj->type), type_name(bitmap_type)); @@ -1662,7 +1675,7 @@ static void test_show_object(struct object *object, const char *name, bitmap_pos = bitmap_position(tdata->bitmap_git, &object->oid); if (bitmap_pos < 0) - die("Object not in bitmap: %s\n", oid_to_hex(&object->oid)); + die(_("object not in bitmap: '%s'"), oid_to_hex(&object->oid)); test_bitmap_type(tdata, object, bitmap_pos); bitmap_set(tdata->base, bitmap_pos); @@ -1677,7 +1690,7 @@ static void test_show_commit(struct commit *commit, void *data) bitmap_pos = bitmap_position(tdata->bitmap_git, &commit->object.oid); if (bitmap_pos < 0) - die("Object not in bitmap: %s\n", oid_to_hex(&commit->object.oid)); + die(_("object not in bitmap: '%s'"), oid_to_hex(&commit->object.oid)); test_bitmap_type(tdata, &commit->object, bitmap_pos); bitmap_set(tdata->base, bitmap_pos); @@ -1694,26 +1707,26 @@ void test_bitmap_walk(struct rev_info *revs) struct ewah_bitmap *bm; if (!(bitmap_git = prepare_bitmap_git(revs->repo))) - die("failed to load bitmap indexes"); + die(_("failed to load bitmap indexes")); if (revs->pending.nr != 1) - die("you must specify exactly one commit to test"); + die(_("you must specify exactly one commit to test")); - fprintf(stderr, "Bitmap v%d test (%d entries loaded)\n", + fprintf_ln(stderr, "Bitmap v%d test (%d entries loaded)", bitmap_git->version, bitmap_git->entry_count); root = revs->pending.objects[0].item; bm = bitmap_for_commit(bitmap_git, (struct commit *)root); if (bm) { - fprintf(stderr, "Found bitmap for %s. %d bits / %08x checksum\n", + fprintf_ln(stderr, "Found bitmap for '%s'. %d bits / %08x checksum", oid_to_hex(&root->oid), (int)bm->bit_size, ewah_checksum(bm)); result = ewah_to_bitmap(bm); } if (!result) - die("Commit %s doesn't have an indexed bitmap", oid_to_hex(&root->oid)); + die(_("commit '%s' doesn't have an indexed bitmap"), oid_to_hex(&root->oid)); revs->tag_objects = 1; revs->tree_objects = 1; @@ -1722,7 +1735,7 @@ void test_bitmap_walk(struct rev_info *revs) result_popcnt = bitmap_popcount(result); if (prepare_revision_walk(revs)) - die("revision walk setup failed"); + die(_("revision walk setup failed")); tdata.bitmap_git = bitmap_git; tdata.base = bitmap_new(); @@ -1738,9 +1751,9 @@ void test_bitmap_walk(struct rev_info *revs) stop_progress(&tdata.prg); if (bitmap_equals(result, tdata.base)) - fprintf(stderr, "OK!\n"); + fprintf_ln(stderr, "OK!"); else - die("mismatch in bitmap results"); + die(_("mismatch in bitmap results")); bitmap_free(result); bitmap_free(tdata.base); @@ -1758,10 +1771,10 @@ int test_bitmap_commits(struct repository *r) MAYBE_UNUSED void *value; if (!bitmap_git) - die("failed to load bitmap indexes"); + die(_("failed to load bitmap indexes")); kh_foreach(bitmap_git->bitmaps, oid, value, { - printf("%s\n", oid_to_hex(&oid)); + printf_ln("%s", oid_to_hex(&oid)); }); free_bitmap_index(bitmap_git); @@ -1786,7 +1799,7 @@ int test_bitmap_hashes(struct repository *r) nth_bitmap_object_oid(bitmap_git, &oid, index_pos); - printf("%s %"PRIu32"\n", + printf_ln("%s %"PRIu32"", oid_to_hex(&oid), get_be32(bitmap_git->hashes + index_pos)); } @@ -1948,7 +1961,7 @@ static off_t get_disk_usage_for_type(struct bitmap_index *bitmap_git, struct object_id oid; nth_midxed_object_oid(&oid, bitmap_git->midx, midx_pos); - die(_("could not find %s in pack %s at offset %"PRIuMAX), + die(_("could not find '%s' in pack '%s' at offset %"PRIuMAX), oid_to_hex(&oid), pack->pack_name, (uintmax_t)offset); @@ -1984,7 +1997,7 @@ static off_t get_disk_usage_for_extended(struct bitmap_index *bitmap_git) continue; if (oid_object_info_extended(the_repository, &obj->oid, &oi, 0) < 0) - die(_("unable to get disk usage of %s"), + die(_("unable to get disk usage of '%s'"), oid_to_hex(&obj->oid)); total += object_size; diff --git a/packfile.c b/packfile.c index 8e812a84a3..ed69fe457b 100644 --- a/packfile.c +++ b/packfile.c @@ -2275,7 +2275,8 @@ int is_promisor_object(const struct object_id *oid) if (has_promisor_remote()) { for_each_packed_object(add_promisor_object, &promisor_objects, - FOR_EACH_OBJECT_PROMISOR_ONLY); + FOR_EACH_OBJECT_PROMISOR_ONLY | + FOR_EACH_OBJECT_PACK_ORDER); } promisor_objects_prepared = 1; } @@ -1575,23 +1575,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ strbuf_addstr(sb, c->signature_check.primary_key_fingerprint); break; case 'T': - switch (c->signature_check.trust_level) { - case TRUST_UNDEFINED: - strbuf_addstr(sb, "undefined"); - break; - case TRUST_NEVER: - strbuf_addstr(sb, "never"); - break; - case TRUST_MARGINAL: - strbuf_addstr(sb, "marginal"); - break; - case TRUST_FULLY: - strbuf_addstr(sb, "fully"); - break; - case TRUST_ULTIMATE: - strbuf_addstr(sb, "ultimate"); - break; - } + strbuf_addstr(sb, gpg_trust_level_to_str(c->signature_check.trust_level)); break; default: return 0; diff --git a/ref-filter.c b/ref-filter.c index d3c90e5dbe..bdf39fa761 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -2405,6 +2405,7 @@ static void reach_filter(struct ref_array *array, int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int type) { struct ref_filter_cbdata ref_cbdata; + int save_commit_buffer_orig; int ret = 0; ref_cbdata.array = array; @@ -2412,6 +2413,9 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int filter->kind = type & FILTER_REFS_KIND_MASK; + save_commit_buffer_orig = save_commit_buffer; + save_commit_buffer = 0; + init_contains_cache(&ref_cbdata.contains_cache); init_contains_cache(&ref_cbdata.no_contains_cache); @@ -2444,6 +2448,7 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int reach_filter(array, filter->reachable_from, INCLUDE_REACHED); reach_filter(array, filter->unreachable_from, EXCLUDE_REACHED); + save_commit_buffer = save_commit_buffer_orig; return ret; } diff --git a/remote-curl.c b/remote-curl.c index 67f178b112..b8758757ec 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -580,6 +580,7 @@ struct rpc_state { char *service_url; char *hdr_content_type; char *hdr_accept; + char *hdr_accept_language; char *protocol_header; char *buf; size_t alloc; @@ -607,6 +608,8 @@ struct rpc_state { unsigned flush_read_but_not_sent : 1; }; +#define RPC_STATE_INIT { 0 } + /* * Appends the result of reading from rpc->out to the string represented by * rpc->buf and rpc->len if there is enough space. Returns 1 if there was @@ -932,6 +935,10 @@ static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_rece headers = curl_slist_append(headers, needs_100_continue ? "Expect: 100-continue" : "Expect:"); + /* Add Accept-Language header */ + if (rpc->hdr_accept_language) + headers = curl_slist_append(headers, rpc->hdr_accept_language); + /* Add the extra Git-Protocol header */ if (rpc->protocol_header) headers = curl_slist_append(headers, rpc->protocol_header); @@ -1080,6 +1087,8 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads, strbuf_addf(&buf, "%s%s", url.buf, svc); rpc->service_url = strbuf_detach(&buf, NULL); + rpc->hdr_accept_language = xstrdup_or_null(http_get_accept_language_header()); + strbuf_addf(&buf, "Content-Type: application/x-%s-request", svc); rpc->hdr_content_type = strbuf_detach(&buf, NULL); @@ -1118,6 +1127,7 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads, free(rpc->service_url); free(rpc->hdr_content_type); free(rpc->hdr_accept); + free(rpc->hdr_accept_language); free(rpc->protocol_header); free(rpc->buf); strbuf_release(&buf); @@ -1153,7 +1163,7 @@ static int fetch_dumb(int nr_heads, struct ref **to_fetch) static int fetch_git(struct discovery *heads, int nr_heads, struct ref **to_fetch) { - struct rpc_state rpc; + struct rpc_state rpc = RPC_STATE_INIT; struct strbuf preamble = STRBUF_INIT; int i, err; struct strvec args = STRVEC_INIT; @@ -1299,7 +1309,7 @@ static int push_dav(int nr_spec, const char **specs) static int push_git(struct discovery *heads, int nr_spec, const char **specs) { - struct rpc_state rpc; + struct rpc_state rpc = RPC_STATE_INIT; int i, err; struct strvec args; struct string_list_item *cas_option; @@ -1398,8 +1408,9 @@ free_specs: static int stateless_connect(const char *service_name) { struct discovery *discover; - struct rpc_state rpc; + struct rpc_state rpc = RPC_STATE_INIT; struct strbuf buf = STRBUF_INIT; + const char *accept_language; /* * Run the info/refs request and see if the server supports protocol @@ -1418,6 +1429,9 @@ static int stateless_connect(const char *service_name) printf("\n"); fflush(stdout); } + accept_language = http_get_accept_language_header(); + if (accept_language) + rpc.hdr_accept_language = xstrfmt("%s", accept_language); rpc.service_name = service_name; rpc.service_url = xstrfmt("%s%s", url.buf, rpc.service_name); @@ -1467,6 +1481,7 @@ static int stateless_connect(const char *service_name) free(rpc.service_url); free(rpc.hdr_content_type); free(rpc.hdr_accept); + free(rpc.hdr_accept_language); free(rpc.protocol_header); free(rpc.buf); strbuf_release(&buf); @@ -850,7 +850,7 @@ static int refspec_match(const struct refspec_item *refspec, return !strcmp(refspec->src, name); } -static int omit_name_by_refspec(const char *name, struct refspec *rs) +int omit_name_by_refspec(const char *name, struct refspec *rs) { int i; @@ -248,6 +248,12 @@ int resolve_remote_symref(struct ref *ref, struct ref *list); struct ref *ref_remove_duplicates(struct ref *ref_map); /* + * Check whether a name matches any negative refspec in rs. Returns 1 if the + * name matches at least one negative refspec, and 0 otherwise. + */ +int omit_name_by_refspec(const char *name, struct refspec *rs); + +/* * Remove all entries in the input list which match any negative refspec in * the refspec list. */ diff --git a/repo-settings.c b/repo-settings.c index 2dfcb2b654..43bc881bfc 100644 --- a/repo-settings.c +++ b/repo-settings.c @@ -11,6 +11,13 @@ static void repo_cfg_bool(struct repository *r, const char *key, int *dest, *dest = def; } +static void repo_cfg_int(struct repository *r, const char *key, int *dest, + int def) +{ + if (repo_config_get_int(r, key, dest)) + *dest = def; +} + void prepare_repo_settings(struct repository *r) { int experimental; @@ -42,11 +49,14 @@ void prepare_repo_settings(struct repository *r) r->settings.core_untracked_cache = UNTRACKED_CACHE_WRITE; } - /* Boolean config or default, does not cascade (simple) */ + /* Commit graph config or default, does not cascade (simple) */ repo_cfg_bool(r, "core.commitgraph", &r->settings.core_commit_graph, 1); + repo_cfg_int(r, "commitgraph.generationversion", &r->settings.commit_graph_generation_version, 2); repo_cfg_bool(r, "commitgraph.readchangedpaths", &r->settings.commit_graph_read_changed_paths, 1); repo_cfg_bool(r, "gc.writecommitgraph", &r->settings.gc_write_commit_graph, 1); repo_cfg_bool(r, "fetch.writecommitgraph", &r->settings.fetch_write_commit_graph, 0); + + /* Boolean config or default, does not cascade (simple) */ repo_cfg_bool(r, "pack.usesparse", &r->settings.pack_use_sparse, 1); repo_cfg_bool(r, "core.multipackindex", &r->settings.core_multi_pack_index, 1); repo_cfg_bool(r, "index.sparse", &r->settings.sparse_index, 0); diff --git a/repository.h b/repository.h index 6cc661e5a4..797f471cce 100644 --- a/repository.h +++ b/repository.h @@ -30,6 +30,7 @@ struct repo_settings { int initialized; int core_commit_graph; + int commit_graph_generation_version; int commit_graph_read_changed_paths; int gc_write_commit_graph; int fetch_write_commit_graph; diff --git a/revision.c b/revision.c index 211352795c..0c6e26cd9c 100644 --- a/revision.c +++ b/revision.c @@ -33,6 +33,7 @@ #include "bloom.h" #include "json-writer.h" #include "list-objects-filter-options.h" +#include "resolve-undo.h" volatile show_early_output_fn_t show_early_output; @@ -1696,6 +1697,39 @@ static void add_cache_tree(struct cache_tree *it, struct rev_info *revs, } +static void add_resolve_undo_to_pending(struct index_state *istate, struct rev_info *revs) +{ + struct string_list_item *item; + struct string_list *resolve_undo = istate->resolve_undo; + + if (!resolve_undo) + return; + + for_each_string_list_item(item, resolve_undo) { + const char *path = item->string; + struct resolve_undo_info *ru = item->util; + int i; + + if (!ru) + continue; + for (i = 0; i < 3; i++) { + struct blob *blob; + + if (!ru->mode[i] || !S_ISREG(ru->mode[i])) + continue; + + blob = lookup_blob(revs->repo, &ru->oid[i]); + if (!blob) { + warning(_("resolve-undo records `%s` which is missing"), + oid_to_hex(&ru->oid[i])); + continue; + } + add_pending_object_with_path(revs, &blob->object, "", + ru->mode[i], path); + } + } +} + static void do_add_index_objects_to_pending(struct rev_info *revs, struct index_state *istate, unsigned int flags) @@ -1724,6 +1758,8 @@ static void do_add_index_objects_to_pending(struct rev_info *revs, add_cache_tree(istate->cache_tree, revs, &path, flags); strbuf_release(&path); } + + add_resolve_undo_to_pending(istate, revs); } void add_index_objects_to_pending(struct rev_info *revs, unsigned int flags) diff --git a/send-pack.c b/send-pack.c index bc0fcdbb00..662f7c2aeb 100644 --- a/send-pack.c +++ b/send-pack.c @@ -84,6 +84,8 @@ static int pack_objects(int fd, struct ref *refs, struct oid_array *advertised, strvec_push(&po.args, "--progress"); if (is_repository_shallow(the_repository)) strvec_push(&po.args, "--shallow"); + if (args->disable_bitmaps) + strvec_push(&po.args, "--no-use-bitmap-index"); po.in = -1; po.out = args->stateless_rpc ? -1 : fd; po.git_cmd = 1; @@ -487,6 +489,7 @@ int send_pack(struct send_pack_args *args, struct async demux; const char *push_cert_nonce = NULL; struct packet_reader reader; + int use_bitmaps; if (!remote_refs) { fprintf(stderr, "No refs in common and none specified; doing nothing.\n" @@ -498,6 +501,9 @@ int send_pack(struct send_pack_args *args, if (push_negotiate) get_commons_through_negotiation(args->url, remote_refs, &commons); + if (!git_config_get_bool("push.usebitmaps", &use_bitmaps)) + args->disable_bitmaps = !use_bitmaps; + git_config_get_bool("transfer.advertisesid", &advertise_sid); /* Does the other end support the reporting? */ diff --git a/send-pack.h b/send-pack.h index e148fcd960..7edb80596c 100644 --- a/send-pack.h +++ b/send-pack.h @@ -26,7 +26,8 @@ struct send_pack_args { /* One of the SEND_PACK_PUSH_CERT_* constants. */ push_cert:2, stateless_rpc:1, - atomic:1; + atomic:1, + disable_bitmaps:1; const struct string_list *push_options; }; @@ -10,6 +10,10 @@ static int inside_git_dir = -1; static int inside_work_tree = -1; static int work_tree_config_is_bogus; +enum allowed_bare_repo { + ALLOWED_BARE_REPO_EXPLICIT = 0, + ALLOWED_BARE_REPO_ALL, +}; static struct startup_info the_startup_info; struct startup_info *startup_info = &the_startup_info; @@ -1155,11 +1159,51 @@ static int ensure_valid_ownership(const char *gitfile, * constant regardless of what failed above. data.is_safe should be * initialized to false, and might be changed by the callback. */ - read_very_early_config(safe_directory_cb, &data); + git_protected_config(safe_directory_cb, &data); return data.is_safe; } +static int allowed_bare_repo_cb(const char *key, const char *value, void *d) +{ + enum allowed_bare_repo *allowed_bare_repo = d; + + if (strcasecmp(key, "safe.bareRepository")) + return 0; + + if (!strcmp(value, "explicit")) { + *allowed_bare_repo = ALLOWED_BARE_REPO_EXPLICIT; + return 0; + } + if (!strcmp(value, "all")) { + *allowed_bare_repo = ALLOWED_BARE_REPO_ALL; + return 0; + } + return -1; +} + +static enum allowed_bare_repo get_allowed_bare_repo(void) +{ + enum allowed_bare_repo result = ALLOWED_BARE_REPO_ALL; + git_protected_config(allowed_bare_repo_cb, &result); + return result; +} + +static const char *allowed_bare_repo_to_string( + enum allowed_bare_repo allowed_bare_repo) +{ + switch (allowed_bare_repo) { + case ALLOWED_BARE_REPO_EXPLICIT: + return "explicit"; + case ALLOWED_BARE_REPO_ALL: + return "all"; + default: + BUG("invalid allowed_bare_repo %d", + allowed_bare_repo); + } + return NULL; +} + enum discovery_result { GIT_DIR_NONE = 0, GIT_DIR_EXPLICIT, @@ -1169,7 +1213,8 @@ enum discovery_result { GIT_DIR_HIT_CEILING = -1, GIT_DIR_HIT_MOUNT_POINT = -2, GIT_DIR_INVALID_GITFILE = -3, - GIT_DIR_INVALID_OWNERSHIP = -4 + GIT_DIR_INVALID_OWNERSHIP = -4, + GIT_DIR_DISALLOWED_BARE = -5, }; /* @@ -1297,6 +1342,8 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir, } if (is_git_directory(dir->buf)) { + if (get_allowed_bare_repo() == ALLOWED_BARE_REPO_EXPLICIT) + return GIT_DIR_DISALLOWED_BARE; if (!ensure_valid_ownership(NULL, NULL, dir->buf)) return GIT_DIR_INVALID_OWNERSHIP; strbuf_addstr(gitdir, "."); @@ -1443,6 +1490,14 @@ const char *setup_git_directory_gently(int *nongit_ok) } *nongit_ok = 1; break; + case GIT_DIR_DISALLOWED_BARE: + if (!nongit_ok) { + die(_("cannot use bare repository '%s' (safe.bareRepository is '%s')"), + dir.buf, + allowed_bare_repo_to_string(get_allowed_bare_repo())); + } + *nongit_ok = 1; + break; case GIT_DIR_NONE: /* * As a safeguard against setup_git_directory_gently_1 returning diff --git a/sha256/nettle.h b/sha256/nettle.h new file mode 100644 index 0000000000..b63e1c8190 --- /dev/null +++ b/sha256/nettle.h @@ -0,0 +1,31 @@ +#ifndef SHA256_NETTLE_H +#define SHA256_NETTLE_H + +#include <nettle/sha2.h> + +typedef struct sha256_ctx nettle_SHA256_CTX; + +static inline void nettle_SHA256_Init(nettle_SHA256_CTX *ctx) +{ + sha256_init(ctx); +} + +static inline void nettle_SHA256_Update(nettle_SHA256_CTX *ctx, + const void *data, + size_t len) +{ + sha256_update(ctx, len, data); +} + +static inline void nettle_SHA256_Final(unsigned char *digest, + nettle_SHA256_CTX *ctx) +{ + sha256_digest(ctx, SHA256_DIGEST_SIZE, digest); +} + +#define platform_SHA256_CTX nettle_SHA256_CTX +#define platform_SHA256_Init nettle_SHA256_Init +#define platform_SHA256_Update nettle_SHA256_Update +#define platform_SHA256_Final nettle_SHA256_Final + +#endif diff --git a/shared.mak b/shared.mak index 4330192e9c..33f43edbf9 100644 --- a/shared.mak +++ b/shared.mak @@ -70,6 +70,7 @@ ifndef V QUIET_HDR = @echo ' ' HDR $(<:hcc=h); QUIET_RC = @echo ' ' RC $@; QUIET_SPATCH = @echo ' ' SPATCH $<; + QUIET_SPATCH_T = @echo ' ' SPATCH TEST $(@:.build/%=%); ## Used in "Documentation/Makefile" QUIET_ASCIIDOC = @echo ' ' ASCIIDOC $@; diff --git a/submodule.c b/submodule.c index 4e299f578f..3fa5db3ecd 100644 --- a/submodule.c +++ b/submodule.c @@ -2374,7 +2374,7 @@ void absorb_git_dir_into_superproject(const char *path, cp.no_stdin = 1; strvec_pushl(&cp.args, "--super-prefix", sb.buf, "submodule--helper", - "absorb-git-dirs", NULL); + "absorbgitdirs", NULL); prepare_submodule_repo_env(&cp.env); if (run_command(&cp)) die(_("could not recurse into submodule '%s'"), path); @@ -2388,7 +2388,7 @@ int get_superproject_working_tree(struct strbuf *buf) struct child_process cp = CHILD_PROCESS_INIT; struct strbuf sb = STRBUF_INIT; struct strbuf one_up = STRBUF_INIT; - const char *cwd = xgetcwd(); + char *cwd = xgetcwd(); int ret = 0; const char *subpath; int code; @@ -2451,6 +2451,7 @@ int get_superproject_working_tree(struct strbuf *buf) ret = 1; free(super_wt); } + free(cwd); strbuf_release(&sb); code = finish_command(&cp); diff --git a/t/Makefile b/t/Makefile index 056ce55dcc..7f56e52f76 100644 --- a/t/Makefile +++ b/t/Makefile @@ -35,7 +35,6 @@ TEST_RESULTS_DIRECTORY_SQ = $(subst ','\'',$(TEST_RESULTS_DIRECTORY)) CHAINLINTTMP_SQ = $(subst ','\'',$(CHAINLINTTMP)) T = $(sort $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)) -TGITWEB = $(sort $(wildcard t95[0-9][0-9]-*.sh)) THELPERS = $(sort $(filter-out $(T),$(wildcard *.sh))) TPERF = $(sort $(wildcard perf/p[0-9][0-9][0-9][0-9]-*.sh)) CHAINLINTTESTS = $(sort $(patsubst chainlint/%.test,%,$(wildcard chainlint/*.test))) @@ -112,9 +111,6 @@ aggregate-results: echo "$$f"; \ done | '$(SHELL_PATH_SQ)' ./aggregate-results.sh -gitweb-test: - $(MAKE) $(TGITWEB) - valgrind: $(MAKE) GIT_TEST_OPTS="$(GIT_TEST_OPTS) --valgrind" diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh index cc01d89150..f1b9a6ce4d 100644 --- a/t/annotate-tests.sh +++ b/t/annotate-tests.sh @@ -153,7 +153,7 @@ test_expect_success 'blame evil merge' ' test_expect_success 'blame huge graft' ' test_when_finished "git checkout branch2" && - test_when_finished "rm -f .git/info/grafts" && + test_when_finished "rm -rf .git/info" && graft= && for i in 0 1 2 do @@ -168,6 +168,7 @@ test_expect_success 'blame huge graft' ' graft="$graft$commit " || return 1 done done && + mkdir .git/info && printf "%s " $graft >.git/info/grafts && check_count -h 00 01 1 10 1 ' diff --git a/t/helper/test-bloom.c b/t/helper/test-bloom.c index ad3ef1cd77..6c900ca668 100644 --- a/t/helper/test-bloom.c +++ b/t/helper/test-bloom.c @@ -16,6 +16,7 @@ static void add_string_to_filter(const char *data, struct bloom_filter *filter) } printf("\n"); add_key_to_filter(&key, filter, &settings); + clear_bloom_key(&key); } static void print_bloom_filter(struct bloom_filter *filter) { @@ -80,6 +81,7 @@ int cmd__bloom(int argc, const char **argv) } print_bloom_filter(&filter); + free(filter.data); } if (!strcmp(argv[1], "get_filter_for_commit")) { diff --git a/t/helper/test-delta.c b/t/helper/test-delta.c index e749a49c88..b15481ea59 100644 --- a/t/helper/test-delta.c +++ b/t/helper/test-delta.c @@ -20,8 +20,9 @@ int cmd__delta(int argc, const char **argv) { int fd; struct stat st; - void *from_buf, *data_buf, *out_buf; + void *from_buf = NULL, *data_buf = NULL, *out_buf = NULL; unsigned long from_size, data_size, out_size; + int ret = 1; if (argc != 5 || (strcmp(argv[1], "-d") && strcmp(argv[1], "-p"))) { fprintf(stderr, "usage: %s\n", usage_str); @@ -38,21 +39,21 @@ int cmd__delta(int argc, const char **argv) if (read_in_full(fd, from_buf, from_size) < 0) { perror(argv[2]); close(fd); - return 1; + goto cleanup; } close(fd); fd = open(argv[3], O_RDONLY); if (fd < 0 || fstat(fd, &st)) { perror(argv[3]); - return 1; + goto cleanup; } data_size = st.st_size; data_buf = xmalloc(data_size); if (read_in_full(fd, data_buf, data_size) < 0) { perror(argv[3]); close(fd); - return 1; + goto cleanup; } close(fd); @@ -66,14 +67,20 @@ int cmd__delta(int argc, const char **argv) &out_size); if (!out_buf) { fprintf(stderr, "delta operation failed (returned NULL)\n"); - return 1; + goto cleanup; } fd = open (argv[4], O_WRONLY|O_CREAT|O_TRUNC, 0666); if (fd < 0 || write_in_full(fd, out_buf, out_size) < 0) { perror(argv[4]); - return 1; + goto cleanup; } - return 0; + ret = 0; +cleanup: + free(from_buf); + free(data_buf); + free(out_buf); + + return ret; } diff --git a/t/helper/test-dump-cache-tree.c b/t/helper/test-dump-cache-tree.c index 6a3f88f5f5..0d6d7f1ecb 100644 --- a/t/helper/test-dump-cache-tree.c +++ b/t/helper/test-dump-cache-tree.c @@ -59,11 +59,16 @@ int cmd__dump_cache_tree(int ac, const char **av) { struct index_state istate; struct cache_tree *another = cache_tree(); + int ret; + setup_git_directory(); if (read_cache() < 0) die("unable to read index file"); istate = the_index; istate.cache_tree = another; cache_tree_update(&istate, WRITE_TREE_DRY_RUN); - return dump_cache_tree(active_cache_tree, another, ""); + ret = dump_cache_tree(active_cache_tree, another, ""); + cache_tree_free(&another); + + return ret; } diff --git a/t/helper/test-hash.c b/t/helper/test-hash.c index 261c545b9d..5860dab0ff 100644 --- a/t/helper/test-hash.c +++ b/t/helper/test-hash.c @@ -54,5 +54,6 @@ int cmd_hash_impl(int ac, const char **av, int algo) fwrite(hash, 1, algop->rawsz, stdout); else puts(hash_to_hex_algop(hash, algop)); + free(buffer); return 0; } diff --git a/t/helper/test-json-writer.c b/t/helper/test-json-writer.c index 37c452535f..8c3edacc00 100644 --- a/t/helper/test-json-writer.c +++ b/t/helper/test-json-writer.c @@ -181,12 +181,18 @@ static struct json_writer nest1 = JSON_WRITER_INIT; static void make_nest1(int pretty) { + make_obj1(0); + make_arr1(0); + jw_object_begin(&nest1, pretty); { jw_object_sub_jw(&nest1, "obj1", &obj1); jw_object_sub_jw(&nest1, "arr1", &arr1); } jw_end(&nest1); + + jw_release(&obj1); + jw_release(&arr1); } static char *expect_inline1 = @@ -313,6 +319,9 @@ static void make_mixed1(int pretty) jw_object_sub_jw(&mixed1, "arr1", &arr1); } jw_end(&mixed1); + + jw_release(&obj1); + jw_release(&arr1); } static void cmp(const char *test, const struct json_writer *jw, const char *exp) @@ -325,8 +334,8 @@ static void cmp(const char *test, const struct json_writer *jw, const char *exp) exit(1); } -#define t(v) do { make_##v(0); cmp(#v, &v, expect_##v); } while (0) -#define p(v) do { make_##v(1); cmp(#v, &v, pretty_##v); } while (0) +#define t(v) do { make_##v(0); cmp(#v, &v, expect_##v); jw_release(&v); } while (0) +#define p(v) do { make_##v(1); cmp(#v, &v, pretty_##v); jw_release(&v); } while (0) /* * Run some basic regression tests with some known patterns. @@ -381,7 +390,6 @@ static int unit_tests(void) /* mixed forms */ t(mixed1); - jw_init(&mixed1); p(mixed1); return 0; @@ -544,7 +552,7 @@ static int scripted(void) printf("%s\n", jw.json.buf); - strbuf_release(&jw.json); + jw_release(&jw); return 0; } diff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c index 229ed416b0..d20e1b7a18 100644 --- a/t/helper/test-path-utils.c +++ b/t/helper/test-path-utils.c @@ -296,9 +296,8 @@ int cmd__path_utils(int argc, const char **argv) if (argc == 3 && !strcmp(argv[1], "normalize_path_copy")) { char *buf = xmallocz(strlen(argv[2])); int rv = normalize_path_copy(buf, argv[2]); - if (rv) - buf = "++failed++"; - puts(buf); + puts(rv ? "++failed++" : buf); + free(buf); return 0; } @@ -356,7 +355,10 @@ int cmd__path_utils(int argc, const char **argv) int nongit_ok; setup_git_directory_gently(&nongit_ok); while (argc > 3) { - puts(prefix_path(prefix, prefix_len, argv[3])); + char *pfx = prefix_path(prefix, prefix_len, argv[3]); + + puts(pfx); + free(pfx); argc--; argv++; } @@ -366,6 +368,7 @@ int cmd__path_utils(int argc, const char **argv) if (argc == 4 && !strcmp(argv[1], "strip_path_suffix")) { char *prefix = strip_path_suffix(argv[2], argv[3]); printf("%s\n", prefix ? prefix : "(null)"); + free(prefix); return 0; } diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c index 9646d85fc8..4d18bfb1ca 100644 --- a/t/helper/test-ref-store.c +++ b/t/helper/test-ref-store.c @@ -96,6 +96,7 @@ static const char **get_store(const char **argv, struct ref_store **refs) die("no such worktree: %s", gitdir); *refs = get_worktree_ref_store(*p); + free_worktrees(worktrees); } else die("unknown backend %s", argv[0]); diff --git a/t/helper/test-regex.c b/t/helper/test-regex.c index d6f28ca8d1..bd871a735b 100644 --- a/t/helper/test-regex.c +++ b/t/helper/test-regex.c @@ -34,6 +34,7 @@ static int test_regex_bug(void) if (m[0].rm_so == 3) /* matches '\n' when it should not */ die("regex bug confirmed: re-build git with NO_REGEX=1"); + regfree(&r); return 0; } @@ -94,18 +95,20 @@ int cmd__regex(int argc, const char **argv) die("failed regcomp() for pattern '%s' (%s)", pat, errbuf); } if (!str) - return 0; + goto cleanup; ret = regexec(&r, str, 1, m, 0); if (ret) { if (silent || ret == REG_NOMATCH) - return ret; + goto cleanup; regerror(ret, &r, errbuf, sizeof(errbuf)); die("failed regexec() for subject '%s' (%s)", str, errbuf); } - return 0; +cleanup: + regfree(&r); + return ret; usage: usage("\ttest-tool regex --bug\n" "\ttest-tool regex [--silent] <pattern>\n" diff --git a/t/helper/test-scrap-cache-tree.c b/t/helper/test-scrap-cache-tree.c index 393f1604ff..026c802479 100644 --- a/t/helper/test-scrap-cache-tree.c +++ b/t/helper/test-scrap-cache-tree.c @@ -12,6 +12,7 @@ int cmd__scrap_cache_tree(int ac, const char **av) hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); if (read_cache() < 0) die("unable to read index file"); + cache_tree_free(&active_cache_tree); active_cache_tree = NULL; if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) die("unable to write index file"); diff --git a/t/helper/test-urlmatch-normalization.c b/t/helper/test-urlmatch-normalization.c index 8f4d67e646..86edd454f5 100644 --- a/t/helper/test-urlmatch-normalization.c +++ b/t/helper/test-urlmatch-normalization.c @@ -5,8 +5,9 @@ int cmd__urlmatch_normalization(int argc, const char **argv) { const char usage[] = "test-tool urlmatch-normalization [-p | -l] <url1> | <url1> <url2>"; - char *url1, *url2; + char *url1 = NULL, *url2 = NULL; int opt_p = 0, opt_l = 0; + int ret = 0; /* * For one url, succeed if url_normalize succeeds on it, fail otherwise. @@ -39,7 +40,7 @@ int cmd__urlmatch_normalization(int argc, const char **argv) printf("%s\n", url1); if (opt_l) printf("%u\n", (unsigned)info.url_len); - return 0; + goto cleanup; } if (opt_p || opt_l) @@ -47,5 +48,9 @@ int cmd__urlmatch_normalization(int argc, const char **argv) url1 = url_normalize(argv[1], NULL); url2 = url_normalize(argv[2], NULL); - return (url1 && url2 && !strcmp(url1, url2)) ? 0 : 1; + ret = (url1 && url2 && !strcmp(url1, url2)) ? 0 : 1; +cleanup: + free(url1); + free(url2); + return ret; } diff --git a/t/lib-proto-disable.sh b/t/lib-proto-disable.sh index 83babe57d9..890622be81 100644 --- a/t/lib-proto-disable.sh +++ b/t/lib-proto-disable.sh @@ -1,7 +1,7 @@ # Test routines for checking protocol disabling. -# Test clone/fetch/push with GIT_ALLOW_PROTOCOL whitelist -test_whitelist () { +# Test clone/fetch/push with GIT_ALLOW_PROTOCOL environment variable +test_allow_var () { desc=$1 proto=$2 url=$3 @@ -183,7 +183,7 @@ test_config () { # $2 - machine-readable name of the protocol # $3 - the URL to try cloning test_proto () { - test_whitelist "$@" + test_allow_var "$@" test_config "$@" } diff --git a/t/lib-submodule-update.sh b/t/lib-submodule-update.sh index f7c7df0ca4..03e0abbdb8 100644 --- a/t/lib-submodule-update.sh +++ b/t/lib-submodule-update.sh @@ -207,7 +207,7 @@ prolog () { # should be updated to an existing commit. reset_work_tree_to () { rm -rf submodule_update && - git clone submodule_update_repo submodule_update && + git clone --template= submodule_update_repo submodule_update && ( cd submodule_update && rm -rf sub1 && @@ -902,13 +902,14 @@ test_submodule_switch_recursing_with_args () { ' # ... but an ignored file is fine. test_expect_$RESULTOI "$command: added submodule removes an untracked ignored file" ' - test_when_finished "rm submodule_update/.git/info/exclude" && + test_when_finished "rm -rf submodule_update/.git/info" && prolog && reset_work_tree_to_interested no_submodule && ( cd submodule_update && git branch -t add_sub1 origin/add_sub1 && : >sub1 && + mkdir .git/info && echo sub1 >.git/info/exclude && $command add_sub1 && test_superproject_content origin/add_sub1 && @@ -951,7 +952,9 @@ test_submodule_switch_recursing_with_args () { reset_work_tree_to_interested add_sub1 && ( cd submodule_update && + rm -rf .git/modules/sub1/info && git branch -t replace_sub1_with_file origin/replace_sub1_with_file && + mkdir .git/modules/sub1/info && echo ignored >.git/modules/sub1/info/exclude && : >sub1/ignored && $command replace_sub1_with_file && diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh index 143f100517..f7ee2f2ff0 100755 --- a/t/t0003-attributes.sh +++ b/t/t0003-attributes.sh @@ -3,6 +3,7 @@ test_description=gitattributes TEST_PASSES_SANITIZE_LEAK=true +TEST_CREATE_REPO_NO_TEMPLATE=1 . ./test-lib.sh attr_check_basic () { @@ -284,7 +285,7 @@ test_expect_success 'using --git-dir and --work-tree' ' ' test_expect_success 'setup bare' ' - git clone --bare . bare.git + git clone --template= --bare . bare.git ' test_expect_success 'bare repository: check that .gitattribute is ignored' ' @@ -315,6 +316,7 @@ test_expect_success 'bare repository: check that --cached honors index' ' test_expect_success 'bare repository: test info/attributes' ' ( cd bare.git && + mkdir info && ( echo "f test=f" && echo "a/i test=a/i" @@ -360,6 +362,7 @@ test_expect_success SYMLINKS 'symlinks respected in core.attributesFile' ' test_expect_success SYMLINKS 'symlinks respected in info/attributes' ' test_when_finished "rm .git/info/attributes" && + mkdir .git/info && ln -s ../../attr .git/info/attributes && attr_check file set ' diff --git a/t/t0008-ignores.sh b/t/t0008-ignores.sh index 5575dade8e..c70d11bc91 100755 --- a/t/t0008-ignores.sh +++ b/t/t0008-ignores.sh @@ -3,6 +3,7 @@ test_description=check-ignore TEST_PASSES_SANITIZE_LEAK=true +TEST_CREATE_REPO_NO_TEMPLATE=1 . ./test-lib.sh init_vars () { @@ -225,7 +226,8 @@ test_expect_success 'setup' ' !globaltwo globalthree EOF - cat <<-\EOF >>.git/info/exclude + mkdir .git/info && + cat <<-\EOF >.git/info/exclude per-repo EOF ' @@ -543,9 +545,9 @@ test_expect_success_multi 'submodule from subdirectory' '' ' test_expect_success 'global ignore not yet enabled' ' expect_from_stdin <<-\EOF && - .git/info/exclude:7:per-repo per-repo + .git/info/exclude:1:per-repo per-repo a/.gitignore:2:*three a/globalthree - .git/info/exclude:7:per-repo a/per-repo + .git/info/exclude:1:per-repo a/per-repo EOF test_check_ignore "-v globalone per-repo a/globalthree a/per-repo not-ignored a/globaltwo" ' @@ -566,10 +568,10 @@ test_expect_success 'global ignore with -v' ' enable_global_excludes && expect_from_stdin <<-EOF && $global_excludes:1:globalone globalone - .git/info/exclude:7:per-repo per-repo + .git/info/exclude:1:per-repo per-repo $global_excludes:3:globalthree globalthree a/.gitignore:2:*three a/globalthree - .git/info/exclude:7:per-repo a/per-repo + .git/info/exclude:1:per-repo a/per-repo $global_excludes:2:!globaltwo globaltwo EOF test_check_ignore "-v globalone per-repo globalthree a/globalthree a/per-repo not-ignored globaltwo" diff --git a/t/t0015-hash.sh b/t/t0015-hash.sh index 086822fc45..0a087a1983 100755 --- a/t/t0015-hash.sh +++ b/t/t0015-hash.sh @@ -1,8 +1,9 @@ #!/bin/sh test_description='test basic hash implementation' -. ./test-lib.sh +TEST_PASSES_SANITIZE_LEAK=true +. ./test-lib.sh test_expect_success 'test basic SHA-1 hash values' ' test-tool sha1 </dev/null >actual && diff --git a/t/t0019-json-writer.sh b/t/t0019-json-writer.sh index 3b0c336b38..19a730c29e 100755 --- a/t/t0019-json-writer.sh +++ b/t/t0019-json-writer.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='test json-writer JSON generation' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'unit test of json-writer routines' ' diff --git a/t/t0028-working-tree-encoding.sh b/t/t0028-working-tree-encoding.sh index 82905a2156..c196fdb0ee 100755 --- a/t/t0028-working-tree-encoding.sh +++ b/t/t0028-working-tree-encoding.sh @@ -5,6 +5,8 @@ test_description='working-tree-encoding conversion via gitattributes' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true +TEST_CREATE_REPO_NO_TEMPLATE=1 . ./test-lib.sh . "$TEST_DIRECTORY/lib-encoding.sh" @@ -69,6 +71,7 @@ test_expect_success 'check $GIT_DIR/info/attributes support' ' test_when_finished "rm -f test.utf32.git" && test_when_finished "git reset --hard HEAD" && + mkdir .git/info && echo "*.utf32 text working-tree-encoding=utf-32" >.git/info/attributes && git add test.utf32 && diff --git a/t/t0033-safe-directory.sh b/t/t0033-safe-directory.sh index 3908597d42..f4d737dadd 100755 --- a/t/t0033-safe-directory.sh +++ b/t/t0033-safe-directory.sh @@ -16,24 +16,20 @@ test_expect_success 'safe.directory is not set' ' expect_rejected_dir ' -test_expect_success 'ignoring safe.directory on the command line' ' - test_must_fail git -c safe.directory="$(pwd)" status 2>err && - grep "dubious ownership" err +test_expect_success 'safe.directory on the command line' ' + git -c safe.directory="$(pwd)" status ' -test_expect_success 'ignoring safe.directory in the environment' ' - test_must_fail env GIT_CONFIG_COUNT=1 \ - GIT_CONFIG_KEY_0="safe.directory" \ - GIT_CONFIG_VALUE_0="$(pwd)" \ - git status 2>err && - grep "dubious ownership" err +test_expect_success 'safe.directory in the environment' ' + env GIT_CONFIG_COUNT=1 \ + GIT_CONFIG_KEY_0="safe.directory" \ + GIT_CONFIG_VALUE_0="$(pwd)" \ + git status ' -test_expect_success 'ignoring safe.directory in GIT_CONFIG_PARAMETERS' ' - test_must_fail env \ - GIT_CONFIG_PARAMETERS="${SQ}safe.directory${SQ}=${SQ}$(pwd)${SQ}" \ - git status 2>err && - grep "dubious ownership" err +test_expect_success 'safe.directory in GIT_CONFIG_PARAMETERS' ' + env GIT_CONFIG_PARAMETERS="${SQ}safe.directory${SQ}=${SQ}$(pwd)${SQ}" \ + git status ' test_expect_success 'ignoring safe.directory in repo config' ' diff --git a/t/t0035-safe-bare-repository.sh b/t/t0035-safe-bare-repository.sh new file mode 100755 index 0000000000..ecbdc8238d --- /dev/null +++ b/t/t0035-safe-bare-repository.sh @@ -0,0 +1,54 @@ +#!/bin/sh + +test_description='verify safe.bareRepository checks' + +TEST_PASSES_SANITIZE_LEAK=true +. ./test-lib.sh + +pwd="$(pwd)" + +expect_accepted () { + git "$@" rev-parse --git-dir +} + +expect_rejected () { + test_must_fail git "$@" rev-parse --git-dir 2>err && + grep -F "cannot use bare repository" err +} + +test_expect_success 'setup bare repo in worktree' ' + git init outer-repo && + git init --bare outer-repo/bare-repo +' + +test_expect_success 'safe.bareRepository unset' ' + expect_accepted -C outer-repo/bare-repo +' + +test_expect_success 'safe.bareRepository=all' ' + test_config_global safe.bareRepository all && + expect_accepted -C outer-repo/bare-repo +' + +test_expect_success 'safe.bareRepository=explicit' ' + test_config_global safe.bareRepository explicit && + expect_rejected -C outer-repo/bare-repo +' + +test_expect_success 'safe.bareRepository in the repository' ' + # safe.bareRepository must not be "explicit", otherwise + # git config fails with "fatal: not in a git directory" (like + # safe.directory) + test_config -C outer-repo/bare-repo safe.bareRepository \ + all && + test_config_global safe.bareRepository explicit && + expect_rejected -C outer-repo/bare-repo +' + +test_expect_success 'safe.bareRepository on the command line' ' + test_config_global safe.bareRepository explicit && + expect_accepted -C outer-repo/bare-repo \ + -c safe.bareRepository=all +' + +test_done diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh index aa35350b6f..1f2007e62b 100755 --- a/t/t0060-path-utils.sh +++ b/t/t0060-path-utils.sh @@ -5,6 +5,7 @@ test_description='Test various path utilities' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh norm_path() { diff --git a/t/t0090-cache-tree.sh b/t/t0090-cache-tree.sh index 9067572648..d8e2fc42e1 100755 --- a/t/t0090-cache-tree.sh +++ b/t/t0090-cache-tree.sh @@ -5,6 +5,8 @@ test_description="Test whether cache-tree is properly updated Tests whether various commands properly update and/or rewrite the cache-tree extension. " + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh cmp_cache_tree () { diff --git a/t/t0095-bloom.sh b/t/t0095-bloom.sh index 5945973552..daeb4a5e3e 100755 --- a/t/t0095-bloom.sh +++ b/t/t0095-bloom.sh @@ -67,7 +67,7 @@ test_expect_success 'compute bloom key for test string 2' ' test_cmp expect actual ' -test_expect_success 'get bloom filters for commit with no changes' ' +test_expect_success !SANITIZE_LEAK 'get bloom filters for commit with no changes' ' git init && git commit --allow-empty -m "c0" && cat >expect <<-\EOF && diff --git a/t/t0110-urlmatch-normalization.sh b/t/t0110-urlmatch-normalization.sh index 4dc9fecf72..12d817fbd3 100755 --- a/t/t0110-urlmatch-normalization.sh +++ b/t/t0110-urlmatch-normalization.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='urlmatch URL normalization' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # The base name of the test url files diff --git a/t/t0212/parse_events.perl b/t/t0212/parse_events.perl index b6408560c0..30a9f51e9f 100644 --- a/t/t0212/parse_events.perl +++ b/t/t0212/parse_events.perl @@ -216,12 +216,19 @@ while (<>) { elsif ($event eq 'data') { my $cat = $line->{'category'}; - if ($cat eq 'test_category') { - - my $key = $line->{'key'}; - my $value = $line->{'value'}; - $processes->{$sid}->{'data'}->{$cat}->{$key} = $value; - } + my $key = $line->{'key'}; + my $value = $line->{'value'}; + $processes->{$sid}->{'data'}->{$cat}->{$key} = $value; + } + + elsif ($event eq 'data_json') { + # NEEDSWORK: Ignore due to + # compat/win32/trace2_win32_process_info.c, which should log a + # "cmd_ancestry" event instead. + } + + else { + push @{$processes->{$sid}->{$event}} => $line->{value}; } # This trace2 target does not emit 'printf' events. diff --git a/t/t1011-read-tree-sparse-checkout.sh b/t/t1011-read-tree-sparse-checkout.sh index 63a553d7b3..742f0fa909 100755 --- a/t/t1011-read-tree-sparse-checkout.sh +++ b/t/t1011-read-tree-sparse-checkout.sh @@ -11,6 +11,7 @@ test_description='sparse checkout tests A init.t ' +TEST_CREATE_REPO_NO_TEMPLATE=1 . ./test-lib.sh . "$TEST_DIRECTORY"/lib-read-tree.sh @@ -53,6 +54,7 @@ test_expect_success 'read-tree without .git/info/sparse-checkout' ' ' test_expect_success 'read-tree with .git/info/sparse-checkout but disabled' ' + mkdir .git/info && echo >.git/info/sparse-checkout && read_tree_u_must_succeed -m -u HEAD && git ls-files -t >result && diff --git a/t/t1051-large-conversion.sh b/t/t1051-large-conversion.sh index 042b0e4429..f6709c9f56 100755 --- a/t/t1051-large-conversion.sh +++ b/t/t1051-large-conversion.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='test conversion filters on large files' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh set_attr() { diff --git a/t/t1090-sparse-checkout-scope.sh b/t/t1090-sparse-checkout-scope.sh index d1833c0f31..3a14218b24 100755 --- a/t/t1090-sparse-checkout-scope.sh +++ b/t/t1090-sparse-checkout-scope.sh @@ -5,6 +5,7 @@ test_description='sparse checkout scope tests' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_CREATE_REPO_NO_TEMPLATE=1 . ./test-lib.sh test_expect_success 'setup' ' @@ -25,6 +26,7 @@ test_expect_success 'create feature branch' ' test_expect_success 'perform sparse checkout of main' ' git config --local --bool core.sparsecheckout true && + mkdir .git/info && echo "!/*" >.git/info/sparse-checkout && echo "/a" >>.git/info/sparse-checkout && echo "/c" >>.git/info/sparse-checkout && @@ -73,7 +75,7 @@ test_expect_success 'skip-worktree on files outside sparse patterns' ' test_expect_success 'in partial clone, sparse checkout only fetches needed blobs' ' test_create_repo server && - git clone "file://$(pwd)/server" client && + git clone --template= "file://$(pwd)/server" client && test_config -C server uploadpack.allowfilter 1 && test_config -C server uploadpack.allowanysha1inwant 1 && @@ -85,6 +87,7 @@ test_expect_success 'in partial clone, sparse checkout only fetches needed blobs git -C server commit -m message && test_config -C client core.sparsecheckout 1 && + mkdir client/.git/info && echo "!/*" >client/.git/info/sparse-checkout && echo "/a" >>client/.git/info/sparse-checkout && git -C client fetch --filter=blob:none origin && diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh index f9f8c988bb..763c6cc684 100755 --- a/t/t1092-sparse-checkout-compatibility.sh +++ b/t/t1092-sparse-checkout-compatibility.sh @@ -1828,4 +1828,29 @@ test_expect_success 'checkout behaves oddly with df-conflict-2' ' test_cmp full-checkout-err sparse-index-err ' +test_expect_success 'mv directory from out-of-cone to in-cone' ' + init_repos && + + # <source> as a sparse directory (or SKIP_WORKTREE_DIR without enabling + # sparse index). + test_all_match git mv --sparse folder1 deep && + test_all_match git status --porcelain=v2 && + test_sparse_match git ls-files -t && + git -C sparse-checkout ls-files -t >actual && + grep -e "H deep/folder1/0/0/0" actual && + grep -e "H deep/folder1/0/1" actual && + grep -e "H deep/folder1/a" actual && + + test_all_match git reset --hard && + + # <source> as a directory deeper than sparse index boundary (where + # sparse index will expand). + test_sparse_match git mv --sparse folder1/0 deep && + test_sparse_match git status --porcelain=v2 && + test_sparse_match git ls-files -t && + git -C sparse-checkout ls-files -t >actual && + grep -e "H deep/0/0/0" actual && + grep -e "H deep/0/1" actual +' + test_done diff --git a/t/t1301-shared-repo.sh b/t/t1301-shared-repo.sh index 84bf1970d8..93a2f91f8a 100755 --- a/t/t1301-shared-repo.sh +++ b/t/t1301-shared-repo.sh @@ -48,7 +48,7 @@ done test_expect_success 'shared=all' ' mkdir sub && cd sub && - git init --shared=all && + git init --template= --shared=all && test 2 = $(git config core.sharedrepository) ' @@ -57,6 +57,7 @@ test_expect_success POSIXPERM 'update-server-info honors core.sharedRepository' git add a1 && test_tick && git commit -m a1 && + mkdir .git/info && umask 0277 && git update-server-info && actual="$(ls -l .git/info/refs)" && diff --git a/t/t1402-check-ref-format.sh b/t/t1402-check-ref-format.sh index cabc516ae9..5ed9d7318e 100755 --- a/t/t1402-check-ref-format.sh +++ b/t/t1402-check-ref-format.sh @@ -2,6 +2,7 @@ test_description='Test git check-ref-format' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh valid_ref() { diff --git a/t/t2018-checkout-branch.sh b/t/t2018-checkout-branch.sh index 52e51b0726..771c3c3c50 100755 --- a/t/t2018-checkout-branch.sh +++ b/t/t2018-checkout-branch.sh @@ -2,6 +2,7 @@ test_description='checkout' +TEST_CREATE_REPO_NO_TEMPLATE=1 . ./test-lib.sh # Arguments: [!] <branch> <oid> [<checkout options>] @@ -257,11 +258,12 @@ test_expect_success 'checkout -b to a new branch preserves mergeable changes des git checkout branch1-scratch && test_might_fail git branch -D branch3 && git config core.sparseCheckout false && - rm .git/info/sparse-checkout" && + rm -rf .git/info" && test_commit file2 && echo stuff >>file1 && + mkdir .git/info && echo file2 >.git/info/sparse-checkout && git config core.sparseCheckout true && diff --git a/t/t2030-unresolve-info.sh b/t/t2030-unresolve-info.sh index f691e6d903..2d8c70b03a 100755 --- a/t/t2030-unresolve-info.sh +++ b/t/t2030-unresolve-info.sh @@ -194,4 +194,75 @@ test_expect_success 'rerere forget (add-add conflict)' ' test_i18ngrep "no remembered" actual ' +test_expect_success 'resolve-undo keeps blobs from gc' ' + git checkout -f main && + + # First make sure we do not have any cruft left in the object store + git repack -a -d && + git prune --expire=now && + git prune-packed && + git gc --prune=now && + git fsck --unreachable >cruft && + test_must_be_empty cruft && + + # Now add three otherwise unreferenced blob objects to the index + git reset --hard && + B1=$(echo "resolve undo test data 1" | git hash-object -w --stdin) && + B2=$(echo "resolve undo test data 2" | git hash-object -w --stdin) && + B3=$(echo "resolve undo test data 3" | git hash-object -w --stdin) && + git update-index --add --index-info <<-EOF && + 100644 $B1 1 frotz + 100644 $B2 2 frotz + 100644 $B3 3 frotz + EOF + + # These three blob objects are reachable (only) from the index + git fsck --unreachable >cruft && + test_must_be_empty cruft && + # and they should be protected from GC + git gc --prune=now && + git cat-file -e $B1 && + git cat-file -e $B2 && + git cat-file -e $B3 && + + # Now resolve the conflicted path + B0=$(echo "resolve undo test data 0" | git hash-object -w --stdin) && + git update-index --add --cacheinfo 100644,$B0,frotz && + + # These three blob objects are now reachable only from the resolve-undo + git fsck --unreachable >cruft && + test_must_be_empty cruft && + + # and they should survive GC + git gc --prune=now && + git cat-file -e $B0 && + git cat-file -e $B1 && + git cat-file -e $B2 && + git cat-file -e $B3 && + + # Now we switch away, which nukes resolve-undo, and + # blobs B0..B3 would become dangling. fsck should + # notice that they are now unreachable. + git checkout -f side && + git fsck --unreachable >cruft && + sort cruft >actual && + sort <<-EOF >expect && + unreachable blob $B0 + unreachable blob $B1 + unreachable blob $B2 + unreachable blob $B3 + EOF + test_cmp expect actual && + + # And they should go away when gc runs. + git gc --prune=now && + git fsck --unreachable >cruft && + test_must_be_empty cruft && + + test_must_fail git cat-file -e $B0 && + test_must_fail git cat-file -e $B1 && + test_must_fail git cat-file -e $B2 && + test_must_fail git cat-file -e $B3 +' + test_done diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh index 2f564d533d..f3242fef6b 100755 --- a/t/t2400-worktree-add.sh +++ b/t/t2400-worktree-add.sh @@ -5,6 +5,7 @@ test_description='test git worktree add' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_CREATE_REPO_NO_TEMPLATE=1 . ./test-lib.sh . "$TEST_DIRECTORY"/lib-rebase.sh @@ -229,6 +230,7 @@ test_expect_success 'checkout with grafts' ' SHA1=$(git rev-parse HEAD) && test_commit def && test_commit xyz && + mkdir .git/info && echo "$(git rev-parse HEAD) $SHA1" >.git/info/grafts && cat >expected <<-\EOF && xyz @@ -559,6 +561,8 @@ test_expect_success 'git worktree --no-guess-remote option overrides config' ' ' post_checkout_hook () { + test_when_finished "rm -rf .git/hooks" && + mkdir .git/hooks && test_hook -C "$1" post-checkout <<-\EOF { echo $* diff --git a/t/t2407-worktree-heads.sh b/t/t2407-worktree-heads.sh new file mode 100755 index 0000000000..b6be42f74a --- /dev/null +++ b/t/t2407-worktree-heads.sh @@ -0,0 +1,129 @@ +#!/bin/sh + +test_description='test operations trying to overwrite refs at worktree HEAD' + +TEST_PASSES_SANITIZE_LEAK=true +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit init && + git branch -f fake-1 && + git branch -f fake-2 && + + for i in 1 2 3 4 + do + test_commit $i && + git branch wt-$i && + git worktree add wt-$i wt-$i || return 1 + done && + + # Create a server that updates each branch by one commit + git init server && + test_commit -C server initial && + git remote add server ./server && + for i in 1 2 3 4 + do + git -C server checkout -b wt-$i && + test_commit -C server A-$i || return 1 + done && + for i in 1 2 + do + git -C server checkout -b fake-$i && + test_commit -C server f-$i || return 1 + done +' + +test_expect_success 'refuse to overwrite: checked out in worktree' ' + for i in 1 2 3 4 + do + test_must_fail git branch -f wt-$i HEAD 2>err + grep "cannot force update the branch" err && + + test_must_fail git branch -D wt-$i 2>err + grep "Cannot delete branch" err || return 1 + done +' + +test_expect_success 'refuse to overwrite: worktree in bisect' ' + test_when_finished rm -rf .git/worktrees/wt-*/BISECT_* && + + touch .git/worktrees/wt-4/BISECT_LOG && + echo refs/heads/fake-2 >.git/worktrees/wt-4/BISECT_START && + + test_must_fail git branch -f fake-2 HEAD 2>err && + grep "cannot force update the branch '\''fake-2'\'' checked out at.*wt-4" err +' + +test_expect_success 'refuse to overwrite: worktree in rebase' ' + test_when_finished rm -rf .git/worktrees/wt-*/rebase-merge && + + mkdir -p .git/worktrees/wt-3/rebase-merge && + touch .git/worktrees/wt-3/rebase-merge/interactive && + echo refs/heads/fake-1 >.git/worktrees/wt-3/rebase-merge/head-name && + echo refs/heads/fake-2 >.git/worktrees/wt-3/rebase-merge/onto && + + test_must_fail git branch -f fake-1 HEAD 2>err && + grep "cannot force update the branch '\''fake-1'\'' checked out at.*wt-3" err +' + +test_expect_success !SANITIZE_LEAK 'refuse to fetch over ref: checked out' ' + test_must_fail git fetch server +refs/heads/wt-3:refs/heads/wt-3 2>err && + grep "refusing to fetch into branch '\''refs/heads/wt-3'\''" err && + + # General fetch into refs/heads/ will fail on first ref, + # so use a generic error message check. + test_must_fail git fetch server +refs/heads/*:refs/heads/* 2>err && + grep "refusing to fetch into branch" err +' + +test_expect_success !SANITIZE_LEAK 'refuse to fetch over ref: worktree in bisect' ' + test_when_finished rm -rf .git/worktrees/wt-*/BISECT_* && + + touch .git/worktrees/wt-4/BISECT_LOG && + echo refs/heads/fake-2 >.git/worktrees/wt-4/BISECT_START && + + test_must_fail git fetch server +refs/heads/fake-2:refs/heads/fake-2 2>err && + grep "refusing to fetch into branch" err +' + +test_expect_success !SANITIZE_LEAK 'refuse to fetch over ref: worktree in rebase' ' + test_when_finished rm -rf .git/worktrees/wt-*/rebase-merge && + + mkdir -p .git/worktrees/wt-4/rebase-merge && + touch .git/worktrees/wt-4/rebase-merge/interactive && + echo refs/heads/fake-1 >.git/worktrees/wt-4/rebase-merge/head-name && + echo refs/heads/fake-2 >.git/worktrees/wt-4/rebase-merge/onto && + + test_must_fail git fetch server +refs/heads/fake-1:refs/heads/fake-1 2>err && + grep "refusing to fetch into branch" err +' + +test_expect_success 'refuse to overwrite when in error states' ' + test_when_finished rm -rf .git/worktrees/wt-*/rebase-merge && + test_when_finished rm -rf .git/worktrees/wt-*/BISECT_* && + + # Both branches are currently under rebase. + mkdir -p .git/worktrees/wt-3/rebase-merge && + touch .git/worktrees/wt-3/rebase-merge/interactive && + echo refs/heads/fake-1 >.git/worktrees/wt-3/rebase-merge/head-name && + echo refs/heads/fake-2 >.git/worktrees/wt-3/rebase-merge/onto && + mkdir -p .git/worktrees/wt-4/rebase-merge && + touch .git/worktrees/wt-4/rebase-merge/interactive && + echo refs/heads/fake-2 >.git/worktrees/wt-4/rebase-merge/head-name && + echo refs/heads/fake-1 >.git/worktrees/wt-4/rebase-merge/onto && + + # Both branches are currently under bisect. + touch .git/worktrees/wt-4/BISECT_LOG && + echo refs/heads/fake-2 >.git/worktrees/wt-4/BISECT_START && + touch .git/worktrees/wt-1/BISECT_LOG && + echo refs/heads/fake-1 >.git/worktrees/wt-1/BISECT_START && + + for i in 1 2 + do + test_must_fail git branch -f fake-$i HEAD 2>err && + grep "cannot force update the branch '\''fake-$i'\'' checked out at" err || + return 1 + done +' + +test_done diff --git a/t/t3304-notes-mixed.sh b/t/t3304-notes-mixed.sh index 03dfcd3954..2c3a245266 100755 --- a/t/t3304-notes-mixed.sh +++ b/t/t3304-notes-mixed.sh @@ -5,6 +5,7 @@ test_description='Test notes trees that also contain non-notes' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh number_of_commits=100 diff --git a/t/t3426-rebase-submodule.sh b/t/t3426-rebase-submodule.sh index 0ad3a07bf4..7a9f1127a4 100755 --- a/t/t3426-rebase-submodule.sh +++ b/t/t3426-rebase-submodule.sh @@ -35,6 +35,7 @@ git_rebase_interactive () { ls -1pR * >>actual && test_cmp expect actual && set_fake_editor && + mkdir .git/info && echo "fake-editor.sh" >.git/info/exclude && may_only_be_test_must_fail "$2" && $2 git rebase -i "$1" diff --git a/t/t3507-cherry-pick-conflict.sh b/t/t3507-cherry-pick-conflict.sh index 979e843c65..f32799e046 100755 --- a/t/t3507-cherry-pick-conflict.sh +++ b/t/t3507-cherry-pick-conflict.sh @@ -12,6 +12,7 @@ test_description='test cherry-pick and revert with conflicts GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_CREATE_REPO_NO_TEMPLATE=1 . ./test-lib.sh pristine_detach () { @@ -558,6 +559,7 @@ test_expect_success 'cherry-pick preserves sparse-checkout' ' echo \"/*\" >.git/info/sparse-checkout git read-tree --reset -u HEAD rm .git/info/sparse-checkout" && + mkdir .git/info && echo /unrelated >.git/info/sparse-checkout && git read-tree --reset -u HEAD && test_must_fail git cherry-pick -Xours picked>actual && diff --git a/t/t4044-diff-index-unique-abbrev.sh b/t/t4044-diff-index-unique-abbrev.sh index 4701796d10..29e49d2290 100755 --- a/t/t4044-diff-index-unique-abbrev.sh +++ b/t/t4044-diff-index-unique-abbrev.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='test unique sha1 abbreviation on "index from..to" line' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' diff --git a/t/t4140-apply-ita.sh b/t/t4140-apply-ita.sh index c614eaf04c..b375aca0d7 100755 --- a/t/t4140-apply-ita.sh +++ b/t/t4140-apply-ita.sh @@ -2,6 +2,7 @@ test_description='git apply of i-t-a file' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup ' diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh index 9f8c76dffb..7025cfdae5 100755 --- a/t/t4200-rerere.sh +++ b/t/t4200-rerere.sh @@ -365,9 +365,6 @@ test_expect_success 'set up an unresolved merge' ' test_might_fail git config --unset rerere.autoupdate && git reset --hard && git checkout version2 && - fifth=$(git rev-parse fifth) && - echo "$fifth branch fifth of ." | - git fmt-merge-msg >msg && ancestor=$(git merge-base version2 fifth) && test_must_fail git merge-recursive "$ancestor" -- HEAD fifth && diff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh new file mode 100755 index 0000000000..f091259a55 --- /dev/null +++ b/t/t4301-merge-tree-write-tree.sh @@ -0,0 +1,240 @@ +#!/bin/sh + +test_description='git merge-tree --write-tree' + +. ./test-lib.sh + +# This test is ort-specific +if test "$GIT_TEST_MERGE_ALGORITHM" != "ort" +then + skip_all="GIT_TEST_MERGE_ALGORITHM != ort" + test_done +fi + +test_expect_success setup ' + test_write_lines 1 2 3 4 5 >numbers && + echo hello >greeting && + echo foo >whatever && + git add numbers greeting whatever && + test_tick && + git commit -m initial && + + git branch side1 && + git branch side2 && + git branch side3 && + + git checkout side1 && + test_write_lines 1 2 3 4 5 6 >numbers && + echo hi >greeting && + echo bar >whatever && + git add numbers greeting whatever && + test_tick && + git commit -m modify-stuff && + + git checkout side2 && + test_write_lines 0 1 2 3 4 5 >numbers && + echo yo >greeting && + git rm whatever && + mkdir whatever && + >whatever/empty && + git add numbers greeting whatever/empty && + test_tick && + git commit -m other-modifications && + + git checkout side3 && + git mv numbers sequence && + test_tick && + git commit -m rename-numbers && + + git switch --orphan unrelated && + >something-else && + git add something-else && + test_tick && + git commit -m first-commit +' + +test_expect_success 'Clean merge' ' + TREE_OID=$(git merge-tree --write-tree side1 side3) && + q_to_tab <<-EOF >expect && + 100644 blob $(git rev-parse side1:greeting)Qgreeting + 100644 blob $(git rev-parse side1:numbers)Qsequence + 100644 blob $(git rev-parse side1:whatever)Qwhatever + EOF + + git ls-tree $TREE_OID >actual && + test_cmp expect actual +' + +test_expect_success 'Content merge and a few conflicts' ' + git checkout side1^0 && + test_must_fail git merge side2 && + expected_tree=$(git rev-parse AUTO_MERGE) && + + # We will redo the merge, while we are still in a conflicted state! + git ls-files -u >conflicted-file-info && + test_when_finished "git reset --hard" && + + test_expect_code 1 git merge-tree --write-tree side1 side2 >RESULT && + actual_tree=$(head -n 1 RESULT) && + + # Due to differences of e.g. "HEAD" vs "side1", the results will not + # exactly match. Dig into individual files. + + # Numbers should have three-way merged cleanly + test_write_lines 0 1 2 3 4 5 6 >expect && + git show ${actual_tree}:numbers >actual && + test_cmp expect actual && + + # whatever and whatever~<branch> should have same HASHES + git rev-parse ${expected_tree}:whatever ${expected_tree}:whatever~HEAD >expect && + git rev-parse ${actual_tree}:whatever ${actual_tree}:whatever~side1 >actual && + test_cmp expect actual && + + # greeting should have a merge conflict + git show ${expected_tree}:greeting >tmp && + sed -e s/HEAD/side1/ tmp >expect && + git show ${actual_tree}:greeting >actual && + test_cmp expect actual +' + +test_expect_success 'Barf on misspelled option, with exit code other than 0 or 1' ' + # Mis-spell with single "s" instead of double "s" + test_expect_code 129 git merge-tree --write-tree --mesages FOOBAR side1 side2 2>expect && + + grep "error: unknown option.*mesages" expect +' + +test_expect_success 'Barf on too many arguments' ' + test_expect_code 129 git merge-tree --write-tree side1 side2 invalid 2>expect && + + grep "^usage: git merge-tree" expect +' + +anonymize_hash() { + sed -e "s/[0-9a-f]\{40,\}/HASH/g" "$@" +} + +test_expect_success 'test conflict notices and such' ' + test_expect_code 1 git merge-tree --write-tree --name-only side1 side2 >out && + anonymize_hash out >actual && + + # Expected results: + # "greeting" should merge with conflicts + # "numbers" should merge cleanly + # "whatever" has *both* a modify/delete and a file/directory conflict + cat <<-EOF >expect && + HASH + greeting + whatever~side1 + + Auto-merging greeting + CONFLICT (content): Merge conflict in greeting + Auto-merging numbers + CONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead. + CONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1. Version side1 of whatever~side1 left in tree. + EOF + + test_cmp expect actual +' + +for opt in $(git merge-tree --git-completion-helper-all) +do + if test $opt = "--trivial-merge" || test $opt = "--write-tree" + then + continue + fi + + test_expect_success "usage: --trivial-merge is incompatible with $opt" ' + test_expect_code 128 git merge-tree --trivial-merge $opt side1 side2 side3 + ' +done + +test_expect_success 'Just the conflicted files without the messages' ' + test_expect_code 1 git merge-tree --write-tree --no-messages --name-only side1 side2 >out && + anonymize_hash out >actual && + + test_write_lines HASH greeting whatever~side1 >expect && + + test_cmp expect actual +' + +test_expect_success 'Check conflicted oids and modes without messages' ' + test_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out && + anonymize_hash out >actual && + + # Compare the basic output format + q_to_tab >expect <<-\EOF && + HASH + 100644 HASH 1Qgreeting + 100644 HASH 2Qgreeting + 100644 HASH 3Qgreeting + 100644 HASH 1Qwhatever~side1 + 100644 HASH 2Qwhatever~side1 + EOF + + test_cmp expect actual && + + # Check the actual hashes against the `ls-files -u` output too + tail -n +2 out | sed -e s/side1/HEAD/ >actual && + test_cmp conflicted-file-info actual +' + +test_expect_success 'NUL terminated conflicted file "lines"' ' + git checkout -b tweak1 side1 && + test_write_lines zero 1 2 3 4 5 6 >numbers && + git add numbers && + git mv numbers "Αυτά μου φαίνονται κινÎζικα" && + git commit -m "Renamed numbers" && + + test_expect_code 1 git merge-tree --write-tree -z tweak1 side2 >out && + anonymize_hash out >actual && + printf "\\n" >>actual && + + # Expected results: + # "greeting" should merge with conflicts + # "whatever" has *both* a modify/delete and a file/directory conflict + # "Αυτά μου φαίνονται κινÎζικα" should have a conflict + echo HASH | lf_to_nul >expect && + + q_to_tab <<-EOF | lf_to_nul >>expect && + 100644 HASH 1Qgreeting + 100644 HASH 2Qgreeting + 100644 HASH 3Qgreeting + 100644 HASH 1Qwhatever~tweak1 + 100644 HASH 2Qwhatever~tweak1 + 100644 HASH 1QΑυτά μου φαίνονται κινÎζικα + 100644 HASH 2QΑυτά μου φαίνονται κινÎζικα + 100644 HASH 3QΑυτά μου φαίνονται κινÎζικα + + EOF + + q_to_nul <<-EOF >>expect && + 1QgreetingQAuto-mergingQAuto-merging greeting + Q1QgreetingQCONFLICT (contents)QCONFLICT (content): Merge conflict in greeting + Q2Qwhatever~tweak1QwhateverQCONFLICT (file/directory)QCONFLICT (file/directory): directory in the way of whatever from tweak1; moving it to whatever~tweak1 instead. + Q1Qwhatever~tweak1QCONFLICT (modify/delete)QCONFLICT (modify/delete): whatever~tweak1 deleted in side2 and modified in tweak1. Version tweak1 of whatever~tweak1 left in tree. + Q1QΑυτά μου φαίνονται κινÎζικαQAuto-mergingQAuto-merging Αυτά μου φαίνονται κινÎζικα + Q1QΑυτά μου φαίνονται κινÎζικαQCONFLICT (contents)QCONFLICT (content): Merge conflict in Αυτά μου φαίνονται κινÎζικα + Q + EOF + + test_cmp expect actual +' + +test_expect_success 'error out by default for unrelated histories' ' + test_expect_code 128 git merge-tree --write-tree side1 unrelated 2>error && + + grep "refusing to merge unrelated histories" error +' + +test_expect_success 'can override merge of unrelated histories' ' + git merge-tree --write-tree --allow-unrelated-histories side1 unrelated >tree && + TREE=$(cat tree) && + + git rev-parse side1:numbers side1:greeting side1:whatever unrelated:something-else >expect && + git rev-parse $TREE:numbers $TREE:greeting $TREE:whatever $TREE:something-else >actual && + + test_cmp expect actual +' + +test_done diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index 7f8d2ab0a7..eaa0b22ece 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -24,6 +24,7 @@ commit id embedding: ' +TEST_CREATE_REPO_NO_TEMPLATE=1 . ./test-lib.sh SUBSTFORMAT=%H%n @@ -143,6 +144,7 @@ test_expect_success 'populate workdir' ' test_expect_success \ 'add ignored file' \ 'echo ignore me >a/ignored && + mkdir .git/info && echo ignored export-ignore >.git/info/attributes' test_expect_success 'add files to repository' ' @@ -157,7 +159,8 @@ test_expect_success 'setup export-subst' ' ' test_expect_success 'create bare clone' ' - git clone --bare . bare.git && + git clone --template= --bare . bare.git && + mkdir bare.git/info && cp .git/info/attributes bare.git/info/attributes ' @@ -339,21 +342,21 @@ test_expect_success 'only enabled filters are available remotely' ' test_cmp_bin remote.bar config.bar ' -test_expect_success GZIP 'git archive --format=tgz' ' +test_expect_success 'git archive --format=tgz' ' git archive --format=tgz HEAD >j.tgz ' -test_expect_success GZIP 'git archive --format=tar.gz' ' +test_expect_success 'git archive --format=tar.gz' ' git archive --format=tar.gz HEAD >j1.tar.gz && test_cmp_bin j.tgz j1.tar.gz ' -test_expect_success GZIP 'infer tgz from .tgz filename' ' +test_expect_success 'infer tgz from .tgz filename' ' git archive --output=j2.tgz HEAD && test_cmp_bin j.tgz j2.tgz ' -test_expect_success GZIP 'infer tgz from .tar.gz filename' ' +test_expect_success 'infer tgz from .tar.gz filename' ' git archive --output=j3.tar.gz HEAD && test_cmp_bin j.tgz j3.tar.gz ' @@ -363,17 +366,33 @@ test_expect_success GZIP 'extract tgz file' ' test_cmp_bin b.tar j.tar ' -test_expect_success GZIP 'remote tar.gz is allowed by default' ' +test_expect_success 'remote tar.gz is allowed by default' ' git archive --remote=. --format=tar.gz HEAD >remote.tar.gz && test_cmp_bin j.tgz remote.tar.gz ' -test_expect_success GZIP 'remote tar.gz can be disabled' ' +test_expect_success 'remote tar.gz can be disabled' ' git config tar.tar.gz.remote false && test_must_fail git archive --remote=. --format=tar.gz HEAD \ >remote.tar.gz ' +test_expect_success GZIP 'git archive --format=tgz (external gzip)' ' + test_config tar.tgz.command "gzip -cn" && + git archive --format=tgz HEAD >external_gzip.tgz +' + +test_expect_success GZIP 'git archive --format=tar.gz (external gzip)' ' + test_config tar.tar.gz.command "gzip -cn" && + git archive --format=tar.gz HEAD >external_gzip.tar.gz && + test_cmp_bin external_gzip.tgz external_gzip.tar.gz +' + +test_expect_success GZIP 'extract tgz file (external gzip)' ' + gzip -d -c <external_gzip.tgz >external_gzip.tar && + test_cmp_bin b.tar external_gzip.tar +' + test_expect_success 'archive and :(glob)' ' git archive -v HEAD -- ":(glob)**/sh" >/dev/null 2>actual && cat >expect <<EOF && diff --git a/t/t5001-archive-attr.sh b/t/t5001-archive-attr.sh index 712ae52299..2f6eef5e37 100755 --- a/t/t5001-archive-attr.sh +++ b/t/t5001-archive-attr.sh @@ -2,6 +2,7 @@ test_description='git archive attribute tests' +TEST_CREATE_REPO_NO_TEMPLATE=1 . ./test-lib.sh SUBSTFORMAT='%H (%h)%n' @@ -20,6 +21,7 @@ extract_tar_to_dir () { test_expect_success 'setup' ' echo ignored >ignored && + mkdir .git/info && echo ignored export-ignore >>.git/info/attributes && git add ignored && @@ -46,7 +48,8 @@ test_expect_success 'setup' ' git commit -m. && - git clone --bare . bare && + git clone --template= --bare . bare && + mkdir bare/info && cp .git/info/attributes bare/info/attributes ' diff --git a/t/t5002-archive-attr-pattern.sh b/t/t5002-archive-attr-pattern.sh index a66b5ba27e..78ab75f1bc 100755 --- a/t/t5002-archive-attr-pattern.sh +++ b/t/t5002-archive-attr-pattern.sh @@ -3,6 +3,7 @@ test_description='git archive attribute pattern tests' TEST_PASSES_SANITIZE_LEAK=true +TEST_CREATE_REPO_NO_TEMPLATE=1 . ./test-lib.sh test_expect_exists() { @@ -15,6 +16,7 @@ test_expect_missing() { test_expect_success 'setup' ' echo ignored >ignored && + mkdir .git/info && echo ignored export-ignore >>.git/info/attributes && git add ignored && @@ -54,7 +56,8 @@ test_expect_success 'setup' ' git commit -m. && - git clone --bare . bare && + git clone --template= --bare . bare && + mkdir bare/info && cp .git/info/attributes bare/info/attributes ' diff --git a/t/t5003-archive-zip.sh b/t/t5003-archive-zip.sh index 3992d08158..fc499cdff0 100755 --- a/t/t5003-archive-zip.sh +++ b/t/t5003-archive-zip.sh @@ -2,6 +2,7 @@ test_description='git archive --format=zip test' +TEST_CREATE_REPO_NO_TEMPLATE=1 . ./test-lib.sh SUBSTFORMAT=%H%n @@ -121,6 +122,7 @@ test_expect_success 'prepare file list' ' test_expect_success \ 'add ignored file' \ 'echo ignore me >a/ignored && + mkdir .git/info && echo ignored export-ignore >.git/info/attributes' test_expect_success 'add files to repository' ' @@ -139,7 +141,8 @@ test_expect_success 'setup export-subst and diff attributes' ' ' test_expect_success 'create bare clone' ' - git clone --bare . bare.git && + git clone --template= --bare . bare.git && + mkdir bare.git/info && cp .git/info/attributes bare.git/info/attributes && # Recreate our changes to .git/config rather than just copying it, as # we do not want to clobber core.bare or other settings. diff --git a/t/t5303-pack-corruption-resilience.sh b/t/t5303-pack-corruption-resilience.sh index 41e6dc4dcf..2926e8dfc4 100755 --- a/t/t5303-pack-corruption-resilience.sh +++ b/t/t5303-pack-corruption-resilience.sh @@ -4,6 +4,8 @@ # test_description='resilience to pack corruptions with redundant objects' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # Note: the test objects are created with knowledge of their pack encoding diff --git a/t/t5308-pack-detect-duplicates.sh b/t/t5308-pack-detect-duplicates.sh index 693b2411c8..655cafa054 100755 --- a/t/t5308-pack-detect-duplicates.sh +++ b/t/t5308-pack-detect-duplicates.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='handling of duplicate objects in incoming packfiles' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-pack.sh diff --git a/t/t5309-pack-delta-cycles.sh b/t/t5309-pack-delta-cycles.sh index 55b787630f..4e910c5b9d 100755 --- a/t/t5309-pack-delta-cycles.sh +++ b/t/t5309-pack-delta-cycles.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='test index-pack handling of delta cycles in packfiles' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-pack.sh diff --git a/t/t5314-pack-cycle-detection.sh b/t/t5314-pack-cycle-detection.sh index 0aec8619e2..73a241743a 100755 --- a/t/t5314-pack-cycle-detection.sh +++ b/t/t5314-pack-cycle-detection.sh @@ -49,9 +49,9 @@ Then no matter which order we start looking at the packs in, we know that we will always find a delta for "file", because its lookup will always come immediately after the lookup for "dummy". ' -. ./test-lib.sh - +TEST_PASSES_SANITIZE_LEAK=true +. ./test-lib.sh # Create a pack containing the tree $1 and blob $1:file, with # the latter stored as a delta against $2:file. diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh index fbf0d64578..be0b5641ff 100755 --- a/t/t5318-commit-graph.sh +++ b/t/t5318-commit-graph.sh @@ -361,13 +361,14 @@ test_expect_success 'replace-objects invalidates commit-graph' ' test_expect_success 'commit grafts invalidate commit-graph' ' cd "$TRASH_DIRECTORY" && test_when_finished rm -rf graft && - git clone full graft && + git clone --template= full graft && ( cd graft && git commit-graph write --reachable && test_path_is_file .git/objects/info/commit-graph && H1=$(git rev-parse --verify HEAD~1) && H3=$(git rev-parse --verify HEAD~3) && + mkdir .git/info && echo "$H1 $H3" >.git/info/grafts && git -c core.commitGraph=false log >expect && git -c core.commitGraph=true log >actual && diff --git a/t/t5321-pack-large-objects.sh b/t/t5321-pack-large-objects.sh index 8a56d98a0e..70770fe274 100755 --- a/t/t5321-pack-large-objects.sh +++ b/t/t5321-pack-large-objects.sh @@ -6,6 +6,8 @@ test_description='git pack-object with "large" deltas ' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-pack.sh diff --git a/t/t5330-no-lazy-fetch-with-commit-graph.sh b/t/t5330-no-lazy-fetch-with-commit-graph.sh new file mode 100755 index 0000000000..2cc7fd7a47 --- /dev/null +++ b/t/t5330-no-lazy-fetch-with-commit-graph.sh @@ -0,0 +1,47 @@ +#!/bin/sh + +test_description='test for no lazy fetch with the commit-graph' + +. ./test-lib.sh + +test_expect_success 'setup: prepare a repository with a commit' ' + git init with-commit && + test_commit -C with-commit the-commit && + oid=$(git -C with-commit rev-parse HEAD) +' + +test_expect_success 'setup: prepare a repository with commit-graph contains the commit' ' + git init with-commit-graph && + echo "$(pwd)/with-commit/.git/objects" \ + >with-commit-graph/.git/objects/info/alternates && + # create a ref that points to the commit in alternates + git -C with-commit-graph update-ref refs/ref_to_the_commit "$oid" && + # prepare some other objects to commit-graph + test_commit -C with-commit-graph something && + git -c gc.writeCommitGraph=true -C with-commit-graph gc && + test_path_is_file with-commit-graph/.git/objects/info/commit-graph +' + +test_expect_success 'setup: change the alternates to what without the commit' ' + git init --bare without-commit && + git -C with-commit-graph cat-file -e $oid && + echo "$(pwd)/without-commit/objects" \ + >with-commit-graph/.git/objects/info/alternates && + test_must_fail git -C with-commit-graph cat-file -e $oid +' + +test_expect_success 'fetch any commit from promisor with the usage of the commit graph' ' + # setup promisor and prepare any commit to fetch + git -C with-commit-graph remote add origin "$(pwd)/with-commit" && + git -C with-commit-graph config remote.origin.promisor true && + git -C with-commit-graph config remote.origin.partialclonefilter blob:none && + test_commit -C with-commit any-commit && + anycommit=$(git -C with-commit rev-parse HEAD) && + GIT_TRACE="$(pwd)/trace.txt" \ + git -C with-commit-graph fetch origin $anycommit 2>err && + ! grep "fatal: promisor-remote: unable to fork off fetch subprocess" err && + grep "git fetch origin" trace.txt >actual && + test_line_count = 1 actual +' + +test_done diff --git a/t/t5351-unpack-large-objects.sh b/t/t5351-unpack-large-objects.sh new file mode 100755 index 0000000000..f785cb0617 --- /dev/null +++ b/t/t5351-unpack-large-objects.sh @@ -0,0 +1,96 @@ +#!/bin/sh +# +# Copyright (c) 2022 Han Xin +# + +test_description='git unpack-objects with large objects' + +. ./test-lib.sh + +prepare_dest () { + test_when_finished "rm -rf dest.git" && + git init --bare dest.git && + git -C dest.git config core.bigFileThreshold "$1" +} + +test_expect_success "create large objects (1.5 MB) and PACK" ' + test-tool genrandom foo 1500000 >big-blob && + test_commit --append foo big-blob && + test-tool genrandom bar 1500000 >big-blob && + test_commit --append bar big-blob && + PACK=$(echo HEAD | git pack-objects --revs pack) && + git verify-pack -v pack-$PACK.pack >out && + sed -n -e "s/^\([0-9a-f][0-9a-f]*\).*\(commit\|tree\|blob\).*/\1/p" \ + <out >obj-list +' + +test_expect_success 'set memory limitation to 1MB' ' + GIT_ALLOC_LIMIT=1m && + export GIT_ALLOC_LIMIT +' + +test_expect_success 'unpack-objects failed under memory limitation' ' + prepare_dest 2m && + test_must_fail git -C dest.git unpack-objects <pack-$PACK.pack 2>err && + grep "fatal: attempting to allocate" err +' + +test_expect_success 'unpack-objects works with memory limitation in dry-run mode' ' + prepare_dest 2m && + git -C dest.git unpack-objects -n <pack-$PACK.pack && + test_stdout_line_count = 0 find dest.git/objects -type f && + test_dir_is_empty dest.git/objects/pack +' + +test_expect_success 'unpack big object in stream' ' + prepare_dest 1m && + git -C dest.git unpack-objects <pack-$PACK.pack && + test_dir_is_empty dest.git/objects/pack +' + +check_fsync_events () { + local trace="$1" && + shift && + + cat >expect && + sed -n \ + -e '/^{"event":"data",.*"category":"fsync",/ { + s/.*"category":"fsync",//; + s/}$//; + p; + }' \ + <"$trace" >actual && + test_cmp expect actual +} + +BATCH_CONFIGURATION='-c core.fsync=loose-object -c core.fsyncmethod=batch' + +test_expect_success 'unpack big object in stream (core.fsyncmethod=batch)' ' + prepare_dest 1m && + GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \ + GIT_TEST_FSYNC=true \ + git -C dest.git $BATCH_CONFIGURATION unpack-objects <pack-$PACK.pack && + check_fsync_events trace2.txt <<-\EOF && + "key":"fsync/writeout-only","value":"6" + "key":"fsync/hardware-flush","value":"1" + EOF + + test_dir_is_empty dest.git/objects/pack && + git -C dest.git cat-file --batch-check="%(objectname)" <obj-list >current && + cmp obj-list current +' + +test_expect_success 'do not unpack existing large objects' ' + prepare_dest 1m && + git -C dest.git index-pack --stdin <pack-$PACK.pack && + git -C dest.git unpack-objects <pack-$PACK.pack && + + # The destination came up with the exact same pack... + DEST_PACK=$(echo dest.git/objects/pack/pack-*.pack) && + test_cmp pack-$PACK.pack $DEST_PACK && + + # ...and wrote no loose objects + test_stdout_line_count = 0 find dest.git/objects -type f ! -name "pack-*" +' + +test_done diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index fff14e13ed..6c7370f87f 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -302,6 +302,52 @@ test_expect_success 'show' ' ) ' +cat >expect <<EOF +* remote origin + Fetch URL: $(pwd)/one + Push URL: $(pwd)/one + HEAD branch: main + Remote branches: + main skipped + side tracked + Local branches configured for 'git pull': + ahead merges with remote main + main merges with remote main + Local refs configured for 'git push': + main pushes to main (local out of date) + main pushes to upstream (create) +EOF + +test_expect_success 'show with negative refspecs' ' + test_when_finished "git -C test config --unset-all --fixed-value remote.origin.fetch ^refs/heads/main" && + git -C test config --add remote.origin.fetch ^refs/heads/main && + git -C test remote show origin >output && + test_cmp expect output +' + +cat >expect <<EOF +* remote origin + Fetch URL: $(pwd)/one + Push URL: $(pwd)/one + HEAD branch: main + Remote branches: + main new (next fetch will store in remotes/origin) + side stale (use 'git remote prune' to remove) + Local branches configured for 'git pull': + ahead merges with remote main + main merges with remote main + Local refs configured for 'git push': + main pushes to main (local out of date) + main pushes to upstream (create) +EOF + +test_expect_failure 'show stale with negative refspecs' ' + test_when_finished "git -C test config --unset-all --fixed-value remote.origin.fetch ^refs/heads/side" && + git -C test config --add remote.origin.fetch ^refs/heads/side && + git -C test remote show origin >output && + test_cmp expect output +' + cat >test/expect <<EOF * remote origin Fetch URL: $(pwd)/one @@ -957,11 +1003,12 @@ test_expect_success 'migrate a remote from named file in $GIT_DIR/remotes' ' ' test_expect_success 'migrate a remote from named file in $GIT_DIR/branches' ' - git clone one six && + git clone --template= one six && origin_url=$(pwd)/one && ( cd six && git remote rm origin && + mkdir .git/branches && echo "$origin_url#main" >.git/branches/origin && git remote rename origin origin && test_path_is_missing .git/branches/origin && @@ -972,10 +1019,11 @@ test_expect_success 'migrate a remote from named file in $GIT_DIR/branches' ' ' test_expect_success 'migrate a remote from named file in $GIT_DIR/branches (2)' ' - git clone one seven && + git clone --template= one seven && ( cd seven && git remote rm origin && + mkdir .git/branches && echo "quux#foom" > .git/branches/origin && git remote rename origin origin && test_path_is_missing .git/branches/origin && diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 541adbb310..f3356f9ea8 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -18,6 +18,7 @@ This test checks the following functionality: GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_CREATE_REPO_NO_TEMPLATE=1 . ./test-lib.sh D=$(pwd) @@ -26,7 +27,8 @@ mk_empty () { repo_name="$1" test_when_finished "rm -rf \"$repo_name\"" && test_path_is_missing "$repo_name" && - git init "$repo_name" && + git init --template= "$repo_name" && + mkdir "$repo_name"/.git/hooks && git -C "$repo_name" config receive.denyCurrentBranch warn } @@ -78,7 +80,7 @@ mk_test_with_hooks() { mk_child() { test_when_finished "rm -rf \"$2\"" && - git clone "$1" "$2" + git clone --template= "$1" "$2" } check_push_result () { @@ -937,6 +939,7 @@ test_expect_success 'fetch with branches' ' mk_empty testrepo && git branch second $the_first_commit && git checkout second && + mkdir testrepo/.git/branches && echo ".." > testrepo/.git/branches/branch1 && ( cd testrepo && @@ -950,6 +953,7 @@ test_expect_success 'fetch with branches' ' test_expect_success 'fetch with branches containing #' ' mk_empty testrepo && + mkdir testrepo/.git/branches && echo "..#second" > testrepo/.git/branches/branch2 && ( cd testrepo && @@ -964,7 +968,11 @@ test_expect_success 'fetch with branches containing #' ' test_expect_success 'push with branches' ' mk_empty testrepo && git checkout second && + + test_when_finished "rm -rf .git/branches" && + mkdir .git/branches && echo "testrepo" > .git/branches/branch1 && + git push branch1 && ( cd testrepo && @@ -976,7 +984,11 @@ test_expect_success 'push with branches' ' test_expect_success 'push with branches containing #' ' mk_empty testrepo && + + test_when_finished "rm -rf .git/branches" && + mkdir .git/branches && echo "testrepo#branch3" > .git/branches/branch2 && + git push branch2 && ( cd testrepo && @@ -1865,4 +1877,26 @@ test_expect_success LIBCURL 'push warns or fails when using username:password' ' test_line_count = 1 warnings ' +test_expect_success 'push with config push.useBitmaps' ' + mk_test testrepo heads/main && + git checkout main && + test_unconfig push.useBitmaps && + GIT_TRACE2_EVENT="$PWD/default" \ + git push testrepo main:test && + test_subcommand git pack-objects --all-progress-implied --revs --stdout \ + --thin --delta-base-offset -q <default && + + test_config push.useBitmaps true && + GIT_TRACE2_EVENT="$PWD/true" \ + git push testrepo main:test2 && + test_subcommand git pack-objects --all-progress-implied --revs --stdout \ + --thin --delta-base-offset -q <true && + + test_config push.useBitmaps false && + GIT_TRACE2_EVENT="$PWD/false" \ + git push testrepo main:test3 && + test_subcommand git pack-objects --all-progress-implied --revs --stdout \ + --thin --delta-base-offset -q --no-use-bitmap-index <false +' + test_done diff --git a/t/t5524-pull-msg.sh b/t/t5524-pull-msg.sh index b2be3605f5..56716e29dd 100755 --- a/t/t5524-pull-msg.sh +++ b/t/t5524-pull-msg.sh @@ -2,6 +2,7 @@ test_description='git pull message generation' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh dollar='$Dollar' diff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh index 2f09ff4fac..fbad2d5ff5 100755 --- a/t/t5541-http-push-smart.sh +++ b/t/t5541-http-push-smart.sh @@ -80,6 +80,25 @@ test_expect_success 'push to remote repository (standard)' ' test $HEAD = $(git rev-parse --verify HEAD)) ' +test_expect_success 'push to remote repository (standard) with sending Accept-Language' ' + cat >exp <<-\EOF && + => Send header: Accept-Language: ko-KR, *;q=0.9 + => Send header: Accept-Language: ko-KR, *;q=0.9 + EOF + + cd "$ROOT_PATH"/test_repo_clone && + : >path_lang && + git add path_lang && + test_tick && + git commit -m path_lang && + HEAD=$(git rev-parse --verify HEAD) && + GIT_TRACE_CURL=true LANGUAGE="ko_KR.UTF-8" git push -v -v 2>err && + ! grep "Expect: 100-continue" err && + + grep "=> Send header: Accept-Language:" err >err.language && + test_cmp exp err.language +' + test_expect_success 'push already up-to-date' ' git push ' diff --git a/t/t5544-pack-objects-hook.sh b/t/t5544-pack-objects-hook.sh index dd5f44d986..54f54f8d2e 100755 --- a/t/t5544-pack-objects-hook.sh +++ b/t/t5544-pack-objects-hook.sh @@ -56,7 +56,12 @@ test_expect_success 'hook does not run from repo config' ' ! grep "hook running" stderr && test_path_is_missing .git/hook.args && test_path_is_missing .git/hook.stdin && - test_path_is_missing .git/hook.stdout + test_path_is_missing .git/hook.stdout && + + # check that global config is used instead + test_config_global uploadpack.packObjectsHook ./hook && + git clone --no-local . dst2.git 2>stderr && + grep "hook running" stderr ' test_expect_success 'hook works with partial clone' ' diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh index f0d9cd584d..d7cf85ffea 100755 --- a/t/t5550-http-fetch-dumb.sh +++ b/t/t5550-http-fetch-dumb.sh @@ -369,7 +369,7 @@ ja;q=0.95, zh;q=0.94, sv;q=0.93, pt;q=0.92, nb;q=0.91, *;q=0.90" \ ko_KR.EUC-KR:en_US.UTF-8:fr_CA:de.UTF-8@euro:sr@latin:ja:zh:sv:pt:nb ' -test_expect_success 'git client does not send an empty Accept-Language' ' +test_expect_success 'git client send an empty Accept-Language' ' GIT_TRACE_CURL=true LANGUAGE= git ls-remote "$HTTPD_URL/dumb/repo.git" 2>stderr && ! grep "^=> Send header: Accept-Language:" stderr ' @@ -422,7 +422,8 @@ test_expect_success 'set up evil alternates scheme' ' sha1=$(git -C "$victim" rev-parse HEAD) && evil=$HTTPD_DOCUMENT_ROOT_PATH/evil.git && - git init --bare "$evil" && + git init --template= --bare "$evil" && + mkdir "$evil/info" && # do this by hand to avoid object existence check printf "%s\\t%s\\n" $sha1 refs/heads/main >"$evil/info/refs" ' diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh index b9351a732f..245532df88 100755 --- a/t/t5551-http-fetch-smart.sh +++ b/t/t5551-http-fetch-smart.sh @@ -31,6 +31,7 @@ test_expect_success 'clone http repository' ' > GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 > Accept: */* > Accept-Encoding: ENCODINGS + > Accept-Language: ko-KR, *;q=0.9 > Pragma: no-cache < HTTP/1.1 200 OK < Pragma: no-cache @@ -40,13 +41,15 @@ test_expect_success 'clone http repository' ' > Accept-Encoding: ENCODINGS > Content-Type: application/x-git-upload-pack-request > Accept: application/x-git-upload-pack-result + > Accept-Language: ko-KR, *;q=0.9 > Content-Length: xxx < HTTP/1.1 200 OK < Pragma: no-cache < Cache-Control: no-cache, max-age=0, must-revalidate < Content-Type: application/x-git-upload-pack-result EOF - GIT_TRACE_CURL=true GIT_TEST_PROTOCOL_VERSION=0 \ + + GIT_TRACE_CURL=true GIT_TEST_PROTOCOL_VERSION=0 LANGUAGE="ko_KR.UTF-8" \ git clone --quiet $HTTPD_URL/smart/repo.git clone 2>err && test_cmp file clone/file && tr '\''\015'\'' Q <err | @@ -94,7 +97,10 @@ test_expect_success 'clone http repository' ' test_cmp exp actual.smudged && grep "Accept-Encoding:.*gzip" actual >actual.gzip && - test_line_count = 2 actual.gzip + test_line_count = 2 actual.gzip && + + grep "Accept-Language: ko-KR, *" actual >actual.language && + test_line_count = 2 actual.language fi ' diff --git a/t/t5605-clone-local.sh b/t/t5605-clone-local.sh index 21ab619283..38b850c10e 100755 --- a/t/t5605-clone-local.sh +++ b/t/t5605-clone-local.sh @@ -21,7 +21,9 @@ test_expect_success 'preparing origin repository' ' git bundle create b2.bundle main && mkdir dir && cp b1.bundle dir/b3 && - cp b1.bundle b4 + cp b1.bundle b4 && + git branch not-main main && + git bundle create b5.bundle not-main ' test_expect_success 'local clone without .git suffix' ' @@ -83,11 +85,19 @@ test_expect_success 'bundle clone from b4.bundle that does not exist' ' test_must_fail git clone b4.bundle bb ' -test_expect_success 'bundle clone with nonexistent HEAD' ' +test_expect_success 'bundle clone with nonexistent HEAD (match default)' ' git clone b2.bundle b2 && (cd b2 && git fetch && - test_must_fail git rev-parse --verify refs/heads/main) + git rev-parse --verify refs/heads/main) +' + +test_expect_success 'bundle clone with nonexistent HEAD (no match default)' ' + git clone b5.bundle b5 && + (cd b5 && + git fetch && + test_must_fail git rev-parse --verify refs/heads/main && + test_must_fail git rev-parse --verify refs/heads/not-main) ' test_expect_success 'clone empty repository' ' diff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh index 00ce9aec23..5d42a355a8 100755 --- a/t/t5702-protocol-v2.sh +++ b/t/t5702-protocol-v2.sh @@ -250,6 +250,65 @@ test_expect_success 'bare clone propagates empty default branch' ' grep "refs/heads/mydefaultbranch" file_empty_child.git/HEAD ' +test_expect_success 'clone propagates unborn HEAD from non-empty repo' ' + test_when_finished "rm -rf file_unborn_parent file_unborn_child" && + + git init file_unborn_parent && + ( + cd file_unborn_parent && + git checkout -b branchwithstuff && + test_commit --no-tag stuff && + git symbolic-ref HEAD refs/heads/mydefaultbranch + ) && + + GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \ + git -c init.defaultBranch=main -c protocol.version=2 \ + clone "file://$(pwd)/file_unborn_parent" \ + file_unborn_child 2>stderr && + grep "refs/heads/mydefaultbranch" file_unborn_child/.git/HEAD && + grep "warning: remote HEAD refers to nonexistent ref" stderr +' + +test_expect_success 'bare clone propagates unborn HEAD from non-empty repo' ' + test_when_finished "rm -rf file_unborn_parent file_unborn_child.git" && + + git init file_unborn_parent && + ( + cd file_unborn_parent && + git checkout -b branchwithstuff && + test_commit --no-tag stuff && + git symbolic-ref HEAD refs/heads/mydefaultbranch + ) && + + GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \ + git -c init.defaultBranch=main -c protocol.version=2 \ + clone --bare "file://$(pwd)/file_unborn_parent" \ + file_unborn_child.git 2>stderr && + grep "refs/heads/mydefaultbranch" file_unborn_child.git/HEAD && + ! grep "warning:" stderr +' + +test_expect_success 'defaulted HEAD uses remote branch if available' ' + test_when_finished "rm -rf file_unborn_parent file_unborn_child" && + + git init file_unborn_parent && + ( + cd file_unborn_parent && + git config lsrefs.unborn ignore && + git checkout -b branchwithstuff && + test_commit --no-tag stuff && + git symbolic-ref HEAD refs/heads/mydefaultbranch + ) && + + GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \ + git -c init.defaultBranch=branchwithstuff -c protocol.version=2 \ + clone "file://$(pwd)/file_unborn_parent" \ + file_unborn_child 2>stderr && + grep "refs/heads/branchwithstuff" file_unborn_child/.git/HEAD && + test_path_is_file file_unborn_child/stuff.t && + ! grep "warning:" stderr +' + test_expect_success 'fetch with file:// using protocol v2' ' test_when_finished "rm -f log" && diff --git a/t/t5812-proto-disable-http.sh b/t/t5812-proto-disable-http.sh index af8772fada..d8da5f58d1 100755 --- a/t/t5812-proto-disable-http.sh +++ b/t/t5812-proto-disable-http.sh @@ -16,7 +16,7 @@ test_expect_success 'create git-accessible repo' ' test_proto "smart http" http "$HTTPD_URL/smart/repo.git" -test_expect_success 'curl redirects respect whitelist' ' +test_expect_success 'http(s) transport respects GIT_ALLOW_PROTOCOL' ' test_must_fail env GIT_ALLOW_PROTOCOL=http:https \ GIT_SMART_HTTP=0 \ git clone "$HTTPD_URL/ftp-redir/repo.git" 2>stderr && diff --git a/t/t5815-submodule-protos.sh b/t/t5815-submodule-protos.sh index 06f55a1b8a..4d5956cc18 100755 --- a/t/t5815-submodule-protos.sh +++ b/t/t5815-submodule-protos.sh @@ -1,6 +1,6 @@ #!/bin/sh -test_description='test protocol whitelisting with submodules' +test_description='test protocol filtering with submodules' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-proto-disable.sh @@ -36,7 +36,7 @@ test_expect_success 'update of ext not allowed' ' test_must_fail git -C dst submodule update ext-module ' -test_expect_success 'user can override whitelist' ' +test_expect_success 'user can filter protocols with GIT_ALLOW_PROTOCOL' ' GIT_ALLOW_PROTOCOL=ext git -C dst submodule update ext-module ' diff --git a/t/t6001-rev-list-graft.sh b/t/t6001-rev-list-graft.sh index 7294147334..16635ecc33 100755 --- a/t/t6001-rev-list-graft.sh +++ b/t/t6001-rev-list-graft.sh @@ -99,6 +99,7 @@ do " test_expect_success 'with grafts' " + mkdir -p .git/info && echo '$B0 $A2' >.git/info/grafts && check $type $B2 -- $B2 $B1 $B0 $A2 $A1 $A0 " diff --git a/t/t6101-rev-parse-parents.sh b/t/t6101-rev-parse-parents.sh index a3a41c7a3e..d20723d627 100755 --- a/t/t6101-rev-parse-parents.sh +++ b/t/t6101-rev-parse-parents.sh @@ -9,6 +9,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME TEST_PASSES_SANITIZE_LEAK=true +TEST_CREATE_REPO_NO_TEMPLATE=1 . ./test-lib.sh test_cmp_rev_output () { @@ -26,6 +27,7 @@ test_expect_success 'setup' ' git merge -m next --allow-unrelated-histories start2 && test_commit final && + mkdir .git/info && test_seq 40 | while read i do diff --git a/t/t6403-merge-file.sh b/t/t6403-merge-file.sh index 2f421d967a..1a7082323d 100755 --- a/t/t6403-merge-file.sh +++ b/t/t6403-merge-file.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='RCS merge replacement: merge-file' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' diff --git a/t/t6417-merge-ours-theirs.sh b/t/t6417-merge-ours-theirs.sh index 62d1406119..482b73a998 100755 --- a/t/t6417-merge-ours-theirs.sh +++ b/t/t6417-merge-ours-theirs.sh @@ -4,6 +4,7 @@ test_description='Merge-recursive ours and theirs variants' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup ' diff --git a/t/t6422-merge-rename-corner-cases.sh b/t/t6422-merge-rename-corner-cases.sh index bf4ce3c63d..9b65768aed 100755 --- a/t/t6422-merge-rename-corner-cases.sh +++ b/t/t6422-merge-rename-corner-cases.sh @@ -6,6 +6,7 @@ test_description="recursive merge corner cases w/ renames but not criss-crosses" GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-merge.sh diff --git a/t/t6435-merge-sparse.sh b/t/t6435-merge-sparse.sh index 74562e1235..fde4aa3cd1 100755 --- a/t/t6435-merge-sparse.sh +++ b/t/t6435-merge-sparse.sh @@ -2,6 +2,7 @@ test_description='merge with sparse files' +TEST_CREATE_REPO_NO_TEMPLATE=1 . ./test-lib.sh # test_file $filename $content @@ -26,6 +27,7 @@ test_expect_success 'setup' ' git rm modify_delete && test_commit_this ours && git config core.sparseCheckout true && + mkdir .git/info && echo "/checked-out" >.git/info/sparse-checkout && git reset --hard && test_must_fail git merge theirs diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh index 178413c22f..c253bf759a 100755 --- a/t/t6437-submodule-merge.sh +++ b/t/t6437-submodule-merge.sh @@ -133,7 +133,7 @@ test_expect_success 'merging should conflict for non fast-forward' ' (cd merge-search && git checkout -b test-nonforward b && (cd sub && - git rev-parse sub-d > ../expect) && + git rev-parse --short sub-d > ../expect) && if test "$GIT_TEST_MERGE_ALGORITHM" = ort then test_must_fail git merge c >actual diff --git a/t/t7002-mv-sparse-checkout.sh b/t/t7002-mv-sparse-checkout.sh index f0f7cbfcdb..71fe29690f 100755 --- a/t/t7002-mv-sparse-checkout.sh +++ b/t/t7002-mv-sparse-checkout.sh @@ -4,6 +4,18 @@ test_description='git mv in sparse working trees' . ./test-lib.sh +setup_sparse_checkout () { + mkdir folder1 && + touch folder1/file1 && + git add folder1 && + git sparse-checkout set --cone sub +} + +cleanup_sparse_checkout () { + git sparse-checkout disable && + git reset --hard +} + test_expect_success 'setup' " mkdir -p sub/dir sub/dir2 && touch a b c sub/d sub/dir/e sub/dir2/e && @@ -196,6 +208,7 @@ test_expect_success 'can move files to non-sparse dir' ' ' test_expect_success 'refuse to move file to non-skip-worktree sparse path' ' + test_when_finished "cleanup_sparse_checkout" && git reset --hard && git sparse-checkout init --no-cone && git sparse-checkout set a !/x y/ !x/y/z && @@ -206,4 +219,75 @@ test_expect_success 'refuse to move file to non-skip-worktree sparse path' ' test_cmp expect stderr ' +test_expect_success 'refuse to move out-of-cone directory without --sparse' ' + test_when_finished "cleanup_sparse_checkout" && + setup_sparse_checkout && + + test_must_fail git mv folder1 sub 2>stderr && + cat sparse_error_header >expect && + echo folder1/file1 >>expect && + cat sparse_hint >>expect && + test_cmp expect stderr +' + +test_expect_success 'can move out-of-cone directory with --sparse' ' + test_when_finished "cleanup_sparse_checkout" && + setup_sparse_checkout && + + git mv --sparse folder1 sub 2>stderr && + test_must_be_empty stderr && + + test_path_is_dir sub/folder1 && + test_path_is_file sub/folder1/file1 +' + +test_expect_success 'refuse to move out-of-cone file without --sparse' ' + test_when_finished "cleanup_sparse_checkout" && + setup_sparse_checkout && + + test_must_fail git mv folder1/file1 sub 2>stderr && + cat sparse_error_header >expect && + echo folder1/file1 >>expect && + cat sparse_hint >>expect && + test_cmp expect stderr +' + +test_expect_success 'can move out-of-cone file with --sparse' ' + test_when_finished "cleanup_sparse_checkout" && + setup_sparse_checkout && + + git mv --sparse folder1/file1 sub 2>stderr && + test_must_be_empty stderr && + + test_path_is_file sub/file1 +' + +test_expect_success 'refuse to move sparse file to existing destination' ' + test_when_finished "cleanup_sparse_checkout" && + mkdir folder1 && + touch folder1/file1 && + touch sub/file1 && + git add folder1 sub/file1 && + git sparse-checkout set --cone sub && + + test_must_fail git mv --sparse folder1/file1 sub 2>stderr && + echo "fatal: destination exists, source=folder1/file1, destination=sub/file1" >expect && + test_cmp expect stderr +' + +test_expect_success 'move sparse file to existing destination with --force and --sparse' ' + test_when_finished "cleanup_sparse_checkout" && + mkdir folder1 && + touch folder1/file1 && + touch sub/file1 && + echo "overwrite" >folder1/file1 && + git add folder1 sub/file1 && + git sparse-checkout set --cone sub && + + git mv --sparse --force folder1/file1 sub 2>stderr && + test_must_be_empty stderr && + echo "overwrite" >expect && + test_cmp expect sub/file1 +' + test_done diff --git a/t/t7063-status-untracked-cache.sh b/t/t7063-status-untracked-cache.sh index 9936cc329e..f5050b75b2 100755 --- a/t/t7063-status-untracked-cache.sh +++ b/t/t7063-status-untracked-cache.sh @@ -86,7 +86,7 @@ test_expect_success 'core.untrackedCache is unset' ' ' test_expect_success 'setup' ' - git init worktree && + git init --template= worktree && cd worktree && mkdir done dtwo dthree && touch one two three done/one dtwo/two dthree/three && @@ -94,6 +94,7 @@ test_expect_success 'setup' ' test-tool chmtime =-300 done dtwo dthree && test-tool chmtime =-300 . && git add one two done/one && + mkdir .git/info && : >.git/info/exclude && git update-index --untracked-cache && test_oid_cache <<-EOF diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh index 43f779d751..6cc07460dd 100755 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh @@ -1074,7 +1074,7 @@ test_expect_success 'submodule update --quiet passes quietness to merge/rebase' git submodule update --rebase --quiet >out 2>err && test_must_be_empty out && test_must_be_empty err && - git submodule update --rebase -v >out 2>err && + git submodule update --rebase >out 2>err && test_file_not_empty out && test_must_be_empty err ) @@ -1116,4 +1116,66 @@ test_expect_success 'submodule update --filter sets partial clone settings' ' test_cmp_config -C super-filter/submodule blob:none remote.origin.partialclonefilter ' +# NEEDSWORK: Clean up the tests so that we can reuse the test setup. +# Don't reuse the existing repos because the earlier tests have +# intentionally disruptive configurations. +test_expect_success 'setup clean recursive superproject' ' + git init bottom && + test_commit -C bottom "bottom" && + git init middle && + git -C middle submodule add ../bottom bottom && + git -C middle commit -m "middle" && + git init top && + git -C top submodule add ../middle middle && + git -C top commit -m "top" && + git clone --recurse-submodules top top-clean +' + +test_expect_success 'submodule update should skip unmerged submodules' ' + test_when_finished "rm -fr top-cloned" && + cp -r top-clean top-cloned && + + # Create an upstream commit in each repo, starting with bottom + test_commit -C bottom upstream_commit && + # Create middle commit + git -C middle/bottom fetch && + git -C middle/bottom checkout -f FETCH_HEAD && + git -C middle add bottom && + git -C middle commit -m "upstream_commit" && + # Create top commit + git -C top/middle fetch && + git -C top/middle checkout -f FETCH_HEAD && + git -C top add middle && + git -C top commit -m "upstream_commit" && + + # Create a downstream conflict + test_commit -C top-cloned/middle/bottom downstream_commit && + git -C top-cloned/middle add bottom && + git -C top-cloned/middle commit -m "downstream_commit" && + git -C top-cloned/middle fetch --recurse-submodules origin && + test_must_fail git -C top-cloned/middle merge origin/main && + + # Make the update of "middle" a no-op, otherwise we error out + # because of its unmerged state + test_config -C top-cloned submodule.middle.update !true && + git -C top-cloned submodule update --recursive 2>actual.err && + cat >expect.err <<-\EOF && + Skipping unmerged submodule middle/bottom + EOF + test_cmp expect.err actual.err +' + +test_expect_success 'submodule update --recursive skip submodules with strategy=none' ' + test_when_finished "rm -fr top-cloned" && + cp -r top-clean top-cloned && + + test_commit -C top-cloned/middle/bottom downstream_commit && + git -C top-cloned/middle config submodule.bottom.update none && + git -C top-cloned submodule update --recursive 2>actual.err && + cat >expect.err <<-\EOF && + Skipping submodule '\''middle/bottom'\'' + EOF + test_cmp expect.err actual.err +' + test_done diff --git a/t/t7418-submodule-sparse-gitmodules.sh b/t/t7418-submodule-sparse-gitmodules.sh index f87e524d6d..57d7ab3ece 100755 --- a/t/t7418-submodule-sparse-gitmodules.sh +++ b/t/t7418-submodule-sparse-gitmodules.sh @@ -31,8 +31,9 @@ test_expect_success 'sparse checkout setup which hides .gitmodules' ' test_tick && git commit -m "Add submodule" ) && - git clone upstream super && + git clone --template= upstream super && (cd super && + mkdir .git/info && cat >.git/info/sparse-checkout <<-\EOF && /* !/.gitmodules diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh index 6935601171..0f937990a0 100755 --- a/t/t7810-grep.sh +++ b/t/t7810-grep.sh @@ -77,6 +77,7 @@ test_expect_success setup ' # Say hello. function hello() { echo "Hello world." + echo "Hello again." } # hello # Still a no-op. @@ -595,6 +596,92 @@ test_expect_success 'grep --files-without-match --quiet' ' test_must_be_empty actual ' +test_expect_success 'grep --max-count 0 (must exit with non-zero)' ' + test_must_fail git grep --max-count 0 foo >actual && + test_must_be_empty actual +' + +test_expect_success 'grep --max-count 3' ' + cat >expected <<-EOF && + file:foo mmap bar + file:foo_mmap bar + file:foo_mmap bar mmap + EOF + git grep --max-count 3 foo >actual && + test_cmp expected actual +' + +test_expect_success 'grep --max-count -1 (no limit)' ' + cat >expected <<-EOF && + file:foo mmap bar + file:foo_mmap bar + file:foo_mmap bar mmap + file:foo mmap bar_mmap + file:foo_mmap bar mmap baz + EOF + git grep --max-count -1 foo >actual && + test_cmp expected actual +' + +test_expect_success 'grep --max-count 1 --context 2' ' + cat >expected <<-EOF && + file-foo mmap bar + file:foo_mmap bar + file-foo_mmap bar mmap + EOF + git grep --max-count 1 --context 1 foo_mmap >actual && + test_cmp expected actual +' + +test_expect_success 'grep --max-count 1 --show-function' ' + cat >expected <<-EOF && + hello.ps1=function hello() { + hello.ps1: echo "Hello world." + EOF + git grep --max-count 1 --show-function Hello hello.ps1 >actual && + test_cmp expected actual +' + +test_expect_success 'grep --max-count 2 --show-function' ' + cat >expected <<-EOF && + hello.ps1=function hello() { + hello.ps1: echo "Hello world." + hello.ps1: echo "Hello again." + EOF + git grep --max-count 2 --show-function Hello hello.ps1 >actual && + test_cmp expected actual +' + +test_expect_success 'grep --max-count 1 --count' ' + cat >expected <<-EOF && + hello.ps1:1 + EOF + git grep --max-count 1 --count Hello hello.ps1 >actual && + test_cmp expected actual +' + +test_expect_success 'grep --max-count 1 (multiple files)' ' + cat >expected <<-EOF && + hello.c:#include <stdio.h> + hello.ps1:# No-op. + EOF + git grep --max-count 1 -e o -- hello.\* >actual && + test_cmp expected actual +' + +test_expect_success 'grep --max-count 1 --context 1 (multiple files)' ' + cat >expected <<-EOF && + hello.c-#include <assert.h> + hello.c:#include <stdio.h> + hello.c- + -- + hello.ps1:# No-op. + hello.ps1-function dummy() {} + EOF + git grep --max-count 1 --context 1 -e o -- hello.\* >actual && + test_cmp expected actual +' + cat >expected <<EOF file:foo mmap bar_mmap EOF diff --git a/t/t7812-grep-icase-non-ascii.sh b/t/t7812-grep-icase-non-ascii.sh index ac7be54714..31c66b63c2 100755 --- a/t/t7812-grep-icase-non-ascii.sh +++ b/t/t7812-grep-icase-non-ascii.sh @@ -2,6 +2,7 @@ test_description='grep icase on non-English locales' +TEST_PASSES_SANITIZE_LEAK=true . ./lib-gettext.sh doalarm () { diff --git a/t/t7814-grep-recurse-submodules.sh b/t/t7814-grep-recurse-submodules.sh index a4476dc492..3ad80526c4 100755 --- a/t/t7814-grep-recurse-submodules.sh +++ b/t/t7814-grep-recurse-submodules.sh @@ -6,6 +6,7 @@ This test verifies the recurse-submodules feature correctly greps across submodules. ' +TEST_CREATE_REPO_NO_TEMPLATE=1 . ./test-lib.sh GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=1 @@ -471,8 +472,10 @@ test_expect_failure 'grep --textconv: superproject .gitattributes (from index) d test_expect_failure 'grep --textconv: superproject .git/info/attributes does not affect submodules' ' reset_and_clean && test_config_global diff.d2x.textconv "sed -e \"s/d/x/\"" && - super_attr="$(git rev-parse --git-path info/attributes)" && + super_info="$(git rev-parse --git-path info)" && + super_attr="$super_info/attributes" && test_when_finished "rm -f \"$super_attr\"" && + mkdir "$super_info" && echo "a diff=d2x" >"$super_attr" && cat >expect <<-\EOF && @@ -516,7 +519,8 @@ test_expect_failure 'grep --textconv correctly reads submodule .git/info/attribu reset_and_clean && test_config_global diff.d2x.textconv "sed -e \"s/d/x/\"" && - submodule_attr="$(git -C submodule rev-parse --path-format=absolute --git-path info/attributes)" && + submodule_info="$(git -C submodule rev-parse --path-format=absolute --git-path info)" && + submodule_attr="$submodule_info/attributes" && test_when_finished "rm -f \"$submodule_attr\"" && echo "a diff=d2x" >"$submodule_attr" && diff --git a/t/t8001-annotate.sh b/t/t8001-annotate.sh index a536a621b2..d7167f5539 100755 --- a/t/t8001-annotate.sh +++ b/t/t8001-annotate.sh @@ -4,6 +4,7 @@ test_description='git annotate' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_CREATE_REPO_NO_TEMPLATE=1 . ./test-lib.sh PROG='git annotate' diff --git a/t/t8002-blame.sh b/t/t8002-blame.sh index ee4fdd8f18..0147de304b 100755 --- a/t/t8002-blame.sh +++ b/t/t8002-blame.sh @@ -4,6 +4,7 @@ test_description='git blame' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_CREATE_REPO_NO_TEMPLATE=1 . ./test-lib.sh PROG='git blame -c' diff --git a/t/t8007-cat-file-textconv.sh b/t/t8007-cat-file-textconv.sh index b067983ba1..c8266f17f1 100755 --- a/t/t8007-cat-file-textconv.sh +++ b/t/t8007-cat-file-textconv.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='git cat-file textconv support' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh cat >helper <<'EOF' diff --git a/t/t8010-cat-file-filters.sh b/t/t8010-cat-file-filters.sh index 31de4b64dc..ca04242ca0 100755 --- a/t/t8010-cat-file-filters.sh +++ b/t/t8010-cat-file-filters.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='git cat-file filters support' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup ' ' diff --git a/t/t8012-blame-colors.sh b/t/t8012-blame-colors.sh index 90c75dbb28..c3a5f6d01f 100755 --- a/t/t8012-blame-colors.sh +++ b/t/t8012-blame-colors.sh @@ -4,6 +4,7 @@ test_description='colored git blame' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_CREATE_REPO_NO_TEMPLATE=1 . ./test-lib.sh PROG='git blame -c' diff --git a/t/t9101-git-svn-props.sh b/t/t9101-git-svn-props.sh index d043e80fc3..52046e60d5 100755 --- a/t/t9101-git-svn-props.sh +++ b/t/t9101-git-svn-props.sh @@ -5,7 +5,6 @@ test_description='git svn property tests' -TEST_FAILS_SANITIZE_LEAK=true . ./lib-git-svn.sh mkdir import diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 5cf2ef4b8b..85d735861f 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -5,7 +5,6 @@ test_description='git svn fetching' -TEST_FAILS_SANITIZE_LEAK=true . ./lib-git-svn.sh test_expect_success 'initialize repo' ' diff --git a/t/t9132-git-svn-broken-symlink.sh b/t/t9132-git-svn-broken-symlink.sh index 4d8d0584b7..aeceffaf7b 100755 --- a/t/t9132-git-svn-broken-symlink.sh +++ b/t/t9132-git-svn-broken-symlink.sh @@ -2,7 +2,6 @@ test_description='test that git handles an svn repository with empty symlinks' -TEST_FAILS_SANITIZE_LEAK=true . ./lib-git-svn.sh test_expect_success 'load svn dumpfile' ' svnadmin load "$rawsvnrepo" <<EOF diff --git a/t/t9301-fast-import-notes.sh b/t/t9301-fast-import-notes.sh index 1ae4d7c0d3..58413221e6 100755 --- a/t/t9301-fast-import-notes.sh +++ b/t/t9301-fast-import-notes.sh @@ -7,6 +7,7 @@ test_description='test git fast-import of notes objects' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh index 210ddf09e3..379b19f2f8 100755 --- a/t/t9400-git-cvsserver-server.sh +++ b/t/t9400-git-cvsserver-server.sh @@ -221,7 +221,7 @@ test_expect_success 'req_Root (export-all)' \ 'cat request-anonymous | git-cvsserver --export-all pserver "$WORKDIR" >log 2>&1 && sed -ne \$p log | grep "^I LOVE YOU\$"' -test_expect_success 'req_Root failure (export-all w/o whitelist)' \ +test_expect_success 'req_Root failure (export-all w/o directory list)' \ '! (cat request-anonymous | git-cvsserver --export-all pserver >log 2>&1 || false)' test_expect_success 'req_Root (everything together)' \ diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index 31526e6b64..43de868b80 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -2485,6 +2485,13 @@ test_expect_success 'git config - section' ' EOF ' +test_expect_success 'git config - section include, includeIf' ' + test_completion "git config inclu" <<-\EOF + include.Z + includeIf.Z + EOF +' + test_expect_success 'git config - variable name' ' test_completion "git config log.d" <<-\EOF log.date Z @@ -2493,6 +2500,12 @@ test_expect_success 'git config - variable name' ' EOF ' +test_expect_success 'git config - variable name include' ' + test_completion "git config include.p" <<-\EOF + include.path Z + EOF +' + test_expect_success 'git config - value' ' test_completion "git config color.pager " <<-\EOF false Z diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 6da7273f1d..8c44856eae 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -651,8 +651,7 @@ test_set_prereq () { # test_unset_prereq() !*) ;; - # (Temporary?) whitelist of things we can't easily - # pretend not to support + # List of things we can't easily pretend to not support SYMLINKS) ;; # Inspecting whether GIT_TEST_FAIL_PREREQS is on diff --git a/t/test-lib.sh b/t/test-lib.sh index 8cabb4d10f..7726d1da88 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -1456,7 +1456,9 @@ remove_trash_directory "$TRASH_DIRECTORY" || { remove_trash=t if test -z "$TEST_NO_CREATE_REPO" then - git init "$TRASH_DIRECTORY" >&3 2>&4 || + git init \ + ${TEST_CREATE_REPO_NO_TEMPLATE:+--template=} \ + "$TRASH_DIRECTORY" >&3 2>&4 || error "cannot run git init" else mkdir -p "$TRASH_DIRECTORY" diff --git a/transport.c b/transport.c index 52db7a3cb0..b51e991e44 100644 --- a/transport.c +++ b/transport.c @@ -940,7 +940,7 @@ static int external_specification_len(const char *url) return strchr(url, ':') - url; } -static const struct string_list *protocol_whitelist(void) +static const struct string_list *protocol_allow_list(void) { static int enabled = -1; static struct string_list allowed = STRING_LIST_INIT_DUP; @@ -1020,9 +1020,9 @@ static enum protocol_allow_config get_protocol_config(const char *type) int is_transport_allowed(const char *type, int from_user) { - const struct string_list *whitelist = protocol_whitelist(); - if (whitelist) - return string_list_has_string(whitelist, type); + const struct string_list *allow_list = protocol_allow_list(); + if (allow_list) + return string_list_has_string(allow_list, type); switch (get_protocol_config(type)) { case PROTOCOL_ALLOW_ALWAYS: diff --git a/upload-pack.c b/upload-pack.c index 3a851b3606..09f48317b0 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -1321,18 +1321,27 @@ static int upload_pack_config(const char *var, const char *value, void *cb_data) data->advertise_sid = git_config_bool(var, value); } - if (current_config_scope() != CONFIG_SCOPE_LOCAL && - current_config_scope() != CONFIG_SCOPE_WORKTREE) { - if (!strcmp("uploadpack.packobjectshook", var)) - return git_config_string(&data->pack_objects_hook, var, value); - } - if (parse_object_filter_config(var, value, data) < 0) return -1; return parse_hide_refs_config(var, value, "uploadpack"); } +static int upload_pack_protected_config(const char *var, const char *value, void *cb_data) +{ + struct upload_pack_data *data = cb_data; + + if (!strcmp("uploadpack.packobjectshook", var)) + return git_config_string(&data->pack_objects_hook, var, value); + return 0; +} + +static void get_upload_pack_config(struct upload_pack_data *data) +{ + git_config(upload_pack_config, data); + git_protected_config(upload_pack_protected_config, data); +} + void upload_pack(const int advertise_refs, const int stateless_rpc, const int timeout) { @@ -1340,8 +1349,7 @@ void upload_pack(const int advertise_refs, const int stateless_rpc, struct upload_pack_data data; upload_pack_data_init(&data); - - git_config(upload_pack_config, &data); + get_upload_pack_config(&data); data.stateless_rpc = stateless_rpc; data.timeout = timeout; @@ -1695,8 +1703,7 @@ int upload_pack_v2(struct repository *r, struct packet_reader *request) upload_pack_data_init(&data); data.use_sideband = LARGE_PACKET_MAX; - - git_config(upload_pack_config, &data); + get_upload_pack_config(&data); while (state != FETCH_DONE) { switch (state) { @@ -616,10 +616,16 @@ int git_fsync(int fd, enum fsync_action action) } } +static void log_trace_fsync_if(const char *key, intmax_t value) +{ + if (value) + trace2_data_intmax("fsync", the_repository, key, value); +} + void trace_git_fsync_stats(void) { - trace2_data_intmax("fsync", the_repository, "fsync/writeout-only", count_fsync_writeout_only); - trace2_data_intmax("fsync", the_repository, "fsync/hardware-flush", count_fsync_hardware_flush); + log_trace_fsync_if("fsync/writeout-only", count_fsync_writeout_only); + log_trace_fsync_if("fsync/hardware-flush", count_fsync_hardware_flush); } static int warn_if_unremovable(const char *op, const char *file, int rc) |
