diff options
95 files changed, 3389 insertions, 911 deletions
diff --git a/Documentation/RelNotes/2.28.0.txt b/Documentation/RelNotes/2.28.0.txt index 9fc6dc3a13..02e150efcb 100644 --- a/Documentation/RelNotes/2.28.0.txt +++ b/Documentation/RelNotes/2.28.0.txt @@ -10,6 +10,15 @@ Backward compatibility notes easily opt into a set of newer features, which use of the v2 transport protocol is now a part of. + * It used to be that setting extensions.* configuration variables + alone, while leaving core.repositoryFormatVersion=0, made these + settings effective, which was a wrong thing to do. In version 0, + there was no special meaning in extensions.* configuration + variables. This has been corrected. If you need these repository + extensions to be effective, the core.repositoryFormatVersion + variable needs to be updated to 1 after vetting these extensions.* + variables are set correctly. + UI, Workflows & Features @@ -19,6 +28,19 @@ UI, Workflows & Features * The check in "git fsck" to ensure that the tree objects are sorted still had corner cases it missed unsorted entries. + * The interface to redact sensitive information in the trace output + has been simplified. + + * The command line completion (in contrib/) learned to complete + options that the "git switch" command takes. + + * "git diff" used to take arguments in random and nonsense range + notation, e.g. "git diff A..B C", "git diff A..B C...D", etc., + which has been cleaned up. + + * "git diff-files" has been taught to say paths that are marked as + intent-to-add are new files, not modified from an empty blob. + Performance, Internal Implementation, Development Support etc. @@ -50,6 +72,25 @@ Performance, Internal Implementation, Development Support etc. * Workaround breakage in MSVC build, where "curl-config --cflags" gives settings appropriate for GCC build. + * Code clean-up of "git clean" resulted in a fix of recent + performance regression. + + * Code clean-up in the codepath that serves "git fetch" continues. + + * "git merge-base --is-ancestor" is taught to take advantage of the + commit graph. + + * Rewrite of parts of the scripted "git submodule" Porcelain command + continues; this time it is "git submodule set-branch" subcommand's + turn. + + * The "fetch/clone" protocol has been updated to allow the server to + instruct the clients to grab pre-packaged packfile(s) in addition + to the packed object data coming over the wire. + + * A misdesigned strbuf_write_fd() function has been retired. + + Fixes since v2.27 ----------------- @@ -99,9 +140,29 @@ Fixes since v2.27 * Use of negative pathspec, while collecting paths including untracked ones in the working tree, was broken. + * The same worktree directory must be registered only once, but + "git worktree move" allowed this invariant to be violated, which + has been corrected. + (merge 810382ed37 es/worktree-duplicate-paths later to maint). + + * The effect of sparse checkout settings on submodules is documented. + (merge e7d7c73249 en/sparse-with-submodule-doc later to maint). + + * Code clean-up around "git branch" with a minor bugfix. + (merge dc44639904 dl/branch-cleanup later to maint). + + * A branch name used in a test has been clarified to match what is + going on. + (merge 08dc26061f pb/t4014-unslave later to maint). + + * An in-code comment in "git diff" has been updated. + (merge c592fd4c83 dl/diff-usage-comment-update later to maint). + * Other code cleanup, docfix, build fix, etc. (merge 2c31a7aa44 jx/pkt-line-doc-count-fix later to maint). (merge d63ae31962 cb/t5608-cleanup later to maint). (merge 788db145c7 dl/t-readme-spell-git-correctly later to maint). (merge 45a87a83bb dl/python-2.7-is-the-floor-version later to maint). (merge b75a219904 es/advertise-contribution-doc later to maint). + (merge 0c9a4f638a rs/pull-leakfix later to maint). + (merge d546fe2874 rs/commit-reach-leakfix later to maint). diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt index 37781cf175..1018110ddc 100644 --- a/Documentation/git-diff.txt +++ b/Documentation/git-diff.txt @@ -11,15 +11,17 @@ SYNOPSIS [verse] 'git diff' [<options>] [<commit>] [--] [<path>...] 'git diff' [<options>] --cached [<commit>] [--] [<path>...] -'git diff' [<options>] <commit> <commit> [--] [<path>...] +'git diff' [<options>] <commit> [<commit>...] <commit> [--] [<path>...] +'git diff' [<options>] <commit>...<commit> [--] [<path>...] 'git diff' [<options>] <blob> <blob> 'git diff' [<options>] --no-index [--] <path> <path> DESCRIPTION ----------- Show changes between the working tree and the index or a tree, changes -between the index and a tree, changes between two trees, changes between -two blob objects, or changes between two files on disk. +between the index and a tree, changes between two trees, changes resulting +from a merge, changes between two blob objects, or changes between two +files on disk. 'git diff' [<options>] [--] [<path>...]:: @@ -67,6 +69,15 @@ two blob objects, or changes between two files on disk. one side is omitted, it will have the same effect as using HEAD instead. +'git diff' [<options>] <commit> [<commit>...] <commit> [--] [<path>...]:: + + This form is to view the results of a merge commit. The first + listed <commit> must be the merge itself; the remaining two or + more commits should be its parents. A convenient way to produce + the desired set of revisions is to use the {caret}@ suffix. + For instance, if `master` names a merge commit, `git diff master + master^@` gives the same combined diff as `git show master`. + 'git diff' [<options>] <commit>\...<commit> [--] [<path>...]:: This form is to view the changes on the branch containing @@ -196,7 +207,8 @@ linkgit:git-difftool[1], linkgit:git-log[1], linkgit:gitdiffcore[7], linkgit:git-format-patch[1], -linkgit:git-apply[1] +linkgit:git-apply[1], +linkgit:git-show[1] GIT --- diff --git a/Documentation/git-http-fetch.txt b/Documentation/git-http-fetch.txt index 666b042679..4deb4893f5 100644 --- a/Documentation/git-http-fetch.txt +++ b/Documentation/git-http-fetch.txt @@ -9,7 +9,7 @@ git-http-fetch - Download from a remote Git repository via HTTP SYNOPSIS -------- [verse] -'git http-fetch' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] [--stdin] <commit> <url> +'git http-fetch' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] [--stdin | --packfile=<hash> | <commit>] <url> DESCRIPTION ----------- @@ -40,6 +40,13 @@ commit-id:: <commit-id>['\t'<filename-as-in--w>] +--packfile=<hash>:: + Instead of a commit id on the command line (which is not expected in + this case), 'git http-fetch' fetches the packfile directly at the given + URL and uses index-pack to generate corresponding .idx and .keep files. + The hash is used to determine the name of the temporary file and is + arbitrary. The output of index-pack is printed to stdout. + --recover:: Verify that everything reachable from target is fetched. Used after an earlier fetch is interrupted. diff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt index d5b7560bfe..9316d9a80b 100644 --- a/Documentation/git-index-pack.txt +++ b/Documentation/git-index-pack.txt @@ -93,6 +93,14 @@ OPTIONS --max-input-size=<size>:: Die, if the pack is larger than <size>. +--object-format=<hash-algorithm>:: + Specify the given object format (hash algorithm) for the pack. The valid + values are 'sha1' and (if enabled) 'sha256'. The default is the algorithm for + the current repository (set by `extensions.objectFormat`), or 'sha1' if no + value is set or outside a repository. ++ +This option cannot be used with --stdin. + NOTES ----- diff --git a/Documentation/git-show-index.txt b/Documentation/git-show-index.txt index 424e4ba84c..39b1d8eaa1 100644 --- a/Documentation/git-show-index.txt +++ b/Documentation/git-show-index.txt @@ -9,7 +9,7 @@ git-show-index - Show packed archive index SYNOPSIS -------- [verse] -'git show-index' +'git show-index' [--object-format=<hash-algorithm>] DESCRIPTION @@ -36,6 +36,15 @@ Note that you can get more information on a packfile by calling linkgit:git-verify-pack[1]. However, as this command considers only the index file itself, it's both faster and more flexible. +OPTIONS +------- + +--object-format=<hash-algorithm>:: + Specify the given object format (hash algorithm) for the index file. The + valid values are 'sha1' and (if enabled) 'sha256'. The default is the + algorithm for the current repository (set by `extensions.objectFormat`), or + 'sha1' if no value is set or outside a repository.. + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-sparse-checkout.txt b/Documentation/git-sparse-checkout.txt index 7c8943af7a..a0eeaeb02e 100644 --- a/Documentation/git-sparse-checkout.txt +++ b/Documentation/git-sparse-checkout.txt @@ -200,10 +200,32 @@ directory. SUBMODULES ---------- -If your repository contains one or more submodules, then those submodules will -appear based on which you initialized with the `git submodule` command. If -your sparse-checkout patterns exclude an initialized submodule, then that -submodule will still appear in your working directory. +If your repository contains one or more submodules, then submodules +are populated based on interactions with the `git submodule` command. +Specifically, `git submodule init -- <path>` will ensure the submodule +at `<path>` is present, while `git submodule deinit [-f] -- <path>` +will remove the files for the submodule at `<path>` (including any +untracked files, uncommitted changes, and unpushed history). Similar +to how sparse-checkout removes files from the working tree but still +leaves entries in the index, deinitialized submodules are removed from +the working directory but still have an entry in the index. + +Since submodules may have unpushed changes or untracked files, +removing them could result in data loss. Thus, changing sparse +inclusion/exclusion rules will not cause an already checked out +submodule to be removed from the working copy. Said another way, just +as `checkout` will not cause submodules to be automatically removed or +initialized even when switching between branches that remove or add +submodules, using `sparse-checkout` to reduce or expand the scope of +"interesting" files will not cause submodules to be automatically +deinitialized or initialized either. + +Further, the above facts mean that there are multiple reasons that +"tracked" files might not be present in the working copy: sparsity +pattern application from sparse-checkout, and submodule initialization +state. Thus, commands like `git grep` that work on tracked files in +the working copy may return results that are limited by either or both +of these restrictions. SEE ALSO diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt index 85d92c9761..4796c3c05e 100644 --- a/Documentation/git-worktree.txt +++ b/Documentation/git-worktree.txt @@ -126,7 +126,9 @@ OPTIONS locked working tree path, specify `--force` twice. + `move` refuses to move a locked working tree unless `--force` is specified -twice. +twice. If the destination is already assigned to some other working tree but is +missing (for instance, if `<new-path>` was deleted manually), then `--force` +allows the move to proceed; use --force twice if the destination is locked. + `remove` refuses to remove an unclean working tree unless `--force` is used. To remove a locked working tree, specify `--force` twice. diff --git a/Documentation/git.txt b/Documentation/git.txt index 40bd32f590..3e50065198 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -775,11 +775,10 @@ for full details. See `GIT_TRACE2` for available trace output options and link:technical/api-trace2.html[Trace2 documentation] for full details. -`GIT_REDACT_COOKIES`:: - This can be set to a comma-separated list of strings. When a curl trace - is enabled (see `GIT_TRACE_CURL` above), whenever a "Cookies:" header - sent by the client is dumped, values of cookies whose key is in that - list (case-sensitive) are redacted. +`GIT_TRACE_REDACT`:: + By default, when tracing is activated, Git redacts the values of + cookies, the "Authorization:" header, and the "Proxy-Authorization:" + header. Set this variable to `0` to prevent this redaction. `GIT_LITERAL_PATHSPECS`:: Setting this variable to `1` will cause Git to treat all diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt index 81f2a87e88..642471109f 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -404,6 +404,35 @@ Both standard output and standard error output are forwarded to `git send-pack` on the other end, so you can simply `echo` messages for the user. +ref-transaction +~~~~~~~~~~~~~~~ + +This hook is invoked by any Git command that performs reference +updates. It executes whenever a reference transaction is prepared, +committed or aborted and may thus get called multiple times. + +The hook takes exactly one argument, which is the current state the +given reference transaction is in: + + - "prepared": All reference updates have been queued to the + transaction and references were locked on disk. + + - "committed": The reference transaction was committed and all + references now have their respective new value. + + - "aborted": The reference transaction was aborted, no changes + were performed and the locks have been released. + +For each reference update that was added to the transaction, the hook +receives on standard input a line of the format: + + <old-value> SP <new-value> SP <ref-name> LF + +The exit status of the hook is ignored for any state except for the +"prepared" state. In the "prepared" state, a non-zero exit status will +cause the transaction to be aborted. The hook will not be called with +"aborted" state in that case. + push-to-checkout ~~~~~~~~~~~~~~~~ diff --git a/Documentation/gitremote-helpers.txt b/Documentation/gitremote-helpers.txt index 93baeeb029..6f1e269ae4 100644 --- a/Documentation/gitremote-helpers.txt +++ b/Documentation/gitremote-helpers.txt @@ -238,6 +238,9 @@ the remote repository. `--signed-tags=verbatim` to linkgit:git-fast-export[1]. In the absence of this capability, Git will use `--signed-tags=warn-strip`. +'object-format':: + This indicates that the helper is able to interact with the remote + side using an explicit hash algorithm extension. COMMANDS @@ -257,12 +260,14 @@ Support for this command is mandatory. 'list':: Lists the refs, one per line, in the format "<value> <name> [<attr> ...]". The value may be a hex sha1 hash, "@<dest>" for - a symref, or "?" to indicate that the helper could not get the - value of the ref. A space-separated list of attributes follows - the name; unrecognized attributes are ignored. The list ends - with a blank line. + a symref, ":<keyword> <value>" for a key-value pair, or + "?" to indicate that the helper could not get the value of the + ref. A space-separated list of attributes follows the name; + unrecognized attributes are ignored. The list ends with a + blank line. + See REF LIST ATTRIBUTES for a list of currently defined attributes. +See REF LIST KEYWORDS for a list of currently defined keywords. + Supported if the helper has the "fetch" or "import" capability. @@ -432,6 +437,18 @@ attributes are defined. This ref is unchanged since the last import or fetch, although the helper cannot necessarily determine what value that produced. +REF LIST KEYWORDS +----------------- + +The 'list' command may produce a list of key-value pairs. +The following keys are defined. + +'object-format':: + The refs are using the given hash algorithm. This keyword is only + used if the server and client both support the object-format + extension. + + OPTIONS ------- @@ -516,6 +533,14 @@ set by Git if the remote helper has the 'option' capability. transaction. If successful, all refs will be updated, or none will. If the remote side does not support this capability, the push will fail. +'option object-format' {'true'|algorithm}:: + If 'true', indicate that the caller wants hash algorithm information + to be passed back from the remote. This mode is used when fetching + refs. ++ +If set to an algorithm, indicate that the caller wants to interact with +the remote side using that algorithm. + SEE ALSO -------- linkgit:git-remote[1] diff --git a/Documentation/technical/packfile-uri.txt b/Documentation/technical/packfile-uri.txt new file mode 100644 index 0000000000..318713abc3 --- /dev/null +++ b/Documentation/technical/packfile-uri.txt @@ -0,0 +1,78 @@ +Packfile URIs +============= + +This feature allows servers to serve part of their packfile response as URIs. +This allows server designs that improve scalability in bandwidth and CPU usage +(for example, by serving some data through a CDN), and (in the future) provides +some measure of resumability to clients. + +This feature is available only in protocol version 2. + +Protocol +-------- + +The server advertises the `packfile-uris` capability. + +If the client then communicates which protocols (HTTPS, etc.) it supports with +a `packfile-uris` argument, the server MAY send a `packfile-uris` section +directly before the `packfile` section (right after `wanted-refs` if it is +sent) containing URIs of any of the given protocols. The URIs point to +packfiles that use only features that the client has declared that it supports +(e.g. ofs-delta and thin-pack). See protocol-v2.txt for the documentation of +this section. + +Clients should then download and index all the given URIs (in addition to +downloading and indexing the packfile given in the `packfile` section of the +response) before performing the connectivity check. + +Server design +------------- + +The server can be trivially made compatible with the proposed protocol by +having it advertise `packfile-uris`, tolerating the client sending +`packfile-uris`, and never sending any `packfile-uris` section. But we should +include some sort of non-trivial implementation in the Minimum Viable Product, +at least so that we can test the client. + +This is the implementation: a feature, marked experimental, that allows the +server to be configured by one or more `uploadpack.blobPackfileUri=<sha1> +<uri>` entries. Whenever the list of objects to be sent is assembled, all such +blobs are excluded, replaced with URIs. The client will download those URIs, +expecting them to each point to packfiles containing single blobs. + +Client design +------------- + +The client has a config variable `fetch.uriprotocols` that determines which +protocols the end user is willing to use. By default, this is empty. + +When the client downloads the given URIs, it should store them with "keep" +files, just like it does with the packfile in the `packfile` section. These +additional "keep" files can only be removed after the refs have been updated - +just like the "keep" file for the packfile in the `packfile` section. + +The division of work (initial fetch + additional URIs) introduces convenient +points for resumption of an interrupted clone - such resumption can be done +after the Minimum Viable Product (see "Future work"). + +Future work +----------- + +The protocol design allows some evolution of the server and client without any +need for protocol changes, so only a small-scoped design is included here to +form the MVP. For example, the following can be done: + + * On the server, more sophisticated means of excluding objects (e.g. by + specifying a commit to represent that commit and all objects that it + references). + * On the client, resumption of clone. If a clone is interrupted, information + could be recorded in the repository's config and a "clone-resume" command + can resume the clone in progress. (Resumption of subsequent fetches is more + difficult because that must deal with the user wanting to use the repository + even after the fetch was interrupted.) + +There are some possible features that will require a change in protocol: + + * Additional HTTP headers (e.g. authentication) + * Byte range support + * Different file formats referenced by URIs (e.g. raw object) diff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt index 2b267c0da6..36ccd14f97 100644 --- a/Documentation/technical/protocol-capabilities.txt +++ b/Documentation/technical/protocol-capabilities.txt @@ -176,6 +176,21 @@ agent strings are purely informative for statistics and debugging purposes, and MUST NOT be used to programmatically assume the presence or absence of particular features. +object-format +------------- + +This capability, which takes a hash algorithm as an argument, indicates +that the server supports the given hash algorithms. It may be sent +multiple times; if so, the first one given is the one used in the ref +advertisement. + +When provided by the client, this indicates that it intends to use the +given hash algorithm to communicate. The algorithm provided must be one +that the server supports. + +If this capability is not provided, it is assumed that the only +supported algorithm is SHA-1. + symref ------ diff --git a/Documentation/technical/protocol-v2.txt b/Documentation/technical/protocol-v2.txt index 3996d70891..e597b74da3 100644 --- a/Documentation/technical/protocol-v2.txt +++ b/Documentation/technical/protocol-v2.txt @@ -325,13 +325,26 @@ included in the client's request: indicating its sideband (1, 2, or 3), and the server may send "0005\2" (a PKT-LINE of sideband 2 with no payload) as a keepalive packet. +If the 'packfile-uris' feature is advertised, the following argument +can be included in the client's request as well as the potential +addition of the 'packfile-uris' section in the server's response as +explained below. + + packfile-uris <comma-separated list of protocols> + Indicates to the server that the client is willing to receive + URIs of any of the given protocols in place of objects in the + sent packfile. Before performing the connectivity check, the + client should download from all given URIs. Currently, the + protocols supported are "http" and "https". + The response of `fetch` is broken into a number of sections separated by delimiter packets (0001), with each section beginning with its section -header. +header. Most sections are sent only when the packfile is sent. - output = *section - section = (acknowledgments | shallow-info | wanted-refs | packfile) - (flush-pkt | delim-pkt) + output = acknowledgements flush-pkt | + [acknowledgments delim-pkt] [shallow-info delim-pkt] + [wanted-refs delim-pkt] [packfile-uris delim-pkt] + packfile flush-pkt acknowledgments = PKT-LINE("acknowledgments" LF) (nak | *ack) @@ -349,13 +362,17 @@ header. *PKT-LINE(wanted-ref LF) wanted-ref = obj-id SP refname + packfile-uris = PKT-LINE("packfile-uris" LF) *packfile-uri + packfile-uri = PKT-LINE(40*(HEXDIGIT) SP *%x20-ff LF) + packfile = PKT-LINE("packfile" LF) *PKT-LINE(%x01-03 *%x00-ff) acknowledgments section - * If the client determines that it is finished with negotiations - by sending a "done" line, the acknowledgments sections MUST be - omitted from the server's response. + * If the client determines that it is finished with negotiations by + sending a "done" line (thus requiring the server to send a packfile), + the acknowledgments sections MUST be omitted from the server's + response. * Always begins with the section header "acknowledgments" @@ -406,9 +423,6 @@ header. which the client has not indicated was shallow as a part of its request. - * This section is only included if a packfile section is also - included in the response. - wanted-refs section * This section is only included if the client has requested a ref using a 'want-ref' line and if a packfile section is also @@ -422,6 +436,20 @@ header. * The server MUST NOT send any refs which were not requested using 'want-ref' lines. + packfile-uris section + * This section is only included if the client sent + 'packfile-uris' and the server has at least one such URI to + send. + + * Always begins with the section header "packfile-uris". + + * For each URI the server sends, it sends a hash of the pack's + contents (as output by git index-pack) followed by the URI. + + * The hashes are 40 hex characters long. When Git upgrades to a new + hash algorithm, this might need to be updated. (It should match + whatever index-pack outputs after "pack\t" or "keep\t". + packfile section * This section is only included if the client has sent 'want' lines in its request and either requested that no more @@ -455,3 +483,12 @@ included in a request. This is done by sending each option as a a request. The provided options must not contain a NUL or LF character. + + object-format +~~~~~~~~~~~~~~~ + +The server can advertise the `object-format` capability with a value `X` (in the +form `object-format=X`) to notify the client that the server is able to deal +with objects using hash algorithm X. If not specified, the server is assumed to +only handle SHA-1. If the client would like to use a hash algorithm other than +SHA-1, it should specify its object-format string. diff --git a/bugreport.c b/bugreport.c index 28f4568b01..09579e268d 100644 --- a/bugreport.c +++ b/bugreport.c @@ -180,7 +180,9 @@ int cmd_main(int argc, const char **argv) die(_("couldn't create a new file at '%s'"), report_path.buf); } - strbuf_write_fd(&buffer, report); + if (write_in_full(report, buffer.buf, buffer.len) < 0) + die_errno(_("unable to write to %s"), report_path.buf); + close(report); /* diff --git a/builtin/branch.c b/builtin/branch.c index accb61b1aa..99633ad004 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -693,7 +693,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) list = 1; if (!!delete + !!rename + !!copy + !!new_upstream + !!show_current + - list + unset_upstream > 1) + list + edit_description + unset_upstream > 1) usage_with_options(builtin_branch_usage, options); if (filter.abbrev == -1) diff --git a/builtin/clean.c b/builtin/clean.c index 4ca12bc0c0..5a9c29a558 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -924,12 +924,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix) 0); memset(&dir, 0, sizeof(dir)); - if (ignored_only) - dir.flags |= DIR_SHOW_IGNORED; - - if (ignored && ignored_only) - die(_("-x and -X cannot be used together")); - if (!interactive && !dry_run && !force) { if (config_set) die(_("clean.requireForce set to true and neither -i, -n, nor -f given; " @@ -946,6 +940,13 @@ int cmd_clean(int argc, const char **argv, const char *prefix) dir.flags |= DIR_SHOW_OTHER_DIRECTORIES; + if (ignored && ignored_only) + die(_("-x and -X cannot be used together")); + if (!ignored) + setup_standard_excludes(&dir); + if (ignored_only) + dir.flags |= DIR_SHOW_IGNORED; + if (argc) { /* * Remaining args implies pathspecs specified, and we should @@ -954,15 +955,41 @@ int cmd_clean(int argc, const char **argv, const char *prefix) remove_directories = 1; } - if (remove_directories) - dir.flags |= DIR_SHOW_IGNORED_TOO | DIR_KEEP_UNTRACKED_CONTENTS; + if (remove_directories && !ignored_only) { + /* + * We need to know about ignored files too: + * + * If (ignored), then we will delete ignored files as well. + * + * If (!ignored), then even though we not are doing + * anything with ignored files, we need to know about them + * so that we can avoid deleting a directory of untracked + * files that also contains an ignored file within it. + * + * For the (!ignored) case, since we only need to avoid + * deleting ignored files, we can set + * DIR_SHOW_IGNORED_TOO_MODE_MATCHING in order to avoid + * recursing into a directory which is itself ignored. + */ + dir.flags |= DIR_SHOW_IGNORED_TOO; + if (!ignored) + dir.flags |= DIR_SHOW_IGNORED_TOO_MODE_MATCHING; + + /* + * Let the fill_directory() machinery know that we aren't + * just recursing to collect the ignored files; we want all + * the untracked ones so that we can delete them. (Note: + * we could also set DIR_KEEP_UNTRACKED_CONTENTS when + * ignored_only is true, since DIR_KEEP_UNTRACKED_CONTENTS + * only has effect in combination with DIR_SHOW_IGNORED_TOO. It makes + * the code clearer to exclude it, though. + */ + dir.flags |= DIR_KEEP_UNTRACKED_CONTENTS; + } if (read_cache() < 0) die(_("index file corrupt")); - if (!ignored) - setup_standard_excludes(&dir); - pl = add_pattern_list(&dir, EXC_CMDL, "--exclude option"); for (i = 0; i < exclude_list.nr; i++) add_pattern(exclude_list.items[i].string, "", 0, pl, -(i+1)); diff --git a/builtin/clone.c b/builtin/clone.c index 2a8e3aaaed..e3519a8355 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -1220,6 +1220,15 @@ int cmd_clone(int argc, const char **argv, const char *prefix) refs = transport_get_remote_refs(transport, &ref_prefixes); if (refs) { + int hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport)); + + /* + * Now that we know what algorithm the remote side is using, + * let's set ours to the same thing. + */ + initialize_repository_version(hash_algo); + repo_set_hash_algo(the_repository, hash_algo); + mapped_refs = wanted_peer_refs(refs, &remote->fetch); /* * transport_get_remote_refs() may return refs with null sha-1 diff --git a/builtin/diff-files.c b/builtin/diff-files.c index 86ae474fbf..1e352dd8f7 100644 --- a/builtin/diff-files.c +++ b/builtin/diff-files.c @@ -28,6 +28,13 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ repo_init_revisions(the_repository, &rev, prefix); rev.abbrev = 0; + + /* + * Consider "intent-to-add" files as new by default, unless + * explicitly specified in the command line or anywhere else. + */ + rev.diffopt.ita_invisible_in_index = 1; + precompose_argv(argc, argv); argc = setup_revisions(argc, argv, &rev, NULL); diff --git a/builtin/diff.c b/builtin/diff.c index 8537b17bd5..8c36da09b6 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -6,6 +6,7 @@ #define USE_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" #include "config.h" +#include "ewah/ewok.h" #include "lockfile.h" #include "color.h" #include "commit.h" @@ -23,7 +24,13 @@ #define DIFF_NO_INDEX_IMPLICIT 2 static const char builtin_diff_usage[] = -"git diff [<options>] [<commit> [<commit>]] [--] [<path>...]"; +"git diff [<options>] [<commit>] [--] [<path>...]\n" +" or: git diff [<options>] --cached [<commit>] [--] [<path>...]\n" +" or: git diff [<options>] <commit> [<commit>...] <commit> [--] [<path>...]\n" +" or: git diff [<options>] <commit>...<commit>] [--] [<path>...]\n" +" or: git diff [<options>] <blob> <blob>]\n" +" or: git diff [<options>] --no-index [--] <path> <path>]\n" +COMMON_DIFF_OPTIONS_HELP; static const char *blob_path(struct object_array_entry *entry) { @@ -254,6 +261,108 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv return run_diff_files(revs, options); } +struct symdiff { + struct bitmap *skip; + int warn; + const char *base, *left, *right; +}; + +/* + * Check for symmetric-difference arguments, and if present, arrange + * everything we need to know to handle them correctly. As a bonus, + * weed out all bogus range-based revision specifications, e.g., + * "git diff A..B C..D" or "git diff A..B C" get rejected. + * + * For an actual symmetric diff, *symdiff is set this way: + * + * - its skip is non-NULL and marks *all* rev->pending.objects[i] + * indices that the caller should ignore (extra merge bases, of + * which there might be many, and A in A...B). Note that the + * chosen merge base and right side are NOT marked. + * - warn is set if there are multiple merge bases. + * - base, left, and right point to the names to use in a + * warning about multiple merge bases. + * + * If there is no symmetric diff argument, sym->skip is NULL and + * sym->warn is cleared. The remaining fields are not set. + */ +static void symdiff_prepare(struct rev_info *rev, struct symdiff *sym) +{ + int i, is_symdiff = 0, basecount = 0, othercount = 0; + int lpos = -1, rpos = -1, basepos = -1; + struct bitmap *map = NULL; + + /* + * Use the whence fields to find merge bases and left and + * right parts of symmetric difference, so that we do not + * depend on the order that revisions are parsed. If there + * are any revs that aren't from these sources, we have a + * "git diff C A...B" or "git diff A...B C" case. Or we + * could even get "git diff A...B C...E", for instance. + * + * If we don't have just one merge base, we pick one + * at random. + * + * NB: REV_CMD_LEFT, REV_CMD_RIGHT are also used for A..B, + * so we must check for SYMMETRIC_LEFT too. The two arrays + * rev->pending.objects and rev->cmdline.rev are parallel. + */ + for (i = 0; i < rev->cmdline.nr; i++) { + struct object *obj = rev->pending.objects[i].item; + switch (rev->cmdline.rev[i].whence) { + case REV_CMD_MERGE_BASE: + if (basepos < 0) + basepos = i; + basecount++; + break; /* do mark all bases */ + case REV_CMD_LEFT: + if (lpos >= 0) + usage(builtin_diff_usage); + lpos = i; + if (obj->flags & SYMMETRIC_LEFT) { + is_symdiff = 1; + break; /* do mark A */ + } + continue; + case REV_CMD_RIGHT: + if (rpos >= 0) + usage(builtin_diff_usage); + rpos = i; + continue; /* don't mark B */ + case REV_CMD_PARENTS_ONLY: + case REV_CMD_REF: + case REV_CMD_REV: + othercount++; + continue; + } + if (map == NULL) + map = bitmap_new(); + bitmap_set(map, i); + } + + /* + * Forbid any additional revs for both A...B and A..B. + */ + if (lpos >= 0 && othercount > 0) + usage(builtin_diff_usage); + + if (!is_symdiff) { + bitmap_free(map); + sym->warn = 0; + sym->skip = NULL; + return; + } + + sym->left = rev->pending.objects[lpos].name; + sym->right = rev->pending.objects[rpos].name; + sym->base = rev->pending.objects[basepos].name; + if (basecount == 0) + die(_("%s...%s: no merge base"), sym->left, sym->right); + bitmap_unset(map, basepos); /* unmark the base we want */ + sym->warn = basecount > 1; + sym->skip = map; +} + int cmd_diff(int argc, const char **argv, const char *prefix) { int i; @@ -263,19 +372,29 @@ int cmd_diff(int argc, const char **argv, const char *prefix) struct object_array_entry *blob[2]; int nongit = 0, no_index = 0; int result = 0; + struct symdiff sdiff; /* * We could get N tree-ish in the rev.pending_objects list. - * Also there could be M blobs there, and P pathspecs. + * Also there could be M blobs there, and P pathspecs. --cached may + * also be present. * * N=0, M=0: - * cache vs files (diff-files) + * cache vs files (diff-files) + * + * N=0, M=0, --cached: + * HEAD vs cache (diff-index --cached) + * * N=0, M=2: * compare two random blobs. P must be zero. + * * N=0, M=1, P=1: - * compare a blob with a working tree file. + * compare a blob with a working tree file. * * N=1, M=0: + * tree vs files (diff-index) + * + * N=1, M=0, --cached: * tree vs cache (diff-index --cached) * * N=2, M=0: @@ -382,6 +501,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) } } + symdiff_prepare(&rev, &sdiff); for (i = 0; i < rev.pending.nr; i++) { struct object_array_entry *entry = &rev.pending.objects[i]; struct object *obj = entry->item; @@ -396,6 +516,8 @@ int cmd_diff(int argc, const char **argv, const char *prefix) obj = &get_commit_tree(((struct commit *)obj))->object; if (obj->type == OBJ_TREE) { + if (sdiff.skip && bitmap_get(sdiff.skip, i)) + continue; obj->flags |= flags; add_object_array(obj, name, &ent); } else if (obj->type == OBJ_BLOB) { @@ -437,21 +559,12 @@ int cmd_diff(int argc, const char **argv, const char *prefix) usage(builtin_diff_usage); else if (ent.nr == 1) result = builtin_diff_index(&rev, argc, argv); - else if (ent.nr == 2) + else if (ent.nr == 2) { + if (sdiff.warn) + warning(_("%s...%s: multiple merge bases, using %s"), + sdiff.left, sdiff.right, sdiff.base); result = builtin_diff_tree(&rev, argc, argv, &ent.objects[0], &ent.objects[1]); - else if (ent.objects[0].item->flags & UNINTERESTING) { - /* - * diff A...B where there is at least one merge base - * between A and B. We have ent.objects[0] == - * merge-base, ent.objects[ents-2] == A, and - * ent.objects[ents-1] == B. Show diff between the - * base and B. Note that we pick one merge base at - * random if there are more than one. - */ - result = builtin_diff_tree(&rev, argc, argv, - &ent.objects[0], - &ent.objects[ent.nr-1]); } else result = builtin_diff_combined(&rev, argc, argv, ent.objects, ent.nr); diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index 94b0c89b82..bbb5c96167 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -48,8 +48,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) struct ref **sought = NULL; int nr_sought = 0, alloc_sought = 0; int fd[2]; - char *pack_lockfile = NULL; - char **pack_lockfile_ptr = NULL; + struct string_list pack_lockfiles = STRING_LIST_INIT_DUP; + struct string_list *pack_lockfiles_ptr = NULL; struct child_process *conn; struct fetch_pack_args args; struct oid_array shallow = OID_ARRAY_INIT; @@ -134,7 +134,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) } if (!strcmp("--lock-pack", arg)) { args.lock_pack = 1; - pack_lockfile_ptr = &pack_lockfile; + pack_lockfiles_ptr = &pack_lockfiles; continue; } if (!strcmp("--check-self-contained-and-connected", arg)) { @@ -235,10 +235,15 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) } ref = fetch_pack(&args, fd, ref, sought, nr_sought, - &shallow, pack_lockfile_ptr, version); - if (pack_lockfile) { - printf("lock %s\n", pack_lockfile); + &shallow, pack_lockfiles_ptr, version); + if (pack_lockfiles.nr) { + int i; + + printf("lock %s\n", pack_lockfiles.items[0].string); fflush(stdout); + for (i = 1; i < pack_lockfiles.nr; i++) + warning(_("Lockfile created but not reported: %s"), + pack_lockfiles.items[i].string); } if (args.check_self_contained_and_connected && args.self_contained_and_connected) { diff --git a/builtin/fetch.c b/builtin/fetch.c index da11165ce2..82ac4be8a5 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -1795,9 +1795,6 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) if (depth || deepen_since || deepen_not.nr) deepen = 1; - if (filter_options.choice && !has_promisor_remote()) - die("--filter can only be used when extensions.partialClone is set"); - if (all) { if (argc == 1) die(_("fetch --all does not take a repository argument")); diff --git a/builtin/index-pack.c b/builtin/index-pack.c index f176dd28c8..f865666db9 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -1555,13 +1555,9 @@ static void read_v2_anomalous_offsets(struct packed_git *p, { const uint32_t *idx1, *idx2; uint32_t i; - const uint32_t hashwords = the_hash_algo->rawsz / sizeof(uint32_t); /* The address of the 4-byte offset table */ - idx1 = (((const uint32_t *)p->index_data) - + 2 /* 8-byte header */ - + 256 /* fan out */ - + hashwords * p->num_objects /* object ID table */ + idx1 = (((const uint32_t *)((const uint8_t *)p->index_data + p->crc_offset)) + p->num_objects /* CRC32 table */ ); @@ -1671,6 +1667,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) unsigned char pack_hash[GIT_MAX_RAWSZ]; unsigned foreign_nr = 1; /* zero is a "good" value, assume bad */ int report_end_of_input = 0; + int hash_algo = 0; /* * index-pack never needs to fetch missing objects except when @@ -1764,6 +1761,11 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) die(_("bad %s"), arg); } else if (skip_prefix(arg, "--max-input-size=", &arg)) { max_input_size = strtoumax(arg, NULL, 10); + } else if (skip_prefix(arg, "--object-format=", &arg)) { + hash_algo = hash_algo_by_name(arg); + if (hash_algo == GIT_HASH_UNKNOWN) + die(_("unknown hash algorithm '%s'"), arg); + repo_set_hash_algo(the_repository, hash_algo); } else usage(index_pack_usage); continue; @@ -1780,6 +1782,8 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) die(_("--fix-thin cannot be used without --stdin")); if (from_stdin && !startup_info->have_repository) die(_("--stdin requires a git repository")); + if (from_stdin && hash_algo) + die(_("--object-format cannot be used with --stdin")); if (!index_name && pack_name) index_name = derive_filename(pack_name, "idx", &index_name_buf); diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index 6ef519514b..3a4dd12903 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -118,6 +118,10 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) transport->server_options = &server_options; ref = transport_get_remote_refs(transport, &ref_prefixes); + if (ref) { + int hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport)); + repo_set_hash_algo(the_repository, hash_algo); + } if (transport_disconnect(transport)) { UNLEAK(sorting); return 1; diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index c5b433a23f..7016b28485 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -117,6 +117,8 @@ static unsigned long window_memory_limit = 0; static struct list_objects_filter_options filter_options; +static struct string_list uri_protocols = STRING_LIST_INIT_NODUP; + enum missing_action { MA_ERROR = 0, /* fail if any missing objects are encountered */ MA_ALLOW_ANY, /* silently allow ALL missing objects */ @@ -125,6 +127,15 @@ enum missing_action { static enum missing_action arg_missing_action; static show_object_fn fn_show_object; +struct configured_exclusion { + struct oidmap_entry e; + char *pack_hash_hex; + char *uri; +}; +static struct oidmap configured_exclusions; + +static struct oidset excluded_by_config; + /* * stats */ @@ -969,6 +980,25 @@ static void write_reused_pack(struct hashfile *f) unuse_pack(&w_curs); } +static void write_excluded_by_configs(void) +{ + struct oidset_iter iter; + const struct object_id *oid; + + oidset_iter_init(&excluded_by_config, &iter); + while ((oid = oidset_iter_next(&iter))) { + struct configured_exclusion *ex = + oidmap_get(&configured_exclusions, oid); + + if (!ex) + BUG("configured exclusion wasn't configured"); + write_in_full(1, ex->pack_hash_hex, strlen(ex->pack_hash_hex)); + write_in_full(1, " ", 1); + write_in_full(1, ex->uri, strlen(ex->uri)); + write_in_full(1, "\n", 1); + } +} + static const char no_split_warning[] = N_( "disabling bitmap writing, packs are split due to pack.packSizeLimit" ); @@ -1266,6 +1296,25 @@ static int want_object_in_pack(const struct object_id *oid, } } + if (uri_protocols.nr) { + struct configured_exclusion *ex = + oidmap_get(&configured_exclusions, oid); + int i; + const char *p; + + if (ex) { + for (i = 0; i < uri_protocols.nr; i++) { + if (skip_prefix(ex->uri, + uri_protocols.items[i].string, + &p) && + *p == ':') { + oidset_insert(&excluded_by_config, oid); + return 0; + } + } + } + } + return 1; } @@ -2864,6 +2913,29 @@ static int git_pack_config(const char *k, const char *v, void *cb) pack_idx_opts.version); return 0; } + if (!strcmp(k, "uploadpack.blobpackfileuri")) { + struct configured_exclusion *ex = xmalloc(sizeof(*ex)); + const char *oid_end, *pack_end; + /* + * Stores the pack hash. This is not a true object ID, but is + * of the same form. + */ + struct object_id pack_hash; + + if (parse_oid_hex(v, &ex->e.oid, &oid_end) || + *oid_end != ' ' || + parse_oid_hex(oid_end + 1, &pack_hash, &pack_end) || + *pack_end != ' ') + die(_("value of uploadpack.blobpackfileuri must be " + "of the form '<object-hash> <pack-hash> <uri>' (got '%s')"), v); + if (oidmap_get(&configured_exclusions, &ex->e.oid)) + die(_("object already configured in another " + "uploadpack.blobpackfileuri (got '%s')"), v); + ex->pack_hash_hex = xcalloc(1, pack_end - oid_end); + memcpy(ex->pack_hash_hex, oid_end + 1, pack_end - oid_end - 1); + ex->uri = xstrdup(pack_end + 1); + oidmap_put(&configured_exclusions, ex); + } return git_default_config(k, v, cb); } @@ -3462,6 +3534,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) N_("do not pack objects in promisor packfiles")), OPT_BOOL(0, "delta-islands", &use_delta_islands, N_("respect islands during delta compression")), + OPT_STRING_LIST(0, "uri-protocol", &uri_protocols, + N_("protocol"), + N_("exclude any configured uploadpack.blobpackfileuri with this protocol")), OPT_END(), }; @@ -3650,6 +3725,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) } trace2_region_enter("pack-objects", "write-pack-file", the_repository); + write_excluded_by_configs(); write_pack_file(); trace2_region_leave("pack-objects", "write-pack-file", the_repository); diff --git a/builtin/pull.c b/builtin/pull.c index 00e5857a8d..8e6572d305 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -1031,6 +1031,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix) ran_ff = 1; ret = run_merge(); } + free_commit_list(list); } if (!ran_ff) ret = run_rebase(&curr_head, merge_heads.oid, &rebase_fork_point); diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index ea3d0f01af..d43663bb0a 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -249,6 +249,7 @@ static void show_ref(const char *path, const struct object_id *oid) strbuf_addf(&cap, " push-cert=%s", push_cert_nonce); if (advertise_push_options) strbuf_addstr(&cap, " push-options"); + strbuf_addf(&cap, " object-format=%s", the_hash_algo->name); strbuf_addf(&cap, " agent=%s", git_user_agent_sanitized()); packet_write_fmt(1, "%s %s%c%s\n", oid_to_hex(oid), path, 0, cap.buf); @@ -1624,6 +1625,8 @@ static struct command *read_head_info(struct packet_reader *reader, linelen = strlen(reader->line); if (linelen < reader->pktlen) { const char *feature_list = reader->line + linelen + 1; + const char *hash = NULL; + int len = 0; if (parse_feature_request(feature_list, "report-status")) report_status = 1; if (parse_feature_request(feature_list, "side-band-64k")) @@ -1636,6 +1639,13 @@ static struct command *read_head_info(struct packet_reader *reader, if (advertise_push_options && parse_feature_request(feature_list, "push-options")) use_push_options = 1; + hash = parse_feature_value(feature_list, "object-format", &len, NULL); + if (!hash) { + hash = hash_algos[GIT_HASH_SHA1].name; + len = strlen(hash); + } + if (xstrncmpz(the_hash_algo->name, hash, len)) + die("error: unsupported object format '%s'", hash); } if (!strcmp(reader->line, "push-cert")) { diff --git a/builtin/show-index.c b/builtin/show-index.c index 0826f6a5a2..8106b03a6b 100644 --- a/builtin/show-index.c +++ b/builtin/show-index.c @@ -1,9 +1,12 @@ #include "builtin.h" #include "cache.h" #include "pack.h" +#include "parse-options.h" -static const char show_index_usage[] = -"git show-index"; +static const char *const show_index_usage[] = { + "git show-index [--object-format=<hash-algorithm>]", + NULL +}; int cmd_show_index(int argc, const char **argv, const char *prefix) { @@ -11,10 +14,26 @@ int cmd_show_index(int argc, const char **argv, const char *prefix) unsigned nr; unsigned int version; static unsigned int top_index[256]; - const unsigned hashsz = the_hash_algo->rawsz; + unsigned hashsz; + const char *hash_name = NULL; + int hash_algo; + const struct option show_index_options[] = { + OPT_STRING(0, "object-format", &hash_name, N_("hash-algorithm"), + N_("specify the hash algorithm to use")), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, show_index_options, show_index_usage, 0); + + if (hash_name) { + hash_algo = hash_algo_by_name(hash_name); + if (hash_algo == GIT_HASH_UNKNOWN) + die(_("Unknown hash algorithm")); + repo_set_hash_algo(the_repository, hash_algo); + } + + hashsz = the_hash_algo->rawsz; - if (argc != 1) - usage(show_index_usage); if (fread(top_index, 2 * 4, 1, stdin) != 1) die("unable to read header"); if (top_index[0] == htonl(PACK_IDX_SIGNATURE)) { diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c index 595463be68..4003f4d13a 100644 --- a/builtin/sparse-checkout.c +++ b/builtin/sparse-checkout.c @@ -253,6 +253,8 @@ static int set_config(enum sparse_checkout_mode mode) { const char *config_path; + if (upgrade_repository_format(1) < 0) + die(_("unable to upgrade repository format to enable worktreeConfig")); if (git_config_set_gently("extensions.worktreeConfig", "true")) { error(_("failed to set extensions.worktreeConfig setting")); return 1; diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 46c03d2a12..59c1e1217c 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -2277,6 +2277,49 @@ static int module_set_url(int argc, const char **argv, const char *prefix) return 0; } +static int module_set_branch(int argc, const char **argv, const char *prefix) +{ + int opt_default = 0, ret; + const char *opt_branch = NULL; + const char *path; + char *config_name; + + /* + * We accept the `quiet` option for uniformity across subcommands, + * though there is nothing to make less verbose in this subcommand. + */ + struct option options[] = { + OPT_NOOP_NOARG('q', "quiet"), + OPT_BOOL('d', "default", &opt_default, + N_("set the default tracking branch to master")), + OPT_STRING('b', "branch", &opt_branch, N_("branch"), + N_("set the default tracking branch")), + 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>"), + NULL + }; + + argc = parse_options(argc, argv, prefix, options, usage, 0); + + if (!opt_branch && !opt_default) + die(_("--branch or --default required")); + + if (opt_branch && opt_default) + die(_("--branch and --default are mutually exclusive")); + + if (argc != 1 || !(path = argv[0])) + usage_with_options(usage, options); + + config_name = xstrfmt("submodule.%s.branch", path); + ret = config_set_in_gitmodules_file_gently(config_name, opt_branch); + + free(config_name); + return !!ret; +} + #define SUPPORT_SUPER_PREFIX (1<<0) struct cmd_struct { @@ -2308,6 +2351,7 @@ static struct cmd_struct commands[] = { {"check-name", check_name, 0}, {"config", module_config, 0}, {"set-url", module_set_url, 0}, + {"set-branch", module_set_branch, 0}, }; int cmd_submodule__helper(int argc, const char **argv, const char *prefix) diff --git a/builtin/worktree.c b/builtin/worktree.c index d99db35668..1238b6bab1 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -67,7 +67,12 @@ static void delete_worktrees_dir_if_empty(void) rmdir(git_path("worktrees")); /* ignore failed removal */ } -static int prune_worktree(const char *id, struct strbuf *reason) +/* + * Return true if worktree entry should be pruned, along with the reason for + * pruning. Otherwise, return false and the worktree's path, or NULL if it + * cannot be determined. Caller is responsible for freeing returned path. + */ +static int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath) { struct stat st; char *path; @@ -75,20 +80,21 @@ static int prune_worktree(const char *id, struct strbuf *reason) size_t len; ssize_t read_result; + *wtpath = NULL; if (!is_directory(git_path("worktrees/%s", id))) { - strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id); + strbuf_addstr(reason, _("not a valid directory")); return 1; } if (file_exists(git_path("worktrees/%s/locked", id))) return 0; if (stat(git_path("worktrees/%s/gitdir", id), &st)) { - strbuf_addf(reason, _("Removing worktrees/%s: gitdir file does not exist"), id); + strbuf_addstr(reason, _("gitdir file does not exist")); return 1; } fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY); if (fd < 0) { - strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"), - id, strerror(errno)); + strbuf_addf(reason, _("unable to read gitdir file (%s)"), + strerror(errno)); return 1; } len = xsize_t(st.st_size); @@ -96,8 +102,8 @@ static int prune_worktree(const char *id, struct strbuf *reason) read_result = read_in_full(fd, path, len); if (read_result < 0) { - strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"), - id, strerror(errno)); + strbuf_addf(reason, _("unable to read gitdir file (%s)"), + strerror(errno)); close(fd); free(path); return 1; @@ -106,53 +112,103 @@ static int prune_worktree(const char *id, struct strbuf *reason) if (read_result != len) { strbuf_addf(reason, - _("Removing worktrees/%s: short read (expected %"PRIuMAX" bytes, read %"PRIuMAX")"), - id, (uintmax_t)len, (uintmax_t)read_result); + _("short read (expected %"PRIuMAX" bytes, read %"PRIuMAX")"), + (uintmax_t)len, (uintmax_t)read_result); free(path); return 1; } while (len && (path[len - 1] == '\n' || path[len - 1] == '\r')) len--; if (!len) { - strbuf_addf(reason, _("Removing worktrees/%s: invalid gitdir file"), id); + strbuf_addstr(reason, _("invalid gitdir file")); free(path); return 1; } path[len] = '\0'; if (!file_exists(path)) { - free(path); if (stat(git_path("worktrees/%s/index", id), &st) || st.st_mtime <= expire) { - strbuf_addf(reason, _("Removing worktrees/%s: gitdir file points to non-existent location"), id); + strbuf_addstr(reason, _("gitdir file points to non-existent location")); + free(path); return 1; } else { + *wtpath = path; return 0; } } - free(path); + *wtpath = path; return 0; } +static void prune_worktree(const char *id, const char *reason) +{ + if (show_only || verbose) + printf_ln(_("Removing %s/%s: %s"), "worktrees", id, reason); + if (!show_only) + delete_git_dir(id); +} + +static int prune_cmp(const void *a, const void *b) +{ + const struct string_list_item *x = a; + const struct string_list_item *y = b; + int c; + + if ((c = fspathcmp(x->string, y->string))) + return c; + /* + * paths same; prune_dupes() removes all but the first worktree entry + * having the same path, so sort main worktree ('util' is NULL) above + * linked worktrees ('util' not NULL) since main worktree can't be + * removed + */ + if (!x->util) + return -1; + if (!y->util) + return 1; + /* paths same; sort by .git/worktrees/<id> */ + return strcmp(x->util, y->util); +} + +static void prune_dups(struct string_list *l) +{ + int i; + + QSORT(l->items, l->nr, prune_cmp); + for (i = 1; i < l->nr; i++) { + if (!fspathcmp(l->items[i].string, l->items[i - 1].string)) + prune_worktree(l->items[i].util, "duplicate entry"); + } +} + static void prune_worktrees(void) { struct strbuf reason = STRBUF_INIT; + struct strbuf main_path = STRBUF_INIT; + struct string_list kept = STRING_LIST_INIT_NODUP; DIR *dir = opendir(git_path("worktrees")); struct dirent *d; if (!dir) return; while ((d = readdir(dir)) != NULL) { + char *path; if (is_dot_or_dotdot(d->d_name)) continue; strbuf_reset(&reason); - if (!prune_worktree(d->d_name, &reason)) - continue; - if (show_only || verbose) - printf("%s\n", reason.buf); - if (show_only) - continue; - delete_git_dir(d->d_name); + if (should_prune_worktree(d->d_name, &reason, &path)) + prune_worktree(d->d_name, reason.buf); + else if (path) + string_list_append(&kept, path)->util = xstrdup(d->d_name); } closedir(dir); + + strbuf_add_absolute_path(&main_path, get_git_common_dir()); + /* massage main worktree absolute path to match 'gitdir' content */ + strbuf_strip_suffix(&main_path, "/."); + string_list_append(&kept, strbuf_detach(&main_path, NULL)); + prune_dups(&kept); + string_list_clear(&kept, 1); + if (!show_only) delete_worktrees_dir_if_empty(); strbuf_release(&reason); @@ -224,34 +280,33 @@ static const char *worktree_basename(const char *path, int *olen) return name; } -static void validate_worktree_add(const char *path, const struct add_opts *opts) +/* check that path is viable location for worktree */ +static void check_candidate_path(const char *path, + int force, + struct worktree **worktrees, + const char *cmd) { - struct worktree **worktrees; struct worktree *wt; int locked; if (file_exists(path) && !is_empty_dir(path)) die(_("'%s' already exists"), path); - worktrees = get_worktrees(0); wt = find_worktree_by_path(worktrees, path); if (!wt) - goto done; + return; locked = !!worktree_lock_reason(wt); - if ((!locked && opts->force) || (locked && opts->force > 1)) { + if ((!locked && force) || (locked && force > 1)) { if (delete_git_dir(wt->id)) - die(_("unable to re-add worktree '%s'"), path); - goto done; + die(_("unusable worktree destination '%s'"), path); + return; } if (locked) - die(_("'%s' is a missing but locked worktree;\nuse 'add -f -f' to override, or 'unlock' and 'prune' or 'remove' to clear"), path); + die(_("'%s' is a missing but locked worktree;\nuse '%s -f -f' to override, or 'unlock' and 'prune' or 'remove' to clear"), cmd, path); else - die(_("'%s' is a missing but already registered worktree;\nuse 'add -f' to override, or 'prune' or 'remove' to clear"), path); - -done: - free_worktrees(worktrees); + die(_("'%s' is a missing but already registered worktree;\nuse '%s -f' to override, or 'prune' or 'remove' to clear"), cmd, path); } static int add_worktree(const char *path, const char *refname, @@ -268,8 +323,12 @@ static int add_worktree(const char *path, const char *refname, struct commit *commit = NULL; int is_branch = 0; struct strbuf sb_name = STRBUF_INIT; + struct worktree **worktrees; - validate_worktree_add(path, opts); + worktrees = get_worktrees(0); + check_candidate_path(path, opts->force, worktrees, "add"); + free_worktrees(worktrees); + worktrees = NULL; /* is 'refname' a branch or commit? */ if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) && @@ -804,8 +863,7 @@ static int move_worktree(int ac, const char **av, const char *prefix) strbuf_trim_trailing_dir_sep(&dst); strbuf_addstr(&dst, sep); } - if (file_exists(dst.buf)) - die(_("target '%s' already exists"), dst.buf); + check_candidate_path(dst.buf, force, worktrees, "move"); validate_no_submodules(wt); @@ -23,6 +23,17 @@ static void add_to_ref_list(const struct object_id *oid, const char *name, list->nr++; } +static const struct git_hash_algo *detect_hash_algo(struct strbuf *buf) +{ + size_t len = strcspn(buf->buf, " \n"); + int algo; + + algo = hash_algo_by_length(len / 2); + if (algo == GIT_HASH_UNKNOWN) + return NULL; + return &hash_algos[algo]; +} + static int parse_bundle_header(int fd, struct bundle_header *header, const char *report_path) { @@ -52,12 +63,21 @@ static int parse_bundle_header(int fd, struct bundle_header *header, } strbuf_rtrim(&buf); + if (!header->hash_algo) { + header->hash_algo = detect_hash_algo(&buf); + if (!header->hash_algo) { + error(_("unknown hash algorithm length")); + status = -1; + break; + } + } + /* * Tip lines have object name, SP, and refname. * Prerequisites have object name that is optionally * followed by SP and subject line. */ - if (parse_oid_hex(buf.buf, &oid, &p) || + if (parse_oid_hex_algop(buf.buf, &oid, &p, header->hash_algo) || (*p && !isspace(*p)) || (!is_prereq && !*p)) { if (report_path) @@ -15,6 +15,7 @@ struct ref_list { struct bundle_header { struct ref_list prerequisites; struct ref_list references; + const struct git_hash_algo *hash_algo; }; int is_bundle(const char *path, int quiet); @@ -1042,6 +1042,7 @@ struct repository_format { int worktree_config; int is_bare; int hash_algo; + int has_extensions; char *work_tree; struct string_list unknown_extensions; }; diff --git a/commit-reach.c b/commit-reach.c index 4ca7e706a1..1761217663 100644 --- a/commit-reach.c +++ b/commit-reach.c @@ -283,7 +283,9 @@ struct commit_list *repo_get_merge_bases(struct repository *r, /* * Is "commit" a descendant of one of the elements on the "with_commit" list? */ -int is_descendant_of(struct commit *commit, struct commit_list *with_commit) +static int repo_is_descendant_of(struct repository *r, + struct commit *commit, + struct commit_list *with_commit) { if (!with_commit) return 1; @@ -301,13 +303,18 @@ int is_descendant_of(struct commit *commit, struct commit_list *with_commit) other = with_commit->item; with_commit = with_commit->next; - if (in_merge_bases(other, commit)) + if (repo_in_merge_bases_many(r, other, 1, &commit)) return 1; } return 0; } } +int is_descendant_of(struct commit *commit, struct commit_list *with_commit) +{ + return repo_is_descendant_of(the_repository, commit, with_commit); +} + /* * Is "commit" an ancestor of one of the "references"? */ @@ -348,7 +355,15 @@ int repo_in_merge_bases(struct repository *r, struct commit *commit, struct commit *reference) { - return repo_in_merge_bases_many(r, commit, 1, &reference); + int res; + struct commit_list *list = NULL; + struct commit_list **next = &list; + + next = commit_list_append(commit, next); + res = repo_is_descendant_of(r, reference, list); + free_commit_list(list); + + return res; } struct commit_list *reduce_heads(struct commit_list *heads) @@ -396,6 +411,7 @@ int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid) struct object *o; struct commit *old_commit, *new_commit; struct commit_list *old_commit_list = NULL; + int ret; /* * Both new_commit and old_commit must be commit-ish and new_commit is descendant of @@ -417,7 +433,9 @@ int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid) return 0; commit_list_insert(old_commit, &old_commit_list); - return is_descendant_of(new_commit, old_commit_list); + ret = is_descendant_of(new_commit, old_commit_list); + free_commit_list(old_commit_list); + return ret; } /* @@ -18,7 +18,7 @@ static char *server_capabilities_v1; static struct argv_array server_capabilities_v2 = ARGV_ARRAY_INIT; -static const char *parse_feature_value(const char *, const char *, int *); +static const char *next_server_feature_value(const char *feature, int *len, int *offset); static int check_ref(const char *name, unsigned int flags) { @@ -83,6 +83,21 @@ int server_supports_v2(const char *c, int die_on_error) return 0; } +int server_feature_v2(const char *c, const char **v) +{ + int i; + + for (i = 0; i < server_capabilities_v2.argc; i++) { + const char *out; + if (skip_prefix(server_capabilities_v2.argv[i], c, &out) && + (*out == '=')) { + *v = out + 1; + return 1; + } + } + return 0; +} + int server_supports_feature(const char *c, const char *feature, int die_on_error) { @@ -181,17 +196,16 @@ reject: static void annotate_refs_with_symref_info(struct ref *ref) { struct string_list symref = STRING_LIST_INIT_DUP; - const char *feature_list = server_capabilities_v1; + int offset = 0; - while (feature_list) { + while (1) { int len; const char *val; - val = parse_feature_value(feature_list, "symref", &len); + val = next_server_feature_value("symref", &len, &offset); if (!val) break; parse_one_symref_info(&symref, val, len); - feature_list = val + 1; } string_list_sort(&symref); @@ -205,21 +219,36 @@ static void annotate_refs_with_symref_info(struct ref *ref) string_list_clear(&symref, 0); } -static void process_capabilities(const char *line, int *len) +static void process_capabilities(struct packet_reader *reader, int *linelen) { + const char *feat_val; + int feat_len; + const char *line = reader->line; int nul_location = strlen(line); - if (nul_location == *len) + if (nul_location == *linelen) return; server_capabilities_v1 = xstrdup(line + nul_location + 1); - *len = nul_location; + *linelen = nul_location; + + feat_val = server_feature_value("object-format", &feat_len); + if (feat_val) { + char *hash_name = xstrndup(feat_val, feat_len); + int hash_algo = hash_algo_by_name(hash_name); + if (hash_algo != GIT_HASH_UNKNOWN) + reader->hash_algo = &hash_algos[hash_algo]; + free(hash_name); + } else { + reader->hash_algo = &hash_algos[GIT_HASH_SHA1]; + } } -static int process_dummy_ref(const char *line) +static int process_dummy_ref(const struct packet_reader *reader) { + const char *line = reader->line; struct object_id oid; const char *name; - if (parse_oid_hex(line, &oid, &name)) + if (parse_oid_hex_algop(line, &oid, &name, reader->hash_algo)) return 0; if (*name != ' ') return 0; @@ -235,13 +264,15 @@ static void check_no_capabilities(const char *line, int len) line + strlen(line)); } -static int process_ref(const char *line, int len, struct ref ***list, - unsigned int flags, struct oid_array *extra_have) +static int process_ref(const struct packet_reader *reader, int len, + struct ref ***list, unsigned int flags, + struct oid_array *extra_have) { + const char *line = reader->line; struct object_id old_oid; const char *name; - if (parse_oid_hex(line, &old_oid, &name)) + if (parse_oid_hex_algop(line, &old_oid, &name, reader->hash_algo)) return 0; if (*name != ' ') return 0; @@ -261,16 +292,17 @@ static int process_ref(const char *line, int len, struct ref ***list, return 1; } -static int process_shallow(const char *line, int len, +static int process_shallow(const struct packet_reader *reader, int len, struct oid_array *shallow_points) { + const char *line = reader->line; const char *arg; struct object_id old_oid; if (!skip_prefix(line, "shallow ", &arg)) return 0; - if (get_oid_hex(arg, &old_oid)) + if (get_oid_hex_algop(arg, &old_oid, reader->hash_algo)) die(_("protocol error: expected shallow sha-1, got '%s'"), arg); if (!shallow_points) die(_("repository on the other end cannot be shallow")); @@ -317,20 +349,20 @@ struct ref **get_remote_heads(struct packet_reader *reader, switch (state) { case EXPECTING_FIRST_REF: - process_capabilities(reader->line, &len); - if (process_dummy_ref(reader->line)) { + process_capabilities(reader, &len); + if (process_dummy_ref(reader)) { state = EXPECTING_SHALLOW; break; } state = EXPECTING_REF; /* fallthrough */ case EXPECTING_REF: - if (process_ref(reader->line, len, &list, flags, extra_have)) + if (process_ref(reader, len, &list, flags, extra_have)) break; state = EXPECTING_SHALLOW; /* fallthrough */ case EXPECTING_SHALLOW: - if (process_shallow(reader->line, len, shallow_points)) + if (process_shallow(reader, len, shallow_points)) break; die(_("protocol error: unexpected '%s'"), reader->line); case EXPECTING_DONE: @@ -344,7 +376,7 @@ struct ref **get_remote_heads(struct packet_reader *reader, } /* Returns 1 when a valid ref has been added to `list`, 0 otherwise */ -static int process_ref_v2(const char *line, struct ref ***list) +static int process_ref_v2(struct packet_reader *reader, struct ref ***list) { int ret = 1; int i = 0; @@ -352,6 +384,7 @@ static int process_ref_v2(const char *line, struct ref ***list) struct ref *ref; struct string_list line_sections = STRING_LIST_INIT_DUP; const char *end; + const char *line = reader->line; /* * Ref lines have a number of fields which are space deliminated. The @@ -364,7 +397,7 @@ static int process_ref_v2(const char *line, struct ref ***list) goto out; } - if (parse_oid_hex(line_sections.items[i++].string, &old_oid, &end) || + if (parse_oid_hex_algop(line_sections.items[i++].string, &old_oid, &end, reader->hash_algo) || *end) { ret = 0; goto out; @@ -372,7 +405,7 @@ static int process_ref_v2(const char *line, struct ref ***list) ref = alloc_ref(line_sections.items[i++].string); - oidcpy(&ref->old_oid, &old_oid); + memcpy(ref->old_oid.hash, old_oid.hash, reader->hash_algo->rawsz); **list = ref; *list = &ref->next; @@ -385,7 +418,8 @@ static int process_ref_v2(const char *line, struct ref ***list) struct object_id peeled_oid; char *peeled_name; struct ref *peeled; - if (parse_oid_hex(arg, &peeled_oid, &end) || *end) { + if (parse_oid_hex_algop(arg, &peeled_oid, &end, + reader->hash_algo) || *end) { ret = 0; goto out; } @@ -393,7 +427,8 @@ static int process_ref_v2(const char *line, struct ref ***list) peeled_name = xstrfmt("%s^{}", ref->name); peeled = alloc_ref(peeled_name); - oidcpy(&peeled->old_oid, &peeled_oid); + memcpy(peeled->old_oid.hash, peeled_oid.hash, + reader->hash_algo->rawsz); **list = peeled; *list = &peeled->next; @@ -423,6 +458,7 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader, int stateless_rpc) { int i; + const char *hash_name; *list = NULL; if (server_supports_v2("ls-refs", 1)) @@ -431,6 +467,16 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader, if (server_supports_v2("agent", 0)) packet_write_fmt(fd_out, "agent=%s", git_user_agent_sanitized()); + if (server_feature_v2("object-format", &hash_name)) { + int hash_algo = hash_algo_by_name(hash_name); + if (hash_algo == GIT_HASH_UNKNOWN) + die(_("unknown object format '%s' specified by server"), hash_name); + reader->hash_algo = &hash_algos[hash_algo]; + packet_write_fmt(fd_out, "object-format=%s", reader->hash_algo->name); + } else { + reader->hash_algo = &hash_algos[GIT_HASH_SHA1]; + } + if (server_options && server_options->nr && server_supports_v2("server-option", 1)) for (i = 0; i < server_options->nr; i++) @@ -450,7 +496,7 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader, /* Process response from server */ while (packet_reader_read(reader) == PACKET_READ_NORMAL) { - if (!process_ref_v2(reader->line, &list)) + if (!process_ref_v2(reader, &list)) die(_("invalid ls-refs response: %s"), reader->line); } @@ -463,7 +509,7 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader, return list; } -static const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp) +const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp, int *offset) { int len; @@ -471,6 +517,8 @@ static const char *parse_feature_value(const char *feature_list, const char *fea return NULL; len = strlen(feature); + if (offset) + feature_list += *offset; while (*feature_list) { const char *found = strstr(feature_list, feature); if (!found) @@ -485,9 +533,14 @@ static const char *parse_feature_value(const char *feature_list, const char *fea } /* feature with a value (e.g., "agent=git/1.2.3") */ else if (*value == '=') { + int end; + value++; + end = strcspn(value, " \t\n"); if (lenp) - *lenp = strcspn(value, " \t\n"); + *lenp = end; + if (offset) + *offset = value + end - feature_list; return value; } /* @@ -500,14 +553,41 @@ static const char *parse_feature_value(const char *feature_list, const char *fea return NULL; } +int server_supports_hash(const char *desired, int *feature_supported) +{ + int offset = 0; + int len; + const char *hash; + + hash = next_server_feature_value("object-format", &len, &offset); + if (feature_supported) + *feature_supported = !!hash; + if (!hash) { + hash = hash_algos[GIT_HASH_SHA1].name; + len = strlen(hash); + } + while (hash) { + if (!xstrncmpz(desired, hash, len)) + return 1; + + hash = next_server_feature_value("object-format", &len, &offset); + } + return 0; +} + int parse_feature_request(const char *feature_list, const char *feature) { - return !!parse_feature_value(feature_list, feature, NULL); + return !!parse_feature_value(feature_list, feature, NULL, NULL); +} + +static const char *next_server_feature_value(const char *feature, int *len, int *offset) +{ + return parse_feature_value(server_capabilities_v1, feature, len, offset); } const char *server_feature_value(const char *feature, int *len) { - return parse_feature_value(server_capabilities_v1, feature, len); + return parse_feature_value(server_capabilities_v1, feature, len, NULL); } int server_supports(const char *feature) @@ -18,7 +18,10 @@ int url_is_local_not_ssh(const char *url); struct packet_reader; enum protocol_version discover_version(struct packet_reader *reader); +int server_supports_hash(const char *desired, int *feature_supported); +const char *parse_feature_value(const char *feature_list, const char *feature, int *lenp, int *offset); int server_supports_v2(const char *c, int die_on_error); +int server_feature_v2(const char *c, const char **v); int server_supports_feature(const char *c, const char *feature, int die_on_error); diff --git a/connected.c b/connected.c index 3135b71e19..937b4bae38 100644 --- a/connected.c +++ b/connected.c @@ -43,10 +43,12 @@ int check_connected(oid_iterate_fn fn, void *cb_data, if (transport && transport->smart_options && transport->smart_options->self_contained_and_connected && - transport->pack_lockfile && - strip_suffix(transport->pack_lockfile, ".keep", &base_len)) { + transport->pack_lockfiles.nr == 1 && + strip_suffix(transport->pack_lockfiles.items[0].string, + ".keep", &base_len)) { struct strbuf idx_file = STRBUF_INIT; - strbuf_add(&idx_file, transport->pack_lockfile, base_len); + strbuf_add(&idx_file, transport->pack_lockfiles.items[0].string, + base_len); strbuf_addstr(&idx_file, ".idx"); new_pack = add_packed_git(idx_file.buf, idx_file.len, 1); strbuf_release(&idx_file); diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 4b59004847..de5d0fbbd1 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -301,6 +301,19 @@ __gitcomp_direct () COMPREPLY=($1) } +# Similar to __gitcomp_direct, but appends to COMPREPLY instead. +# Callers must take care of providing only words that match the current word +# to be completed and adding any prefix and/or suffix (trailing space!), if +# necessary. +# 1: List of newline-separated matching completion words, complete with +# prefix and suffix. +__gitcomp_direct_append () +{ + local IFS=$'\n' + + COMPREPLY+=($1) +} + __gitcompappend () { local x i=${#COMPREPLY[@]} @@ -611,6 +624,19 @@ __git_heads () "refs/heads/$cur_*" "refs/heads/$cur_*/**" } +# Lists branches from remote repositories. +# 1: A prefix to be added to each listed branch (optional). +# 2: List only branches matching this word (optional; list all branches if +# unset or empty). +# 3: A suffix to be appended to each listed branch (optional). +__git_remote_heads () +{ + local pfx="${1-}" cur_="${2-}" sfx="${3-}" + + __git for-each-ref --format="${pfx//\%/%%}%(refname:strip=2)$sfx" \ + "refs/remotes/$cur_*" "refs/remotes/$cur_*/**" +} + # Lists tags from the local repository. # Accepts the same positional parameters as __git_heads() above. __git_tags () @@ -621,6 +647,26 @@ __git_tags () "refs/tags/$cur_*" "refs/tags/$cur_*/**" } +# List unique branches from refs/remotes used for 'git checkout' and 'git +# switch' tracking DWIMery. +# 1: A prefix to be added to each listed branch (optional) +# 2: List only branches matching this word (optional; list all branches if +# unset or empty). +# 3: A suffix to be appended to each listed branch (optional). +__git_dwim_remote_heads () +{ + local pfx="${1-}" cur_="${2-}" sfx="${3-}" + local fer_pfx="${pfx//\%/%%}" # "escape" for-each-ref format specifiers + + # employ the heuristic used by git checkout and git switch + # Try to find a remote branch that cur_es the completion word + # but only output if the branch name is unique + __git for-each-ref --format="$fer_pfx%(refname:strip=3)$sfx" \ + --sort="refname:strip=3" \ + "refs/remotes/*/$cur_*" "refs/remotes/*/$cur_*/**" | \ + uniq -u +} + # Lists refs from the local (by default) or from a remote repository. # It accepts 0, 1 or 2 arguments: # 1: The remote to list refs from (optional; ignored, if set but empty). @@ -696,13 +742,7 @@ __git_refs () __git_dir="$dir" __git for-each-ref --format="$fer_pfx%($format)$sfx" \ "${refs[@]}" if [ -n "$track" ]; then - # employ the heuristic used by git checkout - # Try to find a remote branch that matches the completion word - # but only output if the branch name is unique - __git for-each-ref --format="$fer_pfx%(refname:strip=3)$sfx" \ - --sort="refname:strip=3" \ - "refs/remotes/*/$match*" "refs/remotes/*/$match*/**" | \ - uniq -u + __git_dwim_remote_heads "$pfx" "$match" "$sfx" fi return fi @@ -749,29 +789,51 @@ __git_refs () # Usage: __git_complete_refs [<option>]... # --remote=<remote>: The remote to list refs from, can be the name of a # configured remote, a path, or a URL. -# --track: List unique remote branches for 'git checkout's tracking DWIMery. +# --dwim: List unique remote branches for 'git switch's tracking DWIMery. # --pfx=<prefix>: A prefix to be added to each ref. # --cur=<word>: The current ref to be completed. Defaults to the current # word to be completed. # --sfx=<suffix>: A suffix to be appended to each ref instead of the default # space. +# --mode=<mode>: What set of refs to complete, one of 'refs' (the default) to +# complete all refs, 'heads' to complete only branches, or +# 'remote-heads' to complete only remote branches. Note that +# --remote is only compatible with --mode=refs. __git_complete_refs () { - local remote track pfx cur_="$cur" sfx=" " + local remote dwim pfx cur_="$cur" sfx=" " mode="refs" while test $# != 0; do case "$1" in --remote=*) remote="${1##--remote=}" ;; - --track) track="yes" ;; + --dwim) dwim="yes" ;; + # --track is an old spelling of --dwim + --track) dwim="yes" ;; --pfx=*) pfx="${1##--pfx=}" ;; --cur=*) cur_="${1##--cur=}" ;; --sfx=*) sfx="${1##--sfx=}" ;; + --mode=*) mode="${1##--mode=}" ;; *) return 1 ;; esac shift done - __gitcomp_direct "$(__git_refs "$remote" "$track" "$pfx" "$cur_" "$sfx")" + # complete references based on the specified mode + case "$mode" in + refs) + __gitcomp_direct "$(__git_refs "$remote" "" "$pfx" "$cur_" "$sfx")" ;; + heads) + __gitcomp_direct "$(__git_heads "$pfx" "$cur_" "$sfx")" ;; + remote-heads) + __gitcomp_direct "$(__git_remote_heads "$pfx" "$cur_" "$sfx")" ;; + *) + return 1 ;; + esac + + # Append DWIM remote branch names if requested + if [ "$dwim" = "yes" ]; then + __gitcomp_direct_append "$(__git_dwim_remote_heads "$pfx" "$cur_" "$sfx")" + fi } # __git_refs2 requires 1 argument (to pass to __git_refs) @@ -1102,6 +1164,40 @@ __git_find_on_cmdline () done } +# Similar to __git_find_on_cmdline, except that it loops backwards and thus +# prints the *last* word found. Useful for finding which of two options that +# supersede each other came last, such as "--guess" and "--no-guess". +# +# Usage: __git_find_last_on_cmdline [<option>]... "<wordlist>" +# --show-idx: Optionally show the index of the found word in the $words array. +__git_find_last_on_cmdline () +{ + local word c=$cword show_idx + + while test $# -gt 1; do + case "$1" in + --show-idx) show_idx=y ;; + *) return 1 ;; + esac + shift + done + local wordlist="$1" + + while [ $c -gt 1 ]; do + ((c--)) + for word in $wordlist; do + if [ "$word" = "${words[c]}" ]; then + if [ -n "$show_idx" ]; then + echo "$c $word" + else + echo "$word" + fi + return + fi + done + done +} + # Echo the value of an option set on the command line or config # # $1: short option name @@ -1356,6 +1452,46 @@ _git_bundle () esac } +# Helper function to decide whether or not we should enable DWIM logic for +# git-switch and git-checkout. +# +# To decide between the following rules in priority order +# 1) the last provided of "--guess" or "--no-guess" explicitly enable or +# disable completion of DWIM logic respectively. +# 2) If the --no-track option is provided, take this as a hint to disable the +# DWIM completion logic +# 3) If GIT_COMPLETION_CHECKOUT_NO_GUESS is set, disable the DWIM completion +# logic, as requested by the user. +# 4) Enable DWIM logic otherwise. +# +__git_checkout_default_dwim_mode () +{ + local last_option dwim_opt="--dwim" + + if [ "$GIT_COMPLETION_CHECKOUT_NO_GUESS" = "1" ]; then + dwim_opt="" + fi + + # --no-track disables DWIM, but with lower priority than + # --guess/--no-guess + if [ -n "$(__git_find_on_cmdline "--no-track")" ]; then + dwim_opt="" + fi + + # Find the last provided --guess or --no-guess + last_option="$(__git_find_last_on_cmdline "--guess --no-guess")" + case "$last_option" in + --guess) + dwim_opt="--dwim" + ;; + --no-guess) + dwim_opt="" + ;; + esac + + echo "$dwim_opt" +} + _git_checkout () { __git_has_doubledash && return @@ -1368,14 +1504,38 @@ _git_checkout () __gitcomp_builtin checkout ;; *) - # check if --track, --no-track, or --no-guess was specified - # if so, disable DWIM mode - local flags="--track --no-track --no-guess" track_opt="--track" - if [ "$GIT_COMPLETION_CHECKOUT_NO_GUESS" = "1" ] || - [ -n "$(__git_find_on_cmdline "$flags")" ]; then - track_opt='' + local dwim_opt="$(__git_checkout_default_dwim_mode)" + local prevword prevword="${words[cword-1]}" + + case "$prevword" in + -b|-B|--orphan) + # Complete local branches (and DWIM branch + # remote branch names) for an option argument + # specifying a new branch name. This is for + # convenience, assuming new branches are + # possibly based on pre-existing branch names. + __git_complete_refs $dwim_opt --mode="heads" + return + ;; + *) + ;; + esac + + # At this point, we've already handled special completion for + # the arguments to -b/-B, and --orphan. There are 3 main + # things left we can possibly complete: + # 1) a start-point for -b/-B, -d/--detach, or --orphan + # 2) a remote head, for --track + # 3) an arbitrary reference, possibly including DWIM names + # + + if [ -n "$(__git_find_on_cmdline "-b -B -d --detach --orphan")" ]; then + __git_complete_refs --mode="refs" + elif [ -n "$(__git_find_on_cmdline "--track")" ]; then + __git_complete_refs --mode="remote-heads" + else + __git_complete_refs $dwim_opt --mode="refs" fi - __git_complete_refs $track_opt ;; esac } @@ -2224,29 +2384,43 @@ _git_switch () __gitcomp_builtin switch ;; *) - # check if --track, --no-track, or --no-guess was specified - # if so, disable DWIM mode - local track_opt="--track" only_local_ref=n - if [ "$GIT_COMPLETION_CHECKOUT_NO_GUESS" = "1" ] || - [ -n "$(__git_find_on_cmdline "--track --no-track --no-guess")" ]; then - track_opt='' - fi - # explicit --guess enables DWIM mode regardless of - # $GIT_COMPLETION_CHECKOUT_NO_GUESS - if [ -n "$(__git_find_on_cmdline "--guess")" ]; then - track_opt='--track' - fi - if [ -z "$(__git_find_on_cmdline "-d --detach")" ]; then - only_local_ref=y - else - # --guess --detach is invalid combination, no - # dwim will be done when --detach is specified - track_opt= + local dwim_opt="$(__git_checkout_default_dwim_mode)" + local prevword prevword="${words[cword-1]}" + + case "$prevword" in + -c|-C|--orphan) + # Complete local branches (and DWIM branch + # remote branch names) for an option argument + # specifying a new branch name. This is for + # convenience, assuming new branches are + # possibly based on pre-existing branch names. + __git_complete_refs $dwim_opt --mode="heads" + return + ;; + *) + ;; + esac + + # Unlike in git checkout, git switch --orphan does not take + # a start point. Thus we really have nothing to complete after + # the branch name. + if [ -n "$(__git_find_on_cmdline "--orphan")" ]; then + return fi - if [ $only_local_ref = y -a -z "$track_opt" ]; then - __gitcomp_direct "$(__git_heads "" "$cur" " ")" + + # At this point, we've already handled special completion for + # -c/-C, and --orphan. There are 3 main things left to + # complete: + # 1) a start-point for -c/-C or -d/--detach + # 2) a remote head, for --track + # 3) a branch name, possibly including DWIM remote branches + + if [ -n "$(__git_find_on_cmdline "-c -C -d --detach")" ]; then + __git_complete_refs --mode="refs" + elif [ -n "$(__git_find_on_cmdline "--track")" ]; then + __git_complete_refs --mode="remote-heads" else - __git_complete_refs $track_opt + __git_complete_refs $dwim_opt --mode="heads" fi ;; esac @@ -193,6 +193,10 @@ int fill_directory(struct dir_struct *dir, const char *prefix; size_t prefix_len; + unsigned exclusive_flags = DIR_SHOW_IGNORED | DIR_SHOW_IGNORED_TOO; + if ((dir->flags & exclusive_flags) == exclusive_flags) + BUG("DIR_SHOW_IGNORED and DIR_SHOW_IGNORED_TOO are exclusive"); + /* * Calculate common prefix for the pathspec, and * use that to optimize the directory walk @@ -1836,7 +1840,7 @@ static enum path_treatment treat_directory(struct dir_struct *dir, * to recurse into untracked/ignored directories if either of the * following bits is set: * - DIR_SHOW_IGNORED_TOO (because then we need to determine if - * there are ignored directories below) + * there are ignored entries below) * - DIR_HIDE_EMPTY_DIRECTORIES (because we have to determine if * the directory is empty) */ @@ -1854,10 +1858,11 @@ static enum path_treatment treat_directory(struct dir_struct *dir, return path_excluded; /* - * If we have we don't want to know the all the paths under an - * untracked or ignored directory, we still need to go into the - * directory to determine if it is empty (because an empty directory - * should be path_none instead of path_excluded or path_untracked). + * Even if we don't want to know all the paths under an untracked or + * ignored directory, we may still need to go into the directory to + * determine if it is empty (because with DIR_HIDE_EMPTY_DIRECTORIES, + * an empty directory should be path_none instead of path_excluded or + * path_untracked). */ check_only = ((dir->flags & DIR_HIDE_EMPTY_DIRECTORIES) && !(dir->flags & DIR_SHOW_IGNORED_TOO)); diff --git a/fetch-pack.c b/fetch-pack.c index d8bbf45ee2..80fb3bd899 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -38,6 +38,7 @@ static int server_supports_filtering; static struct shallow_lock shallow_lock; static const char *alternate_shallow_file; static struct strbuf fsck_msg_types = STRBUF_INIT; +static struct string_list uri_protocols = STRING_LIST_INIT_DUP; /* Remember to update object flag allocation in object.h */ #define COMPLETE (1U << 0) @@ -794,7 +795,8 @@ static void write_promisor_file(const char *keep_name, } static int get_pack(struct fetch_pack_args *args, - int xd[2], char **pack_lockfile, + int xd[2], struct string_list *pack_lockfiles, + int only_packfile, struct ref **sought, int nr_sought) { struct async demux; @@ -838,7 +840,7 @@ static int get_pack(struct fetch_pack_args *args, } if (do_keep || args->from_promisor) { - if (pack_lockfile) + if (pack_lockfiles) cmd.out = -1; cmd_name = "index-pack"; argv_array_push(&cmd.args, cmd_name); @@ -855,15 +857,22 @@ static int get_pack(struct fetch_pack_args *args, "--keep=fetch-pack %"PRIuMAX " on %s", (uintmax_t)getpid(), hostname); } - if (args->check_self_contained_and_connected) + if (only_packfile && args->check_self_contained_and_connected) argv_array_push(&cmd.args, "--check-self-contained-and-connected"); + else + /* + * We cannot perform any connectivity checks because + * not all packs have been downloaded; let the caller + * have this responsibility. + */ + args->check_self_contained_and_connected = 0; /* * If we're obtaining the filename of a lockfile, we'll use * that filename to write a .promisor file with more * information below. If not, we need index-pack to do it for * us. */ - if (!(do_keep && pack_lockfile) && args->from_promisor) + if (!(do_keep && pack_lockfiles) && args->from_promisor) argv_array_push(&cmd.args, "--promisor"); } else { @@ -899,8 +908,9 @@ static int get_pack(struct fetch_pack_args *args, cmd.git_cmd = 1; if (start_command(&cmd)) die(_("fetch-pack: unable to fork off %s"), cmd_name); - if (do_keep && pack_lockfile) { - *pack_lockfile = index_pack_lockfile(cmd.out); + if (do_keep && pack_lockfiles) { + string_list_append_nodup(pack_lockfiles, + index_pack_lockfile(cmd.out)); close(cmd.out); } @@ -922,8 +932,8 @@ static int get_pack(struct fetch_pack_args *args, * Now that index-pack has succeeded, write the promisor file using the * obtained .keep filename if necessary */ - if (do_keep && pack_lockfile && args->from_promisor) - write_promisor_file(*pack_lockfile, sought, nr_sought); + if (do_keep && pack_lockfiles && pack_lockfiles->nr && args->from_promisor) + write_promisor_file(pack_lockfiles->items[0].string, sought, nr_sought); return 0; } @@ -940,7 +950,7 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args, const struct ref *orig_ref, struct ref **sought, int nr_sought, struct shallow_info *si, - char **pack_lockfile) + struct string_list *pack_lockfiles) { struct repository *r = the_repository; struct ref *ref = copy_ref_list(orig_ref); @@ -1040,6 +1050,8 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args, print_verbose(args, _("Server supports %s"), "deepen-relative"); else if (args->deepen_relative) die(_("Server does not support --deepen")); + if (!server_supports_hash(the_hash_algo->name, NULL)) + die(_("Server does not support this repository's object format")); if (!args->no_dependents) { mark_complete_and_common_ref(negotiator, args, &ref); @@ -1067,7 +1079,7 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args, alternate_shallow_file = setup_temporary_shallow(si->shallow); else alternate_shallow_file = NULL; - if (get_pack(args, fd, pack_lockfile, sought, nr_sought)) + if (get_pack(args, fd, pack_lockfiles, 1, sought, nr_sought)) die(_("git fetch-pack: fetch failed.")); all_done: @@ -1178,6 +1190,7 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out, int sideband_all, int seen_ack) { int ret = 0; + const char *hash_name; struct strbuf req_buf = STRBUF_INIT; if (server_supports_v2("fetch", 1)) @@ -1192,6 +1205,17 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out, args->server_options->items[i].string); } + if (server_feature_v2("object-format", &hash_name)) { + int hash_algo = hash_algo_by_name(hash_name); + if (hash_algo_by_ptr(the_hash_algo) != hash_algo) + die(_("mismatched algorithms: client %s; server %s"), + the_hash_algo->name, hash_name); + packet_write_fmt(fd_out, "object-format=%s", the_hash_algo->name); + } else if (hash_algo_by_ptr(the_hash_algo) != GIT_HASH_SHA1) { + die(_("the server does not support algorithm '%s'"), + the_hash_algo->name); + } + packet_buf_delim(&req_buf); if (args->use_thin_pack) packet_buf_write(&req_buf, "thin-pack"); @@ -1221,6 +1245,26 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out, warning("filtering not recognized by server, ignoring"); } + if (server_supports_feature("fetch", "packfile-uris", 0)) { + int i; + struct strbuf to_send = STRBUF_INIT; + + for (i = 0; i < uri_protocols.nr; i++) { + const char *s = uri_protocols.items[i].string; + + if (!strcmp(s, "https") || !strcmp(s, "http")) { + if (to_send.len) + strbuf_addch(&to_send, ','); + strbuf_addstr(&to_send, s); + } + } + if (to_send.len) { + packet_buf_write(&req_buf, "packfile-uris %s", + to_send.buf); + strbuf_release(&to_send); + } + } + /* add wants */ add_wants(args->no_dependents, wants, &req_buf); @@ -1443,6 +1487,21 @@ static void receive_wanted_refs(struct packet_reader *reader, die(_("error processing wanted refs: %d"), reader->status); } +static void receive_packfile_uris(struct packet_reader *reader, + struct string_list *uris) +{ + process_section_header(reader, "packfile-uris", 0); + while (packet_reader_read(reader) == PACKET_READ_NORMAL) { + if (reader->pktlen < the_hash_algo->hexsz || + reader->line[the_hash_algo->hexsz] != ' ') + die("expected '<hash> <uri>', got: %s\n", reader->line); + + string_list_append(uris, reader->line); + } + if (reader->status != PACKET_READ_DELIM) + die("expected DELIM"); +} + enum fetch_state { FETCH_CHECK_LOCAL = 0, FETCH_SEND_REQUEST, @@ -1464,7 +1523,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args, struct ref **sought, int nr_sought, struct oid_array *shallows, struct shallow_info *si, - char **pack_lockfile) + struct string_list *pack_lockfiles) { struct repository *r = the_repository; struct ref *ref = copy_ref_list(orig_ref); @@ -1476,6 +1535,8 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args, struct fetch_negotiator negotiator_alloc; struct fetch_negotiator *negotiator; int seen_ack = 0; + struct string_list packfile_uris = STRING_LIST_INIT_DUP; + int i; if (args->no_dependents) { negotiator = NULL; @@ -1569,9 +1630,12 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args, if (process_section_header(&reader, "wanted-refs", 1)) receive_wanted_refs(&reader, sought, nr_sought); - /* get the pack */ + /* get the pack(s) */ + if (process_section_header(&reader, "packfile-uris", 1)) + receive_packfile_uris(&reader, &packfile_uris); process_section_header(&reader, "packfile", 0); - if (get_pack(args, fd, pack_lockfile, sought, nr_sought)) + if (get_pack(args, fd, pack_lockfiles, + !packfile_uris.nr, sought, nr_sought)) die(_("git fetch-pack: fetch failed.")); do_check_stateless_delimiter(args, &reader); @@ -1582,8 +1646,55 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args, } } + for (i = 0; i < packfile_uris.nr; i++) { + struct child_process cmd = CHILD_PROCESS_INIT; + char packname[GIT_MAX_HEXSZ + 1]; + const char *uri = packfile_uris.items[i].string + + the_hash_algo->hexsz + 1; + + argv_array_push(&cmd.args, "http-fetch"); + argv_array_pushf(&cmd.args, "--packfile=%.*s", + (int) the_hash_algo->hexsz, + packfile_uris.items[i].string); + argv_array_push(&cmd.args, uri); + cmd.git_cmd = 1; + cmd.no_stdin = 1; + cmd.out = -1; + if (start_command(&cmd)) + die("fetch-pack: unable to spawn http-fetch"); + + if (read_in_full(cmd.out, packname, 5) < 0 || + memcmp(packname, "keep\t", 5)) + die("fetch-pack: expected keep then TAB at start of http-fetch output"); + + if (read_in_full(cmd.out, packname, + the_hash_algo->hexsz + 1) < 0 || + packname[the_hash_algo->hexsz] != '\n') + die("fetch-pack: expected hash then LF at end of http-fetch output"); + + packname[the_hash_algo->hexsz] = '\0'; + + close(cmd.out); + + if (finish_command(&cmd)) + die("fetch-pack: unable to finish http-fetch"); + + if (memcmp(packfile_uris.items[i].string, packname, + the_hash_algo->hexsz)) + die("fetch-pack: pack downloaded from %s does not match expected hash %.*s", + uri, (int) the_hash_algo->hexsz, + packfile_uris.items[i].string); + + string_list_append_nodup(pack_lockfiles, + xstrfmt("%s/pack/pack-%s.keep", + get_object_directory(), + packname)); + } + string_list_clear(&packfile_uris, 0); + if (negotiator) negotiator->release(negotiator); + oidset_clear(&common); return ref; } @@ -1620,6 +1731,14 @@ static void fetch_pack_config(void) git_config_get_bool("repack.usedeltabaseoffset", &prefer_ofs_delta); git_config_get_bool("fetch.fsckobjects", &fetch_fsck_objects); git_config_get_bool("transfer.fsckobjects", &transfer_fsck_objects); + if (!uri_protocols.nr) { + char *str; + + if (!git_config_get_string("fetch.uriprotocols", &str) && str) { + string_list_split(&uri_protocols, str, ',', -1); + free(str); + } + } git_config(fetch_pack_config_cb, NULL); } @@ -1772,7 +1891,7 @@ struct ref *fetch_pack(struct fetch_pack_args *args, const struct ref *ref, struct ref **sought, int nr_sought, struct oid_array *shallow, - char **pack_lockfile, + struct string_list *pack_lockfiles, enum protocol_version version) { struct ref *ref_cpy; @@ -1807,11 +1926,11 @@ struct ref *fetch_pack(struct fetch_pack_args *args, memset(&si, 0, sizeof(si)); ref_cpy = do_fetch_pack_v2(args, fd, ref, sought, nr_sought, &shallows_scratch, &si, - pack_lockfile); + pack_lockfiles); } else { prepare_shallow_info(&si, shallow); ref_cpy = do_fetch_pack(args, fd, ref, sought, nr_sought, - &si, pack_lockfile); + &si, pack_lockfiles); } reprepare_packed_git(the_repository); diff --git a/fetch-pack.h b/fetch-pack.h index 67f684229a..85d1e39fe7 100644 --- a/fetch-pack.h +++ b/fetch-pack.h @@ -83,7 +83,7 @@ struct ref *fetch_pack(struct fetch_pack_args *args, struct ref **sought, int nr_sought, struct oid_array *shallow, - char **pack_lockfile, + struct string_list *pack_lockfiles, enum protocol_version version); /* diff --git a/git-compat-util.h b/git-compat-util.h index a73632e8e4..5637114b8d 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -869,6 +869,12 @@ FILE *fopen_for_writing(const char *path); FILE *fopen_or_warn(const char *path, const char *mode); /* + * Like strncmp, but only return zero if s is NUL-terminated and exactly len + * characters long. If it is not, consider it greater than t. + */ +int xstrncmpz(const char *s, const char *t, size_t len); + +/* * FREE_AND_NULL(ptr) is like free(ptr) followed by ptr = NULL. Note * that ptr is used twice, so don't pass e.g. ptr++. */ diff --git a/git-submodule.sh b/git-submodule.sh index 39ebdf25b5..43eb6051d2 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -719,7 +719,7 @@ cmd_update() # $@ = requested path # cmd_set_branch() { - unset_branch=false + default= branch= while test $# -ne 0 @@ -729,7 +729,7 @@ cmd_set_branch() { # we don't do anything with this but we need to accept it ;; -d|--default) - unset_branch=true + default=1 ;; -b|--branch) case "$2" in '') usage ;; esac @@ -750,33 +750,7 @@ cmd_set_branch() { shift done - if test $# -ne 1 - then - usage - fi - - # we can't use `git submodule--helper name` here because internally, it - # hashes the path so a trailing slash could lead to an unintentional no match - name="$(git submodule--helper list "$1" | cut -f2)" - if test -z "$name" - then - exit 1 - fi - - test -n "$branch"; has_branch=$? - test "$unset_branch" = true; has_unset_branch=$? - - if test $((!$has_branch != !$has_unset_branch)) -eq 0 - then - usage - fi - - if test $has_branch -eq 0 - then - git submodule--helper config submodule."$name".branch "$branch" - else - git submodule--helper config --unset submodule."$name".branch - fi + git ${wt_prefix:+-C "$wt_prefix"} ${prefix:+--super-prefix "$prefix"} submodule--helper set-branch ${GIT_QUIET:+--quiet} ${branch:+--branch "$branch"} ${default:+--default} -- "$@" } # @@ -574,7 +574,7 @@ static struct cmd_struct commands[] = { { "shortlog", cmd_shortlog, RUN_SETUP_GENTLY | USE_PAGER }, { "show", cmd_show, RUN_SETUP }, { "show-branch", cmd_show_branch, RUN_SETUP }, - { "show-index", cmd_show_index }, + { "show-index", cmd_show_index, RUN_SETUP_GENTLY }, { "show-ref", cmd_show_ref, RUN_SETUP }, { "sparse-checkout", cmd_sparse_checkout, RUN_SETUP | NEED_WORK_TREE }, { "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE }, diff --git a/http-fetch.c b/http-fetch.c index a32ac118d9..1df376e745 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -5,22 +5,90 @@ #include "walker.h" static const char http_fetch_usage[] = "git http-fetch " -"[-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin] commit-id url"; +"[-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin | --packfile=hash | commit-id] url"; -int cmd_main(int argc, const char **argv) +static int fetch_using_walker(const char *raw_url, int get_verbosely, + int get_recover, int commits, char **commit_id, + const char **write_ref, int commits_on_stdin) { + char *url = NULL; struct walker *walker; + int rc; + + str_end_url_with_slash(raw_url, &url); + + http_init(NULL, url, 0); + + walker = get_http_walker(url); + walker->get_verbosely = get_verbosely; + walker->get_recover = get_recover; + walker->get_progress = 0; + + rc = walker_fetch(walker, commits, commit_id, write_ref, url); + + if (commits_on_stdin) + walker_targets_free(commits, commit_id, write_ref); + + if (walker->corrupt_object_found) { + fprintf(stderr, +"Some loose object were found to be corrupt, but they might be just\n" +"a false '404 Not Found' error message sent with incorrect HTTP\n" +"status code. Suggest running 'git fsck'.\n"); + } + + walker_free(walker); + http_cleanup(); + free(url); + + return rc; +} + +static void fetch_single_packfile(struct object_id *packfile_hash, + const char *url) { + struct http_pack_request *preq; + struct slot_results results; + int ret; + + http_init(NULL, url, 0); + + preq = new_direct_http_pack_request(packfile_hash->hash, xstrdup(url)); + if (preq == NULL) + die("couldn't create http pack request"); + preq->slot->results = &results; + preq->generate_keep = 1; + + if (start_active_slot(preq->slot)) { + run_active_slot(preq->slot); + if (results.curl_result != CURLE_OK) { + die("Unable to get pack file %s\n%s", preq->url, + curl_errorstr); + } + } else { + die("Unable to start request"); + } + + if ((ret = finish_http_pack_request(preq))) + die("finish_http_pack_request gave result %d", ret); + + release_http_pack_request(preq); + http_cleanup(); +} + +int cmd_main(int argc, const char **argv) +{ int commits_on_stdin = 0; int commits; const char **write_ref = NULL; char **commit_id; - char *url = NULL; int arg = 1; - int rc = 0; int get_verbosely = 0; int get_recover = 0; + int packfile = 0; + struct object_id packfile_hash; while (arg < argc && argv[arg][0] == '-') { + const char *p; + if (argv[arg][1] == 't') { } else if (argv[arg][1] == 'c') { } else if (argv[arg][1] == 'a') { @@ -35,46 +103,34 @@ int cmd_main(int argc, const char **argv) get_recover = 1; } else if (!strcmp(argv[arg], "--stdin")) { commits_on_stdin = 1; + } else if (skip_prefix(argv[arg], "--packfile=", &p)) { + const char *end; + + packfile = 1; + if (parse_oid_hex(p, &packfile_hash, &end) || *end) + die(_("argument to --packfile must be a valid hash (got '%s')"), p); } arg++; } - if (argc != arg + 2 - commits_on_stdin) + if (argc != arg + 2 - (commits_on_stdin || packfile)) usage(http_fetch_usage); - if (commits_on_stdin) { - commits = walker_targets_stdin(&commit_id, &write_ref); - } else { - commit_id = (char **) &argv[arg++]; - commits = 1; - } - - if (argv[arg]) - str_end_url_with_slash(argv[arg], &url); setup_git_directory(); git_config(git_default_config, NULL); - http_init(NULL, url, 0); - walker = get_http_walker(url); - walker->get_verbosely = get_verbosely; - walker->get_recover = get_recover; - - rc = walker_fetch(walker, commits, commit_id, write_ref, url); - - if (commits_on_stdin) - walker_targets_free(commits, commit_id, write_ref); - - if (walker->corrupt_object_found) { - fprintf(stderr, -"Some loose object were found to be corrupt, but they might be just\n" -"a false '404 Not Found' error message sent with incorrect HTTP\n" -"status code. Suggest running 'git fsck'.\n"); + if (packfile) { + fetch_single_packfile(&packfile_hash, argv[arg]); + return 0; } - walker_free(walker); - http_cleanup(); - - free(url); - - return rc; + if (commits_on_stdin) { + commits = walker_targets_stdin(&commit_id, &write_ref); + } else { + commit_id = (char **) &argv[arg++]; + commits = 1; + } + return fetch_using_walker(argv[arg], get_verbosely, get_recover, + commits, commit_id, write_ref, + commits_on_stdin); } diff --git a/http-push.c b/http-push.c index 822f326599..ac7868ffee 100644 --- a/http-push.c +++ b/http-push.c @@ -117,6 +117,7 @@ enum transfer_state { struct transfer_request { struct object *obj; + struct packed_git *target; char *url; char *dest; struct remote_lock *lock; @@ -314,17 +315,18 @@ static void start_fetch_packed(struct transfer_request *request) release_request(request); return; } + close_pack_index(target); + request->target = target; fprintf(stderr, "Fetching pack %s\n", hash_to_hex(target->hash)); fprintf(stderr, " which contains %s\n", oid_to_hex(&request->obj->oid)); - preq = new_http_pack_request(target, repo->url); + preq = new_http_pack_request(target->hash, repo->url); if (preq == NULL) { repo->can_update_info_refs = 0; return; } - preq->lst = &repo->packs; /* Make sure there isn't another open request for this pack */ while (check_request) { @@ -597,6 +599,8 @@ static void finish_request(struct transfer_request *request) } if (fail) repo->can_update_info_refs = 0; + else + http_install_packfile(request->target, &repo->packs); release_request(request); } } diff --git a/http-walker.c b/http-walker.c index fe15e325fa..4fb1235cd4 100644 --- a/http-walker.c +++ b/http-walker.c @@ -439,6 +439,7 @@ static int http_fetch_pack(struct walker *walker, struct alt_base *repo, unsigne target = find_sha1_pack(sha1, repo->packs); if (!target) return -1; + close_pack_index(target); if (walker->get_verbosely) { fprintf(stderr, "Getting pack %s\n", @@ -447,10 +448,9 @@ static int http_fetch_pack(struct walker *walker, struct alt_base *repo, unsigne hash_to_hex(sha1)); } - preq = new_http_pack_request(target, repo->base); + preq = new_http_pack_request(target->hash, repo->base); if (preq == NULL) goto abort; - preq->lst = &repo->packs; preq->slot->results = &results; if (start_active_slot(preq->slot)) { @@ -469,6 +469,7 @@ static int http_fetch_pack(struct walker *walker, struct alt_base *repo, unsigne release_http_pack_request(preq); if (ret) return ret; + http_install_packfile(target, &repo->packs); return 0; @@ -18,7 +18,7 @@ static struct trace_key trace_curl = TRACE_KEY_INIT(CURL); static int trace_curl_data = 1; -static struct string_list cookies_to_redact = STRING_LIST_INIT_DUP; +static int trace_curl_redact = 1; #if LIBCURL_VERSION_NUM >= 0x070a08 long int git_curl_ipresolve = CURL_IPRESOLVE_WHATEVER; #else @@ -642,8 +642,9 @@ static void redact_sensitive_header(struct strbuf *header) { const char *sensitive_header; - if (skip_prefix(header->buf, "Authorization:", &sensitive_header) || - skip_prefix(header->buf, "Proxy-Authorization:", &sensitive_header)) { + if (trace_curl_redact && + (skip_prefix(header->buf, "Authorization:", &sensitive_header) || + skip_prefix(header->buf, "Proxy-Authorization:", &sensitive_header))) { /* The first token is the type, which is OK to log */ while (isspace(*sensitive_header)) sensitive_header++; @@ -652,20 +653,15 @@ static void redact_sensitive_header(struct strbuf *header) /* Everything else is opaque and possibly sensitive */ strbuf_setlen(header, sensitive_header - header->buf); strbuf_addstr(header, " <redacted>"); - } else if (cookies_to_redact.nr && + } else if (trace_curl_redact && skip_prefix(header->buf, "Cookie:", &sensitive_header)) { struct strbuf redacted_header = STRBUF_INIT; - char *cookie; + const char *cookie; while (isspace(*sensitive_header)) sensitive_header++; - /* - * The contents of header starting from sensitive_header will - * subsequently be overridden, so it is fine to mutate this - * string (hence the assignment to "char *"). - */ - cookie = (char *) sensitive_header; + cookie = sensitive_header; while (cookie) { char *equals; @@ -678,14 +674,8 @@ static void redact_sensitive_header(struct strbuf *header) strbuf_addstr(&redacted_header, cookie); continue; } - *equals = 0; /* temporarily set to NUL for lookup */ - if (string_list_lookup(&cookies_to_redact, cookie)) { - strbuf_addstr(&redacted_header, cookie); - strbuf_addstr(&redacted_header, "=<redacted>"); - } else { - *equals = '='; - strbuf_addstr(&redacted_header, cookie); - } + strbuf_add(&redacted_header, cookie, equals - cookie); + strbuf_addstr(&redacted_header, "=<redacted>"); if (semicolon) { /* * There are more cookies. (Or, for some @@ -1003,11 +993,8 @@ static CURL *get_curl_handle(void) setup_curl_trace(result); if (getenv("GIT_TRACE_CURL_NO_DATA")) trace_curl_data = 0; - if (getenv("GIT_REDACT_COOKIES")) { - string_list_split(&cookies_to_redact, - getenv("GIT_REDACT_COOKIES"), ',', -1); - string_list_sort(&cookies_to_redact); - } + if (!git_env_bool("GIT_TRACE_REDACT", 1)) + trace_curl_redact = 0; curl_easy_setopt(result, CURLOPT_USERAGENT, user_agent ? user_agent : git_user_agent()); @@ -2274,70 +2261,74 @@ void release_http_pack_request(struct http_pack_request *preq) int finish_http_pack_request(struct http_pack_request *preq) { - struct packed_git **lst; - struct packed_git *p = preq->target; - char *tmp_idx; - size_t len; struct child_process ip = CHILD_PROCESS_INIT; - - close_pack_index(p); + int tmpfile_fd; + int ret = 0; fclose(preq->packfile); preq->packfile = NULL; - lst = preq->lst; - while (*lst != p) - lst = &((*lst)->next); - *lst = (*lst)->next; - - if (!strip_suffix(preq->tmpfile.buf, ".pack.temp", &len)) - BUG("pack tmpfile does not end in .pack.temp?"); - tmp_idx = xstrfmt("%.*s.idx.temp", (int)len, preq->tmpfile.buf); + tmpfile_fd = xopen(preq->tmpfile.buf, O_RDONLY); argv_array_push(&ip.args, "index-pack"); - argv_array_pushl(&ip.args, "-o", tmp_idx, NULL); - argv_array_push(&ip.args, preq->tmpfile.buf); + argv_array_push(&ip.args, "--stdin"); ip.git_cmd = 1; - ip.no_stdin = 1; - ip.no_stdout = 1; + ip.in = tmpfile_fd; + if (preq->generate_keep) { + argv_array_pushf(&ip.args, "--keep=git %"PRIuMAX, + (uintmax_t)getpid()); + ip.out = 0; + } else { + ip.no_stdout = 1; + } if (run_command(&ip)) { - unlink(preq->tmpfile.buf); - unlink(tmp_idx); - free(tmp_idx); - return -1; + ret = -1; + goto cleanup; } - unlink(sha1_pack_index_name(p->hash)); +cleanup: + close(tmpfile_fd); + unlink(preq->tmpfile.buf); + return ret; +} + +void http_install_packfile(struct packed_git *p, + struct packed_git **list_to_remove_from) +{ + struct packed_git **lst = list_to_remove_from; - if (finalize_object_file(preq->tmpfile.buf, sha1_pack_name(p->hash)) - || finalize_object_file(tmp_idx, sha1_pack_index_name(p->hash))) { - free(tmp_idx); - return -1; - } + while (*lst != p) + lst = &((*lst)->next); + *lst = (*lst)->next; install_packed_git(the_repository, p); - free(tmp_idx); - return 0; } struct http_pack_request *new_http_pack_request( - struct packed_git *target, const char *base_url) + const unsigned char *packed_git_hash, const char *base_url) { + + struct strbuf buf = STRBUF_INIT; + + end_url_with_slash(&buf, base_url); + strbuf_addf(&buf, "objects/pack/pack-%s.pack", + hash_to_hex(packed_git_hash)); + return new_direct_http_pack_request(packed_git_hash, + strbuf_detach(&buf, NULL)); +} + +struct http_pack_request *new_direct_http_pack_request( + const unsigned char *packed_git_hash, char *url) { off_t prev_posn = 0; - struct strbuf buf = STRBUF_INIT; struct http_pack_request *preq; preq = xcalloc(1, sizeof(*preq)); strbuf_init(&preq->tmpfile, 0); - preq->target = target; - end_url_with_slash(&buf, base_url); - strbuf_addf(&buf, "objects/pack/pack-%s.pack", - hash_to_hex(target->hash)); - preq->url = strbuf_detach(&buf, NULL); + preq->url = url; - strbuf_addf(&preq->tmpfile, "%s.temp", sha1_pack_name(target->hash)); + strbuf_addf(&preq->tmpfile, "%s.temp", sha1_pack_name(packed_git_hash)); preq->packfile = fopen(preq->tmpfile.buf, "a"); if (!preq->packfile) { error("Unable to open local file %s for pack", @@ -2361,7 +2352,7 @@ struct http_pack_request *new_http_pack_request( if (http_is_verbose) fprintf(stderr, "Resuming fetch of pack %s at byte %"PRIuMAX"\n", - hash_to_hex(target->hash), + hash_to_hex(packed_git_hash), (uintmax_t)prev_posn); http_opt_request_remainder(preq->slot->curl, prev_posn); } @@ -216,18 +216,36 @@ int http_get_info_packs(const char *base_url, struct http_pack_request { char *url; - struct packed_git *target; - struct packed_git **lst; + + /* + * If this is true, finish_http_pack_request() will pass "--keep" to + * index-pack, resulting in the creation of a keep file, and will not + * suppress its stdout (that is, the "keep\t<hash>\n" line will be + * printed to stdout). + */ + unsigned generate_keep : 1; + FILE *packfile; struct strbuf tmpfile; struct active_request_slot *slot; }; struct http_pack_request *new_http_pack_request( - struct packed_git *target, const char *base_url); + const unsigned char *packed_git_hash, const char *base_url); +struct http_pack_request *new_direct_http_pack_request( + const unsigned char *packed_git_hash, char *url); int finish_http_pack_request(struct http_pack_request *preq); void release_http_pack_request(struct http_pack_request *preq); +/* + * Remove p from the given list, and invoke install_packed_git() on it. + * + * This is a convenience function for users that have obtained a list of packs + * from http_get_info_packs() and have chosen a specific pack to fetch. + */ +void http_install_packfile(struct packed_git *p, + struct packed_git **list_to_remove_from); + /* Helpers for fetching object */ struct http_object_request { char *url; diff --git a/list-objects-filter-options.c b/list-objects-filter-options.c index 256bcfbdfe..3553ad7b0a 100644 --- a/list-objects-filter-options.c +++ b/list-objects-filter-options.c @@ -326,7 +326,8 @@ void partial_clone_register( /* Check if it is already registered */ if (!promisor_remote_find(remote)) { - git_config_set("core.repositoryformatversion", "1"); + if (upgrade_repository_format(1) < 0) + die(_("unable to upgrade repository format to support partial clone")); /* Add promisor config for the remote */ cfg_name = xstrfmt("remote.%s.promisor", remote); diff --git a/object-store.h b/object-store.h index d1e490f203..f439d47af8 100644 --- a/object-store.h +++ b/object-store.h @@ -70,6 +70,7 @@ struct packed_git { size_t index_size; uint32_t num_objects; uint32_t num_bad_objects; + uint32_t crc_offset; unsigned char *bad_object_sha1; int index_version; time_t mtime; diff --git a/packfile.c b/packfile.c index f4e752996d..6ab5233613 100644 --- a/packfile.c +++ b/packfile.c @@ -178,6 +178,7 @@ int load_idx(const char *path, const unsigned int hashsz, void *idx_map, */ (sizeof(off_t) <= 4)) return error("pack too large for current definition of off_t in %s", path); + p->crc_offset = 8 + 4 * 256 + nr * hashsz; } p->index_version = version; diff --git a/pkt-line.c b/pkt-line.c index 8f9bc68ee2..844c253ccd 100644 --- a/pkt-line.c +++ b/pkt-line.c @@ -490,6 +490,7 @@ void packet_reader_init(struct packet_reader *reader, int fd, reader->buffer_size = sizeof(packet_buffer); reader->options = options; reader->me = "git"; + reader->hash_algo = &hash_algos[GIT_HASH_SHA1]; } enum packet_read_status packet_reader_read(struct packet_reader *reader) diff --git a/pkt-line.h b/pkt-line.h index 5b373fe4cd..8c90daa59e 100644 --- a/pkt-line.h +++ b/pkt-line.h @@ -177,6 +177,9 @@ struct packet_reader { unsigned use_sideband : 1; const char *me; + + /* hash algorithm in use */ + const struct git_hash_algo *hash_algo; }; /* @@ -9,6 +9,7 @@ #include "iterator.h" #include "refs.h" #include "refs/refs-internal.h" +#include "run-command.h" #include "object-store.h" #include "object.h" #include "tag.h" @@ -16,6 +17,7 @@ #include "worktree.h" #include "argv-array.h" #include "repository.h" +#include "sigchain.h" /* * List of all available backends @@ -1986,10 +1988,65 @@ int ref_update_reject_duplicates(struct string_list *refnames, return 0; } +static const char hook_not_found; +static const char *hook; + +static int run_transaction_hook(struct ref_transaction *transaction, + const char *state) +{ + struct child_process proc = CHILD_PROCESS_INIT; + struct strbuf buf = STRBUF_INIT; + int ret = 0, i; + + if (hook == &hook_not_found) + return ret; + if (!hook) + hook = find_hook("reference-transaction"); + if (!hook) { + hook = &hook_not_found; + return ret; + } + + argv_array_pushl(&proc.args, hook, state, NULL); + proc.in = -1; + proc.stdout_to_stderr = 1; + proc.trace2_hook_name = "reference-transaction"; + + ret = start_command(&proc); + if (ret) + return ret; + + sigchain_push(SIGPIPE, SIG_IGN); + + for (i = 0; i < transaction->nr; i++) { + struct ref_update *update = transaction->updates[i]; + + strbuf_reset(&buf); + strbuf_addf(&buf, "%s %s %s\n", + oid_to_hex(&update->old_oid), + oid_to_hex(&update->new_oid), + update->refname); + + if (write_in_full(proc.in, buf.buf, buf.len) < 0) { + if (errno != EPIPE) + ret = -1; + break; + } + } + + close(proc.in); + sigchain_pop(SIGPIPE); + strbuf_release(&buf); + + ret |= finish_command(&proc); + return ret; +} + int ref_transaction_prepare(struct ref_transaction *transaction, struct strbuf *err) { struct ref_store *refs = transaction->ref_store; + int ret; switch (transaction->state) { case REF_TRANSACTION_OPEN: @@ -2012,7 +2069,17 @@ int ref_transaction_prepare(struct ref_transaction *transaction, return -1; } - return refs->be->transaction_prepare(refs, transaction, err); + ret = refs->be->transaction_prepare(refs, transaction, err); + if (ret) + return ret; + + ret = run_transaction_hook(transaction, "prepared"); + if (ret) { + ref_transaction_abort(transaction, err); + die(_("ref updates aborted by hook")); + } + + return 0; } int ref_transaction_abort(struct ref_transaction *transaction, @@ -2036,6 +2103,8 @@ int ref_transaction_abort(struct ref_transaction *transaction, break; } + run_transaction_hook(transaction, "aborted"); + ref_transaction_free(transaction); return ret; } @@ -2064,7 +2133,10 @@ int ref_transaction_commit(struct ref_transaction *transaction, break; } - return refs->be->transaction_finish(refs, transaction, err); + ret = refs->be->transaction_finish(refs, transaction, err); + if (!ret) + run_transaction_hook(transaction, "committed"); + return ret; } int refs_verify_refname_available(struct ref_store *refs, diff --git a/remote-curl.c b/remote-curl.c index 75532a8bae..5cbc6e5002 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -41,7 +41,9 @@ struct options { deepen_relative : 1, from_promisor : 1, no_dependents : 1, - atomic : 1; + atomic : 1, + object_format : 1; + const struct git_hash_algo *hash_algo; }; static struct options options; static struct string_list cas_options = STRING_LIST_INIT_DUP; @@ -190,6 +192,16 @@ static int set_option(const char *name, const char *value) } else if (!strcmp(name, "filter")) { options.filter = xstrdup(value); return 0; + } else if (!strcmp(name, "object-format")) { + int algo; + options.object_format = 1; + if (strcmp(value, "true")) { + algo = hash_algo_by_name(value); + if (algo == GIT_HASH_UNKNOWN) + die("unknown object format '%s'", value); + options.hash_algo = &hash_algos[algo]; + } + return 0; } else { return 1 /* unsupported */; } @@ -231,6 +243,7 @@ static struct ref *parse_git_refs(struct discovery *heads, int for_push) case protocol_v0: get_remote_heads(&reader, &list, for_push ? REF_NORMAL : 0, NULL, &heads->shallow); + options.hash_algo = reader.hash_algo; break; case protocol_unknown_version: BUG("unknown protocol version"); @@ -239,6 +252,19 @@ static struct ref *parse_git_refs(struct discovery *heads, int for_push) return list; } +static const struct git_hash_algo *detect_hash_algo(struct discovery *heads) +{ + const char *p = memchr(heads->buf, '\t', heads->len); + int algo; + if (!p) + return the_hash_algo; + + algo = hash_algo_by_length((p - heads->buf) / 2); + if (algo == GIT_HASH_UNKNOWN) + return NULL; + return &hash_algos[algo]; +} + static struct ref *parse_info_refs(struct discovery *heads) { char *data, *start, *mid; @@ -249,6 +275,12 @@ static struct ref *parse_info_refs(struct discovery *heads) struct ref *ref = NULL; struct ref *last_ref = NULL; + options.hash_algo = detect_hash_algo(heads); + if (!options.hash_algo) + die("%sinfo/refs not valid: could not determine hash algorithm; " + "is this a git repository?", + transport_anonymize_url(url.buf)); + data = heads->buf; start = NULL; mid = data; @@ -259,13 +291,13 @@ static struct ref *parse_info_refs(struct discovery *heads) if (data[i] == '\t') mid = &data[i]; if (data[i] == '\n') { - if (mid - start != the_hash_algo->hexsz) + if (mid - start != options.hash_algo->hexsz) die(_("%sinfo/refs not valid: is this a git repository?"), transport_anonymize_url(url.buf)); data[i] = 0; ref_name = mid + 1; ref = alloc_ref(ref_name); - get_oid_hex(start, &ref->old_oid); + get_oid_hex_algop(start, &ref->old_oid, options.hash_algo); if (!refs) refs = ref; if (last_ref) @@ -509,11 +541,16 @@ static struct ref *get_refs(int for_push) static void output_refs(struct ref *refs) { struct ref *posn; + if (options.object_format && options.hash_algo) { + printf(":object-format %s\n", options.hash_algo->name); + } for (posn = refs; posn; posn = posn->next) { if (posn->symref) printf("@%s %s\n", posn->symref, posn->name); else - printf("%s %s\n", oid_to_hex(&posn->old_oid), posn->name); + printf("%s %s\n", hash_to_hex_algop(posn->old_oid.hash, + options.hash_algo), + posn->name); } printf("\n"); fflush(stdout); @@ -1499,6 +1536,7 @@ int cmd_main(int argc, const char **argv) printf("option\n"); printf("push\n"); printf("check-connectivity\n"); + printf("object-format\n"); printf("\n"); fflush(stdout); } else if (skip_prefix(buf.buf, "stateless-connect ", &arg)) { diff --git a/repository.h b/repository.h index 6534fbb7b3..3c1f7d54bd 100644 --- a/repository.h +++ b/repository.h @@ -196,4 +196,10 @@ void repo_update_index_if_able(struct repository *, struct lock_file *); void prepare_repo_settings(struct repository *r); +/* + * Return 1 if upgrade repository format to target_version succeeded, + * 0 if no upgrade is necessary, and -1 when upgrade is not possible. + */ +int upgrade_repository_format(int target_version); + #endif /* REPOSITORY_H */ diff --git a/send-pack.c b/send-pack.c index 0abee22283..02aefcb08e 100644 --- a/send-pack.c +++ b/send-pack.c @@ -363,6 +363,7 @@ int send_pack(struct send_pack_args *args, int atomic_supported = 0; int use_push_options = 0; int push_options_supported = 0; + int object_format_supported = 0; unsigned cmds_sent = 0; int ret; struct async demux; @@ -389,6 +390,9 @@ int send_pack(struct send_pack_args *args, if (server_supports("push-options")) push_options_supported = 1; + if (!server_supports_hash(the_hash_algo->name, &object_format_supported)) + die(_("the receiving end does not support this repository's hash algorithm")); + if (args->push_cert != SEND_PACK_PUSH_CERT_NEVER) { int len; push_cert_nonce = server_feature_value("push-cert", &len); @@ -429,6 +433,8 @@ int send_pack(struct send_pack_args *args, strbuf_addstr(&cap_buf, " atomic"); if (use_push_options) strbuf_addstr(&cap_buf, " push-options"); + if (object_format_supported) + strbuf_addf(&cap_buf, " object-format=%s", the_hash_algo->name); if (agent_supported) strbuf_addf(&cap_buf, " agent=%s", git_user_agent_sanitized()); @@ -22,6 +22,14 @@ static int agent_advertise(struct repository *r, return 1; } +static int object_format_advertise(struct repository *r, + struct strbuf *value) +{ + if (value) + strbuf_addstr(value, r->hash_algo->name); + return 1; +} + struct protocol_capability { /* * The name of the capability. The server uses this name when @@ -57,6 +65,7 @@ static struct protocol_capability capabilities[] = { { "ls-refs", always_advertise, ls_refs }, { "fetch", upload_pack_advertise, upload_pack_v2 }, { "server-option", always_advertise, NULL }, + { "object-format", object_format_advertise, NULL }, }; static void advertise_capabilities(void) @@ -153,6 +162,22 @@ int has_capability(const struct argv_array *keys, const char *capability, return 0; } +static void check_algorithm(struct repository *r, struct argv_array *keys) +{ + int client = GIT_HASH_SHA1, server = hash_algo_by_ptr(r->hash_algo); + const char *algo_name; + + if (has_capability(keys, "object-format", &algo_name)) { + client = hash_algo_by_name(algo_name); + if (client == GIT_HASH_UNKNOWN) + die("unknown object format '%s'", algo_name); + } + + if (client != server) + die("mismatched object format: server %s; client %s\n", + r->hash_algo->name, hash_algos[client].name); +} + enum request_state { PROCESS_REQUEST_KEYS, PROCESS_REQUEST_DONE, @@ -225,6 +250,8 @@ static int process_request(void) if (!command) die("no command requested"); + check_algorithm(the_repository, &keys); + command->command(the_repository, &keys, &reader); argv_array_clear(&keys); @@ -455,6 +455,7 @@ static int check_repo_format(const char *var, const char *value, void *vdata) if (strcmp(var, "core.repositoryformatversion") == 0) data->version = git_config_int(var, value); else if (skip_prefix(var, "extensions.", &ext)) { + data->has_extensions = 1; /* * record any known extensions here; otherwise, * we fall through to recording it as unknown, and @@ -506,9 +507,15 @@ static int check_repository_format_gently(const char *gitdir, struct repository_ die("%s", err.buf); } - repository_format_precious_objects = candidate->precious_objects; - set_repository_format_partial_clone(candidate->partial_clone); - repository_format_worktree_config = candidate->worktree_config; + if (candidate->version >= 1) { + repository_format_precious_objects = candidate->precious_objects; + set_repository_format_partial_clone(candidate->partial_clone); + repository_format_worktree_config = candidate->worktree_config; + } else { + repository_format_precious_objects = 0; + set_repository_format_partial_clone(NULL); + repository_format_worktree_config = 0; + } string_list_clear(&candidate->unknown_extensions, 0); if (repository_format_worktree_config) { @@ -538,6 +545,34 @@ static int check_repository_format_gently(const char *gitdir, struct repository_ return 0; } +int upgrade_repository_format(int target_version) +{ + struct strbuf sb = STRBUF_INIT; + struct strbuf err = STRBUF_INIT; + struct strbuf repo_version = STRBUF_INIT; + struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT; + + strbuf_git_common_path(&sb, the_repository, "config"); + read_repository_format(&repo_fmt, sb.buf); + strbuf_release(&sb); + + if (repo_fmt.version >= target_version) + return 0; + + if (verify_repository_format(&repo_fmt, &err) < 0 || + (!repo_fmt.version && repo_fmt.has_extensions)) { + warning("unable to upgrade repository format from %d to %d: %s", + repo_fmt.version, target_version, err.buf); + strbuf_release(&err); + return -1; + } + + strbuf_addf(&repo_version, "%d", target_version); + git_config_set("core.repositoryformatversion", repo_version.buf); + strbuf_release(&repo_version); + return 1; +} + static void init_repository_format(struct repository_format *format) { const struct repository_format fresh = REPOSITORY_FORMAT_INIT; @@ -1273,6 +1308,7 @@ void check_repository_format(struct repository_format *fmt) fmt = &repo_fmt; check_repository_format_gently(get_git_dir(), fmt, NULL); startup_info->have_repository = 1; + repo_set_hash_algo(the_repository, fmt->hash_algo); clear_repository_format(&repo_fmt); } @@ -556,11 +556,6 @@ ssize_t strbuf_write(struct strbuf *sb, FILE *f) return sb->len ? fwrite(sb->buf, 1, sb->len, f) : 0; } -ssize_t strbuf_write_fd(struct strbuf *sb, int fd) -{ - return sb->len ? write(fd, sb->buf, sb->len) : 0; -} - #define STRBUF_MAXLINK (2*PATH_MAX) int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint) @@ -473,7 +473,6 @@ int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint); * NUL bytes. */ ssize_t strbuf_write(struct strbuf *sb, FILE *stream); -ssize_t strbuf_write_fd(struct strbuf *sb, int fd); /** * Read a line from a FILE *, overwriting the existing contents of diff --git a/t/helper/test-oid-array.c b/t/helper/test-oid-array.c index ce9fd5f091..b16cd0b11b 100644 --- a/t/helper/test-oid-array.c +++ b/t/helper/test-oid-array.c @@ -12,6 +12,9 @@ int cmd__oid_array(int argc, const char **argv) { struct oid_array array = OID_ARRAY_INIT; struct strbuf line = STRBUF_INIT; + int nongit_ok; + + setup_git_directory_gently(&nongit_ok); while (strbuf_getline(&line, stdin) != EOF) { const char *arg; diff --git a/t/perf/p1400-update-ref.sh b/t/perf/p1400-update-ref.sh new file mode 100755 index 0000000000..d275a81248 --- /dev/null +++ b/t/perf/p1400-update-ref.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +test_description="Tests performance of update-ref" + +. ./perf-lib.sh + +test_perf_fresh_repo + +test_expect_success "setup" ' + test_commit PRE && + test_commit POST && + printf "create refs/heads/%d PRE\n" $(test_seq 1000) >create && + printf "update refs/heads/%d POST PRE\n" $(test_seq 1000) >update && + printf "delete refs/heads/%d POST\n" $(test_seq 1000) >delete +' + +test_perf "update-ref" ' + for i in $(test_seq 1000) + do + git update-ref refs/heads/branch PRE && + git update-ref refs/heads/branch POST PRE && + git update-ref -d refs/heads/branch + done +' + +test_perf "update-ref --stdin" ' + git update-ref --stdin <create && + git update-ref --stdin <update && + git update-ref --stdin <delete +' + +test_done diff --git a/t/t0410-partial-clone.sh b/t/t0410-partial-clone.sh index a3988bd4b8..463dc3a8be 100755 --- a/t/t0410-partial-clone.sh +++ b/t/t0410-partial-clone.sh @@ -30,6 +30,29 @@ test_expect_success 'extensions.partialclone without filter' ' git -C client fetch origin ' +test_expect_success 'convert shallow clone to partial clone' ' + rm -fr server client && + test_create_repo server && + test_commit -C server my_commit 1 && + test_commit -C server my_commit2 1 && + git clone --depth=1 "file://$(pwd)/server" client && + git -C client fetch --unshallow --filter="blob:none" && + test_cmp_config -C client true remote.origin.promisor && + test_cmp_config -C client blob:none remote.origin.partialclonefilter && + test_cmp_config -C client 1 core.repositoryformatversion +' + +test_expect_success 'convert shallow clone to partial clone must fail with any extension' ' + rm -fr server client && + test_create_repo server && + test_commit -C server my_commit 1 && + test_commit -C server my_commit2 1 && + git clone --depth=1 "file://$(pwd)/server" client && + test_cmp_config -C client 0 core.repositoryformatversion && + git -C client config extensions.partialclone origin && + test_must_fail git -C client fetch --unshallow --filter="blob:none" +' + test_expect_success 'missing reflog object, but promised by a commit, passes fsck' ' rm -rf repo && test_create_repo repo && diff --git a/t/t1050-large.sh b/t/t1050-large.sh index 184b479a21..6a56d1ca24 100755 --- a/t/t1050-large.sh +++ b/t/t1050-large.sh @@ -12,6 +12,7 @@ file_size () { } test_expect_success setup ' + test_oid_init && # clone does not allow us to pass core.bigfilethreshold to # new repos, so set core.bigfilethreshold globally git config --global core.bigfilethreshold 200k && @@ -64,7 +65,7 @@ test_expect_success 'add a large file or two' ' test $count = 1 && cnt=$(git show-index <"$idx" | wc -l) && test $cnt = 2 && - for l in .git/objects/??/?????????????????????????????????????? + for l in .git/objects/$OIDPATH_REGEX do test_path_is_file "$l" || continue bad=t @@ -177,7 +178,8 @@ test_expect_success 'git-show a large file' ' test_expect_success 'index-pack' ' git clone file://"$(pwd)"/.git foo && - GIT_DIR=non-existent git index-pack --strict --verify foo/.git/objects/pack/*.pack + GIT_DIR=non-existent git index-pack --object-format=$(test_oid algo) \ + --strict --verify foo/.git/objects/pack/*.pack ' test_expect_success 'repack' ' diff --git a/t/t1090-sparse-checkout-scope.sh b/t/t1090-sparse-checkout-scope.sh index 40cc004326..f35a73dd20 100755 --- a/t/t1090-sparse-checkout-scope.sh +++ b/t/t1090-sparse-checkout-scope.sh @@ -63,7 +63,6 @@ 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 && - test_config -C client extensions.partialclone origin && 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/t1302-repo-version.sh b/t/t1302-repo-version.sh index ce4cff13bb..d60c042ce8 100755 --- a/t/t1302-repo-version.sh +++ b/t/t1302-repo-version.sh @@ -8,6 +8,10 @@ test_description='Test repository version check' . ./test-lib.sh test_expect_success 'setup' ' + test_oid_cache <<-\EOF && + version sha1:0 + version sha256:1 + EOF cat >test.patch <<-\EOF && diff --git a/test.txt b/test.txt new file mode 100644 @@ -23,7 +27,7 @@ test_expect_success 'setup' ' ' test_expect_success 'gitdir selection on normal repos' ' - echo 0 >expect && + echo $(test_oid version) >expect && git config core.repositoryformatversion >actual && git -C test config core.repositoryformatversion >actual2 && test_cmp expect actual && diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh new file mode 100755 index 0000000000..da58d867a5 --- /dev/null +++ b/t/t1416-ref-transaction-hooks.sh @@ -0,0 +1,109 @@ +#!/bin/sh + +test_description='reference transaction hooks' + +. ./test-lib.sh + +test_expect_success setup ' + mkdir -p .git/hooks && + test_commit PRE && + test_commit POST && + POST_OID=$(git rev-parse POST) +' + +test_expect_success 'hook allows updating ref if successful' ' + test_when_finished "rm .git/hooks/reference-transaction" && + git reset --hard PRE && + write_script .git/hooks/reference-transaction <<-\EOF && + echo "$*" >>actual + EOF + cat >expect <<-EOF && + prepared + committed + EOF + git update-ref HEAD POST && + test_cmp expect actual +' + +test_expect_success 'hook aborts updating ref in prepared state' ' + test_when_finished "rm .git/hooks/reference-transaction" && + git reset --hard PRE && + write_script .git/hooks/reference-transaction <<-\EOF && + if test "$1" = prepared + then + exit 1 + fi + EOF + test_must_fail git update-ref HEAD POST 2>err && + test_i18ngrep "ref updates aborted by hook" err +' + +test_expect_success 'hook gets all queued updates in prepared state' ' + test_when_finished "rm .git/hooks/reference-transaction actual" && + git reset --hard PRE && + write_script .git/hooks/reference-transaction <<-\EOF && + if test "$1" = prepared + then + while read -r line + do + printf "%s\n" "$line" + done >actual + fi + EOF + cat >expect <<-EOF && + $ZERO_OID $POST_OID HEAD + $ZERO_OID $POST_OID refs/heads/master + EOF + git update-ref HEAD POST <<-EOF && + update HEAD $ZERO_OID $POST_OID + update refs/heads/master $ZERO_OID $POST_OID + EOF + test_cmp expect actual +' + +test_expect_success 'hook gets all queued updates in committed state' ' + test_when_finished "rm .git/hooks/reference-transaction actual" && + git reset --hard PRE && + write_script .git/hooks/reference-transaction <<-\EOF && + if test "$1" = committed + then + while read -r line + do + printf "%s\n" "$line" + done >actual + fi + EOF + cat >expect <<-EOF && + $ZERO_OID $POST_OID HEAD + $ZERO_OID $POST_OID refs/heads/master + EOF + git update-ref HEAD POST && + test_cmp expect actual +' + +test_expect_success 'hook gets all queued updates in aborted state' ' + test_when_finished "rm .git/hooks/reference-transaction actual" && + git reset --hard PRE && + write_script .git/hooks/reference-transaction <<-\EOF && + if test "$1" = aborted + then + while read -r line + do + printf "%s\n" "$line" + done >actual + fi + EOF + cat >expect <<-EOF && + $ZERO_OID $POST_OID HEAD + $ZERO_OID $POST_OID refs/heads/master + EOF + git update-ref --stdin <<-EOF && + start + update HEAD POST $ZERO_OID + update refs/heads/master POST $ZERO_OID + abort + EOF + test_cmp expect actual +' + +test_done diff --git a/t/t2203-add-intent.sh b/t/t2203-add-intent.sh index 5bbe8dcce4..8a5d55054f 100755 --- a/t/t2203-add-intent.sh +++ b/t/t2203-add-intent.sh @@ -232,17 +232,54 @@ test_expect_success 'double rename detection in status' ' ) ' -test_expect_success 'diff-files/diff-cached shows ita as new/not-new files' ' +test_expect_success 'i-t-a files shown as new for "diff", "diff-files"; not-new for "diff --cached"' ' git reset --hard && - echo new >new-ita && - git add -N new-ita && + : >empty && + content="foo" && + echo "$content" >not-empty && + + hash_e=$(git hash-object empty) && + hash_n=$(git hash-object not-empty) && + hash_t=$(git hash-object -t tree /dev/null) && + + cat >expect.diff_p <<-EOF && + diff --git a/empty b/empty + new file mode 100644 + index 0000000..$(git rev-parse --short $hash_e) + diff --git a/not-empty b/not-empty + new file mode 100644 + index 0000000..$(git rev-parse --short $hash_n) + --- /dev/null + +++ b/not-empty + @@ -0,0 +1 @@ + +$content + EOF + cat >expect.diff_s <<-EOF && + create mode 100644 empty + create mode 100644 not-empty + EOF + cat >expect.diff_a <<-EOF && + :000000 100644 0000000 $(git rev-parse --short $hash_t) A$(printf "\t")empty + :000000 100644 0000000 $(git rev-parse --short $hash_t) A$(printf "\t")not-empty + EOF + + git add -N empty not-empty && + + git diff >actual && + test_cmp expect.diff_p actual && + git diff --summary >actual && - echo " create mode 100644 new-ita" >expected && - test_cmp expected actual && - git diff --cached --summary >actual2 && - test_must_be_empty actual2 -' + test_cmp expect.diff_s actual && + + git diff-files -p >actual && + test_cmp expect.diff_p actual && + git diff-files --abbrev >actual && + test_cmp expect.diff_a actual && + + git diff --cached >actual && + test_must_be_empty actual +' test_expect_success '"diff HEAD" includes ita as new files' ' git reset --hard && diff --git a/t/t2401-worktree-prune.sh b/t/t2401-worktree-prune.sh index b7d6d5d45a..a6ce7f590b 100755 --- a/t/t2401-worktree-prune.sh +++ b/t/t2401-worktree-prune.sh @@ -92,4 +92,28 @@ test_expect_success 'not prune proper checkouts' ' test -d .git/worktrees/nop ' +test_expect_success 'prune duplicate (linked/linked)' ' + test_when_finished rm -fr .git/worktrees w1 w2 && + git worktree add --detach w1 && + git worktree add --detach w2 && + sed "s/w2/w1/" .git/worktrees/w2/gitdir >.git/worktrees/w2/gitdir.new && + mv .git/worktrees/w2/gitdir.new .git/worktrees/w2/gitdir && + git worktree prune --verbose >actual && + test_i18ngrep "duplicate entry" actual && + test -d .git/worktrees/w1 && + ! test -d .git/worktrees/w2 +' + +test_expect_success 'prune duplicate (main/linked)' ' + test_when_finished rm -fr repo wt && + test_create_repo repo && + test_commit -C repo x && + git -C repo worktree add --detach ../wt && + rm -fr wt && + mv repo wt && + git -C wt worktree prune --verbose >actual && + test_i18ngrep "duplicate entry" actual && + ! test -d .git/worktrees/wt +' + test_done diff --git a/t/t2403-worktree-move.sh b/t/t2403-worktree-move.sh index 939d18d728..a4e1a178e0 100755 --- a/t/t2403-worktree-move.sh +++ b/t/t2403-worktree-move.sh @@ -112,6 +112,27 @@ test_expect_success 'move locked worktree (force)' ' git worktree move --force --force flump ploof ' +test_expect_success 'refuse to move worktree atop existing path' ' + >bobble && + git worktree add --detach beeble && + test_must_fail git worktree move beeble bobble +' + +test_expect_success 'move atop existing but missing worktree' ' + git worktree add --detach gnoo && + git worktree add --detach pneu && + rm -fr pneu && + test_must_fail git worktree move gnoo pneu && + git worktree move --force gnoo pneu && + + git worktree add --detach nu && + git worktree lock nu && + rm -fr nu && + test_must_fail git worktree move pneu nu && + test_must_fail git worktree --force move pneu nu && + git worktree move --force --force pneu nu +' + test_expect_success 'move a repo with uninitialized submodule' ' git init withsub && ( diff --git a/t/t2404-worktree-config.sh b/t/t2404-worktree-config.sh index 286121d8de..9536d10919 100755 --- a/t/t2404-worktree-config.sh +++ b/t/t2404-worktree-config.sh @@ -23,8 +23,10 @@ test_expect_success 'config --worktree without extension' ' ' test_expect_success 'enable worktreeConfig extension' ' + git config core.repositoryformatversion 1 && git config extensions.worktreeConfig true && - test_cmp_config true extensions.worktreeConfig + test_cmp_config true extensions.worktreeConfig && + test_cmp_config 1 core.repositoryformatversion ' test_expect_success 'config is shared as before' ' diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 411a70b0ce..b6aa04bbec 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -323,11 +323,11 @@ test_expect_success 'git branch --list -v with --abbrev' ' test_expect_success 'git branch --column' ' COLUMNS=81 git branch --column=column >actual && - cat >expected <<\EOF && + cat >expect <<\EOF && a/b/c bam foo l * master mb o/o q abc bar j/k m/m master2 n o/p r EOF - test_cmp expected actual + test_cmp expect actual ' test_expect_success 'git branch --column with an extremely long branch name' ' @@ -336,7 +336,7 @@ test_expect_success 'git branch --column with an extremely long branch name' ' test_when_finished "git branch -d $long" && git branch $long && COLUMNS=80 git branch --column=column >actual && - cat >expected <<EOF && + cat >expect <<EOF && a/b/c abc bam @@ -355,7 +355,7 @@ test_expect_success 'git branch --column with an extremely long branch name' ' r $long EOF - test_cmp expected actual + test_cmp expect actual ' test_expect_success 'git branch with column.*' ' @@ -364,11 +364,11 @@ test_expect_success 'git branch with column.*' ' COLUMNS=80 git branch >actual && git config --unset column.branch && git config --unset column.ui && - cat >expected <<\EOF && + cat >expect <<\EOF && a/b/c bam foo l * master mb o/o q abc bar j/k m/m master2 n o/p r EOF - test_cmp expected actual + test_cmp expect actual ' test_expect_success 'git branch --column -v should fail' ' @@ -379,7 +379,7 @@ test_expect_success 'git branch -v with column.ui ignored' ' git config column.ui column && COLUMNS=80 git branch -v | cut -c -10 | sed "s/ *$//" >actual && git config --unset column.ui && - cat >expected <<\EOF && + cat >expect <<\EOF && a/b/c abc bam @@ -397,12 +397,12 @@ test_expect_success 'git branch -v with column.ui ignored' ' q r EOF - test_cmp expected actual + test_cmp expect actual ' mv .git/config .git/config-saved -test_expect_success 'git branch -m q q2 without config should succeed' ' +test_expect_success SHA1 'git branch -m q q2 without config should succeed' ' git branch -m q q2 && git branch -m q2 q ' @@ -835,32 +835,42 @@ test_expect_success 'branch from tag w/--track causes failure' ' ' test_expect_success '--set-upstream-to fails on multiple branches' ' - test_must_fail git branch --set-upstream-to master a b c + echo "fatal: too many arguments to set new upstream" >expect && + test_must_fail git branch --set-upstream-to master a b c 2>err && + test_i18ncmp expect err ' test_expect_success '--set-upstream-to fails on detached HEAD' ' git checkout HEAD^{} && - test_must_fail git branch --set-upstream-to master && - git checkout - + test_when_finished git checkout - && + echo "fatal: could not set upstream of HEAD to master when it does not point to any branch." >expect && + test_must_fail git branch --set-upstream-to master 2>err && + test_i18ncmp expect err ' test_expect_success '--set-upstream-to fails on a missing dst branch' ' - test_must_fail git branch --set-upstream-to master does-not-exist + echo "fatal: branch '"'"'does-not-exist'"'"' does not exist" >expect && + test_must_fail git branch --set-upstream-to master does-not-exist 2>err && + test_i18ncmp expect err ' test_expect_success '--set-upstream-to fails on a missing src branch' ' - test_must_fail git branch --set-upstream-to does-not-exist master + test_must_fail git branch --set-upstream-to does-not-exist master 2>err && + test_i18ngrep "the requested upstream branch '"'"'does-not-exist'"'"' does not exist" err ' test_expect_success '--set-upstream-to fails on a non-ref' ' - test_must_fail git branch --set-upstream-to HEAD^{} + echo "fatal: Cannot setup tracking information; starting point '"'"'HEAD^{}'"'"' is not a branch." >expect && + test_must_fail git branch --set-upstream-to HEAD^{} 2>err && + test_i18ncmp expect err ' test_expect_success '--set-upstream-to fails on locked config' ' test_when_finished "rm -f .git/config.lock" && >.git/config.lock && git branch locked && - test_must_fail git branch --set-upstream-to locked + test_must_fail git branch --set-upstream-to locked 2>err && + test_i18ngrep "could not lock config file .git/config: File exists" err ' test_expect_success 'use --set-upstream-to modify HEAD' ' @@ -881,14 +891,17 @@ test_expect_success 'use --set-upstream-to modify a particular branch' ' ' test_expect_success '--unset-upstream should fail if given a non-existent branch' ' - test_must_fail git branch --unset-upstream i-dont-exist + echo "fatal: Branch '"'"'i-dont-exist'"'"' has no upstream information" >expect && + test_must_fail git branch --unset-upstream i-dont-exist 2>err && + test_i18ncmp expect err ' test_expect_success '--unset-upstream should fail if config is locked' ' test_when_finished "rm -f .git/config.lock" && git branch --set-upstream-to locked && >.git/config.lock && - test_must_fail git branch --unset-upstream + test_must_fail git branch --unset-upstream 2>err && + test_i18ngrep "could not lock config file .git/config: File exists" err ' test_expect_success 'test --unset-upstream on HEAD' ' @@ -900,17 +913,23 @@ test_expect_success 'test --unset-upstream on HEAD' ' test_must_fail git config branch.master.remote && test_must_fail git config branch.master.merge && # fail for a branch without upstream set - test_must_fail git branch --unset-upstream + echo "fatal: Branch '"'"'master'"'"' has no upstream information" >expect && + test_must_fail git branch --unset-upstream 2>err && + test_i18ncmp expect err ' test_expect_success '--unset-upstream should fail on multiple branches' ' - test_must_fail git branch --unset-upstream a b c + echo "fatal: too many arguments to unset upstream" >expect && + test_must_fail git branch --unset-upstream a b c 2>err && + test_i18ncmp expect err ' test_expect_success '--unset-upstream should fail on detached HEAD' ' git checkout HEAD^{} && - test_must_fail git branch --unset-upstream && - git checkout - + test_when_finished git checkout - && + echo "fatal: could not unset upstream of HEAD when it does not point to any branch." >expect && + test_must_fail git branch --unset-upstream 2>err && + test_i18ncmp expect err ' test_expect_success 'test --unset-upstream on a particular branch' ' @@ -922,17 +941,17 @@ test_expect_success 'test --unset-upstream on a particular branch' ' ' test_expect_success 'disabled option --set-upstream fails' ' - test_must_fail git branch --set-upstream origin/master + test_must_fail git branch --set-upstream origin/master ' test_expect_success '--set-upstream-to notices an error to set branch as own upstream' ' git branch --set-upstream-to refs/heads/my13 my13 2>actual && - cat >expected <<-\EOF && + cat >expect <<-\EOF && warning: Not setting branch my13 as its own upstream. EOF test_expect_code 1 git config branch.my13.remote && test_expect_code 1 git config branch.my13.merge && - test_i18ncmp expected actual + test_i18ncmp expect actual ' # Keep this test last, as it changes the current branch diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh index a1bc3e2001..b454f400eb 100755 --- a/t/t3430-rebase-merges.sh +++ b/t/t3430-rebase-merges.sh @@ -420,7 +420,7 @@ test_expect_success 'with --autosquash and --exec' ' git commit --fixup B B.t && write_script show.sh <<-\EOF && subject="$(git show -s --format=%s HEAD)" - content="$(git diff HEAD^! | tail -n 1)" + content="$(git diff HEAD^ HEAD | tail -n 1)" echo "$subject: $content" EOF test_tick && diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index 575e079cc2..958c2da56e 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -81,16 +81,16 @@ test_expect_success 'format-patch --ignore-if-in-upstream handles tags' ' ' test_expect_success "format-patch doesn't consider merge commits" ' - git checkout -b slave master && + git checkout -b feature master && echo "Another line" >>file && test_tick && - git commit -am "Slave change #1" && + git commit -am "Feature branch change #1" && echo "Yet another line" >>file && test_tick && - git commit -am "Slave change #2" && + git commit -am "Feature branch change #2" && git checkout -b merger master && test_tick && - git merge --no-ff slave && + git merge --no-ff feature && git format-patch -3 --stdout >patch && grep "^From " patch >from && test_line_count = 3 from diff --git a/t/t4068-diff-symmetric.sh b/t/t4068-diff-symmetric.sh new file mode 100755 index 0000000000..31d17a5af0 --- /dev/null +++ b/t/t4068-diff-symmetric.sh @@ -0,0 +1,91 @@ +#!/bin/sh + +test_description='behavior of diff with symmetric-diff setups' + +. ./test-lib.sh + +# build these situations: +# - normal merge with one merge base (br1...b2r); +# - criss-cross merge ie 2 merge bases (br1...master); +# - disjoint subgraph (orphan branch, br3...master). +# +# B---E <-- master +# / \ / +# A X +# \ / \ +# C---D--G <-- br1 +# \ / +# ---F <-- br2 +# +# H <-- br3 +# +# We put files into a few commits so that we can verify the +# output as well. + +test_expect_success setup ' + git commit --allow-empty -m A && + echo b >b && + git add b && + git commit -m B && + git checkout -b br1 HEAD^ && + echo c >c && + git add c && + git commit -m C && + git tag commit-C && + git merge -m D master && + git tag commit-D && + git checkout master && + git merge -m E commit-C && + git checkout -b br2 commit-C && + echo f >f && + git add f && + git commit -m F && + git checkout br1 && + git merge -m G br2 && + git checkout --orphan br3 && + git commit -m H +' + +test_expect_success 'diff with one merge base' ' + git diff commit-D...br1 >tmp && + tail -n 1 tmp >actual && + echo +f >expect && + test_cmp expect actual +' + +# The output (in tmp) can have +b or +c depending +# on which merge base (commit B or C) is picked. +# It should have one of those two, which comes out +# to seven lines. +test_expect_success 'diff with two merge bases' ' + git diff br1...master >tmp 2>err && + test_line_count = 7 tmp && + test_line_count = 1 err +' + +test_expect_success 'diff with no merge bases' ' + test_must_fail git diff br2...br3 >tmp 2>err && + test_i18ngrep "fatal: br2...br3: no merge base" err +' + +test_expect_success 'diff with too many symmetric differences' ' + test_must_fail git diff br1...master br2...br3 >tmp 2>err && + test_i18ngrep "usage" err +' + +test_expect_success 'diff with symmetric difference and extraneous arg' ' + test_must_fail git diff master br1...master >tmp 2>err && + test_i18ngrep "usage" err +' + +test_expect_success 'diff with two ranges' ' + test_must_fail git diff master br1..master br2..br3 >tmp 2>err && + test_i18ngrep "usage" err +' + +test_expect_success 'diff with ranges and extra arg' ' + test_must_fail git diff master br1..master commit-D >tmp 2>err && + test_i18ngrep "usage" err +' + +test_done diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh index 410a09b0dd..746cdb626e 100755 --- a/t/t5300-pack-object.sh +++ b/t/t5300-pack-object.sh @@ -12,7 +12,8 @@ TRASH=$(pwd) test_expect_success \ 'setup' \ - 'rm -f .git/index* && + 'test_oid_init && + rm -f .git/index* && perl -e "print \"a\" x 4096;" > a && perl -e "print \"b\" x 4096;" > b && perl -e "print \"c\" x 4096;" > c && @@ -412,18 +413,18 @@ test_expect_success 'set up pack for non-repo tests' ' ' test_expect_success 'index-pack --stdin complains of non-repo' ' - nongit test_must_fail git index-pack --stdin <foo.pack && + nongit test_must_fail git index-pack --object-format=$(test_oid algo) --stdin <foo.pack && test_path_is_missing non-repo/.git ' test_expect_success 'index-pack <pack> works in non-repo' ' - nongit git index-pack ../foo.pack && + nongit git index-pack --object-format=$(test_oid algo) ../foo.pack && test_path_is_file foo.idx ' test_expect_success 'index-pack --strict <pack> works in non-repo' ' rm -f foo.idx && - nongit git index-pack --strict ../foo.pack && + nongit git index-pack --strict --object-format=$(test_oid algo) ../foo.pack && test_path_is_file foo.idx ' diff --git a/t/t5302-pack-index.sh b/t/t5302-pack-index.sh index ad07f2f7fc..8981c9b90e 100755 --- a/t/t5302-pack-index.sh +++ b/t/t5302-pack-index.sh @@ -7,65 +7,65 @@ test_description='pack index with 64-bit offsets and object CRC' . ./test-lib.sh test_expect_success 'setup' ' - test_oid_init && - rawsz=$(test_oid rawsz) && - rm -rf .git && - git init && - git config pack.threads 1 && - i=1 && - while test $i -le 100 - do - iii=$(printf '%03i' $i) - test-tool genrandom "bar" 200 > wide_delta_$iii && - test-tool genrandom "baz $iii" 50 >> wide_delta_$iii && - test-tool genrandom "foo"$i 100 > deep_delta_$iii && - test-tool genrandom "foo"$(expr $i + 1) 100 >> deep_delta_$iii && - test-tool genrandom "foo"$(expr $i + 2) 100 >> deep_delta_$iii && - echo $iii >file_$iii && - test-tool genrandom "$iii" 8192 >>file_$iii && - git update-index --add file_$iii deep_delta_$iii wide_delta_$iii && - i=$(expr $i + 1) || return 1 - done && - { echo 101 && test-tool genrandom 100 8192; } >file_101 && - git update-index --add file_101 && - tree=$(git write-tree) && - commit=$(git commit-tree $tree </dev/null) && { - echo $tree && - git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\) .*/\\1/" - } >obj-list && - git update-ref HEAD $commit + test_oid_init && + rawsz=$(test_oid rawsz) && + rm -rf .git && + git init && + git config pack.threads 1 && + i=1 && + while test $i -le 100 + do + iii=$(printf '%03i' $i) + test-tool genrandom "bar" 200 > wide_delta_$iii && + test-tool genrandom "baz $iii" 50 >> wide_delta_$iii && + test-tool genrandom "foo"$i 100 > deep_delta_$iii && + test-tool genrandom "foo"$(expr $i + 1) 100 >> deep_delta_$iii && + test-tool genrandom "foo"$(expr $i + 2) 100 >> deep_delta_$iii && + echo $iii >file_$iii && + test-tool genrandom "$iii" 8192 >>file_$iii && + git update-index --add file_$iii deep_delta_$iii wide_delta_$iii && + i=$(expr $i + 1) || return 1 + done && + { echo 101 && test-tool genrandom 100 8192; } >file_101 && + git update-index --add file_101 && + tree=$(git write-tree) && + commit=$(git commit-tree $tree </dev/null) && { + echo $tree && + git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\) .*/\\1/" + } >obj-list && + git update-ref HEAD $commit ' -test_expect_success \ - 'pack-objects with index version 1' \ - 'pack1=$(git pack-objects --index-version=1 test-1 <obj-list) && - git verify-pack -v "test-1-${pack1}.pack"' +test_expect_success 'pack-objects with index version 1' ' + pack1=$(git pack-objects --index-version=1 test-1 <obj-list) && + git verify-pack -v "test-1-${pack1}.pack" +' -test_expect_success \ - 'pack-objects with index version 2' \ - 'pack2=$(git pack-objects --index-version=2 test-2 <obj-list) && - git verify-pack -v "test-2-${pack2}.pack"' +test_expect_success 'pack-objects with index version 2' ' + pack2=$(git pack-objects --index-version=2 test-2 <obj-list) && + git verify-pack -v "test-2-${pack2}.pack" +' -test_expect_success \ - 'both packs should be identical' \ - 'cmp "test-1-${pack1}.pack" "test-2-${pack2}.pack"' +test_expect_success 'both packs should be identical' ' + cmp "test-1-${pack1}.pack" "test-2-${pack2}.pack" +' -test_expect_success \ - 'index v1 and index v2 should be different' \ - '! cmp "test-1-${pack1}.idx" "test-2-${pack2}.idx"' +test_expect_success 'index v1 and index v2 should be different' ' + ! cmp "test-1-${pack1}.idx" "test-2-${pack2}.idx" +' -test_expect_success \ - 'index-pack with index version 1' \ - 'git index-pack --index-version=1 -o 1.idx "test-1-${pack1}.pack"' +test_expect_success 'index-pack with index version 1' ' + git index-pack --index-version=1 -o 1.idx "test-1-${pack1}.pack" +' -test_expect_success \ - 'index-pack with index version 2' \ - 'git index-pack --index-version=2 -o 2.idx "test-1-${pack1}.pack"' +test_expect_success 'index-pack with index version 2' ' + git index-pack --index-version=2 -o 2.idx "test-1-${pack1}.pack" +' -test_expect_success \ - 'index-pack results should match pack-objects ones' \ - 'cmp "test-1-${pack1}.idx" "1.idx" && - cmp "test-2-${pack2}.idx" "2.idx"' +test_expect_success 'index-pack results should match pack-objects ones' ' + cmp "test-1-${pack1}.idx" "1.idx" && + cmp "test-2-${pack2}.idx" "2.idx" +' test_expect_success 'index-pack --verify on index version 1' ' git index-pack --verify "test-1-${pack1}.pack" @@ -75,13 +75,13 @@ test_expect_success 'index-pack --verify on index version 2' ' git index-pack --verify "test-2-${pack2}.pack" ' -test_expect_success \ - 'pack-objects --index-version=2, is not accepted' \ - 'test_must_fail git pack-objects --index-version=2, test-3 <obj-list' +test_expect_success 'pack-objects --index-version=2, is not accepted' ' + test_must_fail git pack-objects --index-version=2, test-3 <obj-list +' -test_expect_success \ - 'index v2: force some 64-bit offsets with pack-objects' \ - 'pack3=$(git pack-objects --index-version=2,0x40000 test-3 <obj-list)' +test_expect_success 'index v2: force some 64-bit offsets with pack-objects' ' + pack3=$(git pack-objects --index-version=2,0x40000 test-3 <obj-list) +' if msg=$(git verify-pack -v "test-3-${pack3}.pack" 2>&1) || ! (echo "$msg" | grep "pack too large .* off_t") @@ -91,21 +91,21 @@ else say "# skipping tests concerning 64-bit offsets" fi -test_expect_success OFF64_T \ - 'index v2: verify a pack with some 64-bit offsets' \ - 'git verify-pack -v "test-3-${pack3}.pack"' +test_expect_success OFF64_T 'index v2: verify a pack with some 64-bit offsets' ' + git verify-pack -v "test-3-${pack3}.pack" +' -test_expect_success OFF64_T \ - '64-bit offsets: should be different from previous index v2 results' \ - '! cmp "test-2-${pack2}.idx" "test-3-${pack3}.idx"' +test_expect_success OFF64_T '64-bit offsets: should be different from previous index v2 results' ' + ! cmp "test-2-${pack2}.idx" "test-3-${pack3}.idx" +' -test_expect_success OFF64_T \ - 'index v2: force some 64-bit offsets with index-pack' \ - 'git index-pack --index-version=2,0x40000 -o 3.idx "test-1-${pack1}.pack"' +test_expect_success OFF64_T 'index v2: force some 64-bit offsets with index-pack' ' + git index-pack --index-version=2,0x40000 -o 3.idx "test-1-${pack1}.pack" +' -test_expect_success OFF64_T \ - '64-bit offsets: index-pack result should match pack-objects one' \ - 'cmp "test-3-${pack3}.idx" "3.idx"' +test_expect_success OFF64_T '64-bit offsets: index-pack result should match pack-objects one' ' + cmp "test-3-${pack3}.idx" "3.idx" +' test_expect_success OFF64_T 'index-pack --verify on 64-bit offset v2 (cheat)' ' # This cheats by knowing which lower offset should still be encoded @@ -120,135 +120,143 @@ test_expect_success OFF64_T 'index-pack --verify on 64-bit offset v2' ' # returns the object number for given object in given pack index index_obj_nr() { - idx_file=$1 - object_sha1=$2 - nr=0 - git show-index < $idx_file | - while read offs sha1 extra - do - nr=$(($nr + 1)) - test "$sha1" = "$object_sha1" || continue - echo "$(($nr - 1))" - break - done + idx_file=$1 + object_sha1=$2 + nr=0 + git show-index < $idx_file | + while read offs sha1 extra + do + nr=$(($nr + 1)) + test "$sha1" = "$object_sha1" || continue + echo "$(($nr - 1))" + break + done } # returns the pack offset for given object as found in given pack index index_obj_offset() { - idx_file=$1 - object_sha1=$2 - git show-index < $idx_file | grep $object_sha1 | - ( read offs extra && echo "$offs" ) + idx_file=$1 + object_sha1=$2 + git show-index < $idx_file | grep $object_sha1 | + ( read offs extra && echo "$offs" ) } -test_expect_success \ - '[index v1] 1) stream pack to repository' \ - 'git index-pack --index-version=1 --stdin < "test-1-${pack1}.pack" && - git prune-packed && - git count-objects | ( read nr rest && test "$nr" -eq 1 ) && - cmp "test-1-${pack1}.pack" ".git/objects/pack/pack-${pack1}.pack" && - cmp "test-1-${pack1}.idx" ".git/objects/pack/pack-${pack1}.idx"' +test_expect_success '[index v1] 1) stream pack to repository' ' + git index-pack --index-version=1 --stdin < "test-1-${pack1}.pack" && + git prune-packed && + git count-objects | ( read nr rest && test "$nr" -eq 1 ) && + cmp "test-1-${pack1}.pack" ".git/objects/pack/pack-${pack1}.pack" && + cmp "test-1-${pack1}.idx" ".git/objects/pack/pack-${pack1}.idx" +' test_expect_success \ - '[index v1] 2) create a stealth corruption in a delta base reference' \ - '# This test assumes file_101 is a delta smaller than 16 bytes. - # It should be against file_100 but we substitute its base for file_099 - sha1_101=$(git hash-object file_101) && - sha1_099=$(git hash-object file_099) && - offs_101=$(index_obj_offset 1.idx $sha1_101) && - nr_099=$(index_obj_nr 1.idx $sha1_099) && - chmod +w ".git/objects/pack/pack-${pack1}.pack" && - recordsz=$((rawsz + 4)) && - dd of=".git/objects/pack/pack-${pack1}.pack" seek=$(($offs_101 + 1)) \ - if=".git/objects/pack/pack-${pack1}.idx" \ - skip=$((4 + 256 * 4 + $nr_099 * recordsz)) \ - bs=1 count=$rawsz conv=notrunc && - git cat-file blob $sha1_101 > file_101_foo1' + '[index v1] 2) create a stealth corruption in a delta base reference' ' + # This test assumes file_101 is a delta smaller than 16 bytes. + # It should be against file_100 but we substitute its base for file_099 + sha1_101=$(git hash-object file_101) && + sha1_099=$(git hash-object file_099) && + offs_101=$(index_obj_offset 1.idx $sha1_101) && + nr_099=$(index_obj_nr 1.idx $sha1_099) && + chmod +w ".git/objects/pack/pack-${pack1}.pack" && + recordsz=$((rawsz + 4)) && + dd of=".git/objects/pack/pack-${pack1}.pack" seek=$(($offs_101 + 1)) \ + if=".git/objects/pack/pack-${pack1}.idx" \ + skip=$((4 + 256 * 4 + $nr_099 * recordsz)) \ + bs=1 count=$rawsz conv=notrunc && + git cat-file blob $sha1_101 > file_101_foo1 +' test_expect_success \ - '[index v1] 3) corrupted delta happily returned wrong data' \ - 'test -f file_101_foo1 && ! cmp file_101 file_101_foo1' + '[index v1] 3) corrupted delta happily returned wrong data' ' + test -f file_101_foo1 && ! cmp file_101 file_101_foo1 +' test_expect_success \ - '[index v1] 4) confirm that the pack is actually corrupted' \ - 'test_must_fail git fsck --full $commit' + '[index v1] 4) confirm that the pack is actually corrupted' ' + test_must_fail git fsck --full $commit +' test_expect_success \ - '[index v1] 5) pack-objects happily reuses corrupted data' \ - 'pack4=$(git pack-objects test-4 <obj-list) && - test -f "test-4-${pack4}.pack"' + '[index v1] 5) pack-objects happily reuses corrupted data' ' + pack4=$(git pack-objects test-4 <obj-list) && + test -f "test-4-${pack4}.pack" +' -test_expect_success \ - '[index v1] 6) newly created pack is BAD !' \ - 'test_must_fail git verify-pack -v "test-4-${pack4}.pack"' +test_expect_success '[index v1] 6) newly created pack is BAD !' ' + test_must_fail git verify-pack -v "test-4-${pack4}.pack" +' -test_expect_success \ - '[index v2] 1) stream pack to repository' \ - 'rm -f .git/objects/pack/* && - git index-pack --index-version=2 --stdin < "test-1-${pack1}.pack" && - git prune-packed && - git count-objects | ( read nr rest && test "$nr" -eq 1 ) && - cmp "test-1-${pack1}.pack" ".git/objects/pack/pack-${pack1}.pack" && - cmp "test-2-${pack1}.idx" ".git/objects/pack/pack-${pack1}.idx"' +test_expect_success '[index v2] 1) stream pack to repository' ' + rm -f .git/objects/pack/* && + git index-pack --index-version=2 --stdin < "test-1-${pack1}.pack" && + git prune-packed && + git count-objects | ( read nr rest && test "$nr" -eq 1 ) && + cmp "test-1-${pack1}.pack" ".git/objects/pack/pack-${pack1}.pack" && + cmp "test-2-${pack1}.idx" ".git/objects/pack/pack-${pack1}.idx" +' test_expect_success \ - '[index v2] 2) create a stealth corruption in a delta base reference' \ - '# This test assumes file_101 is a delta smaller than 16 bytes. - # It should be against file_100 but we substitute its base for file_099 - sha1_101=$(git hash-object file_101) && - sha1_099=$(git hash-object file_099) && - offs_101=$(index_obj_offset 1.idx $sha1_101) && - nr_099=$(index_obj_nr 1.idx $sha1_099) && - chmod +w ".git/objects/pack/pack-${pack1}.pack" && - dd of=".git/objects/pack/pack-${pack1}.pack" seek=$(($offs_101 + 1)) \ - if=".git/objects/pack/pack-${pack1}.idx" \ - skip=$((8 + 256 * 4 + $nr_099 * rawsz)) \ - bs=1 count=$rawsz conv=notrunc && - git cat-file blob $sha1_101 > file_101_foo2' + '[index v2] 2) create a stealth corruption in a delta base reference' ' + # This test assumes file_101 is a delta smaller than 16 bytes. + # It should be against file_100 but we substitute its base for file_099 + sha1_101=$(git hash-object file_101) && + sha1_099=$(git hash-object file_099) && + offs_101=$(index_obj_offset 1.idx $sha1_101) && + nr_099=$(index_obj_nr 1.idx $sha1_099) && + chmod +w ".git/objects/pack/pack-${pack1}.pack" && + dd of=".git/objects/pack/pack-${pack1}.pack" seek=$(($offs_101 + 1)) \ + if=".git/objects/pack/pack-${pack1}.idx" \ + skip=$((8 + 256 * 4 + $nr_099 * rawsz)) \ + bs=1 count=$rawsz conv=notrunc && + git cat-file blob $sha1_101 > file_101_foo2 +' test_expect_success \ - '[index v2] 3) corrupted delta happily returned wrong data' \ - 'test -f file_101_foo2 && ! cmp file_101 file_101_foo2' + '[index v2] 3) corrupted delta happily returned wrong data' ' + test -f file_101_foo2 && ! cmp file_101 file_101_foo2 +' test_expect_success \ - '[index v2] 4) confirm that the pack is actually corrupted' \ - 'test_must_fail git fsck --full $commit' + '[index v2] 4) confirm that the pack is actually corrupted' ' + test_must_fail git fsck --full $commit +' test_expect_success \ - '[index v2] 5) pack-objects refuses to reuse corrupted data' \ - 'test_must_fail git pack-objects test-5 <obj-list && - test_must_fail git pack-objects --no-reuse-object test-6 <obj-list' + '[index v2] 5) pack-objects refuses to reuse corrupted data' ' + test_must_fail git pack-objects test-5 <obj-list && + test_must_fail git pack-objects --no-reuse-object test-6 <obj-list +' test_expect_success \ - '[index v2] 6) verify-pack detects CRC mismatch' \ - 'rm -f .git/objects/pack/* && - git index-pack --index-version=2 --stdin < "test-1-${pack1}.pack" && - git verify-pack ".git/objects/pack/pack-${pack1}.pack" && - obj=$(git hash-object file_001) && - nr=$(index_obj_nr ".git/objects/pack/pack-${pack1}.idx" $obj) && - chmod +w ".git/objects/pack/pack-${pack1}.idx" && - printf xxxx | dd of=".git/objects/pack/pack-${pack1}.idx" conv=notrunc \ - bs=1 count=4 seek=$((8 + 256 * 4 + $(wc -l <obj-list) * rawsz + $nr * 4)) && - ( while read obj - do git cat-file -p $obj >/dev/null || exit 1 - done <obj-list ) && - test_must_fail git verify-pack ".git/objects/pack/pack-${pack1}.pack" + '[index v2] 6) verify-pack detects CRC mismatch' ' + rm -f .git/objects/pack/* && + git index-pack --index-version=2 --stdin < "test-1-${pack1}.pack" && + git verify-pack ".git/objects/pack/pack-${pack1}.pack" && + obj=$(git hash-object file_001) && + nr=$(index_obj_nr ".git/objects/pack/pack-${pack1}.idx" $obj) && + chmod +w ".git/objects/pack/pack-${pack1}.idx" && + printf xxxx | dd of=".git/objects/pack/pack-${pack1}.idx" conv=notrunc \ + bs=1 count=4 seek=$((8 + 256 * 4 + $(wc -l <obj-list) * rawsz + $nr * 4)) && + ( while read obj + do git cat-file -p $obj >/dev/null || exit 1 + done <obj-list ) && + test_must_fail git verify-pack ".git/objects/pack/pack-${pack1}.pack" ' test_expect_success 'running index-pack in the object store' ' - rm -f .git/objects/pack/* && - cp test-1-${pack1}.pack .git/objects/pack/pack-${pack1}.pack && - ( - cd .git/objects/pack && - git index-pack pack-${pack1}.pack - ) && - test -f .git/objects/pack/pack-${pack1}.idx + rm -f .git/objects/pack/* && + cp test-1-${pack1}.pack .git/objects/pack/pack-${pack1}.pack && + ( + cd .git/objects/pack && + git index-pack pack-${pack1}.pack + ) && + test -f .git/objects/pack/pack-${pack1}.idx ' test_expect_success 'index-pack --strict warns upon missing tagger in tag' ' - sha=$(git rev-parse HEAD) && - cat >wrong-tag <<EOF && + sha=$(git rev-parse HEAD) && + cat >wrong-tag <<EOF && object $sha type commit tag guten tag @@ -256,18 +264,18 @@ tag guten tag This is an invalid tag. EOF - tag=$(git hash-object -t tag -w --stdin <wrong-tag) && - pack1=$(echo $tag $sha | git pack-objects tag-test) && - echo remove tag object && - thirtyeight=${tag#??} && - rm -f .git/objects/${tag%$thirtyeight}/$thirtyeight && - git index-pack --strict tag-test-${pack1}.pack 2>err && - grep "^warning:.* expected .tagger. line" err + tag=$(git hash-object -t tag -w --stdin <wrong-tag) && + pack1=$(echo $tag $sha | git pack-objects tag-test) && + echo remove tag object && + thirtyeight=${tag#??} && + rm -f .git/objects/${tag%$thirtyeight}/$thirtyeight && + git index-pack --strict tag-test-${pack1}.pack 2>err && + grep "^warning:.* expected .tagger. line" err ' test_expect_success 'index-pack --fsck-objects also warns upon missing tagger in tag' ' - git index-pack --fsck-objects tag-test-${pack1}.pack 2>err && - grep "^warning:.* expected .tagger. line" err + git index-pack --fsck-objects tag-test-${pack1}.pack 2>err && + grep "^warning:.* expected .tagger. line" err ' test_done diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh index 8c54e34ef1..3557374312 100755 --- a/t/t5500-fetch-pack.sh +++ b/t/t5500-fetch-pack.sh @@ -871,9 +871,10 @@ test_expect_success 'shallow since with commit graph and already-seen commit' ' GIT_PROTOCOL=version=2 git upload-pack . <<-EOF >/dev/null 0012command=fetch + $(echo "object-format=$(test_oid algo)" | packetize) 00010013deepen-since 1 - 0032want $(git rev-parse other) - 0032have $(git rev-parse master) + $(echo "want $(git rev-parse other)" | packetize) + $(echo "have $(git rev-parse master)" | packetize) 0000 EOF ) @@ -999,7 +1000,6 @@ fetch_filter_blob_limit_zero () { test_config -C "$SERVER" uploadpack.allowfilter 1 && git clone "$URL" client && - test_config -C client extensions.partialclone origin && test_commit -C "$SERVER" two && diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh index 50485300eb..483578b2d7 100755 --- a/t/t5550-http-fetch-dumb.sh +++ b/t/t5550-http-fetch-dumb.sh @@ -50,6 +50,24 @@ test_expect_success 'create password-protected repository' ' "$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/repo.git" ' +test_expect_success 'create empty remote repository' ' + git init --bare "$HTTPD_DOCUMENT_ROOT_PATH/empty.git" && + (cd "$HTTPD_DOCUMENT_ROOT_PATH/empty.git" && + mkdir -p hooks && + write_script "hooks/post-update" <<-\EOF && + exec git update-server-info + EOF + hooks/post-update + ) +' + +test_expect_success 'empty dumb HTTP repository has default hash algorithm' ' + test_when_finished "rm -fr clone-empty" && + git clone $HTTPD_URL/dumb/empty.git clone-empty && + git -C clone-empty rev-parse --show-object-format >empty-format && + test "$(cat empty-format)" = "$(test_oid algo)" +' + setup_askpass_helper test_expect_success 'cloning password-protected repository can fail' ' @@ -199,6 +217,28 @@ test_expect_success 'fetch packed objects' ' git clone $HTTPD_URL/dumb/repo_pack.git ' +test_expect_success 'http-fetch --packfile' ' + # Arbitrary hash. Use rev-parse so that we get one of the correct + # length. + ARBITRARY=$(git -C "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git rev-parse HEAD) && + + git init packfileclient && + p=$(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git && ls objects/pack/pack-*.pack) && + git -C packfileclient http-fetch --packfile=$ARBITRARY "$HTTPD_URL"/dumb/repo_pack.git/$p >out && + + grep "^keep.[0-9a-f]\{16,\}$" out && + cut -c6- out >packhash && + + # Ensure that the expected files are generated + test -e "packfileclient/.git/objects/pack/pack-$(cat packhash).pack" && + test -e "packfileclient/.git/objects/pack/pack-$(cat packhash).idx" && + test -e "packfileclient/.git/objects/pack/pack-$(cat packhash).keep" && + + # Ensure that it has the HEAD of repo_pack, at least + HASH=$(git -C "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git rev-parse HEAD) && + git -C packfileclient cat-file -e "$HASH" +' + test_expect_success 'fetch notices corrupt pack' ' cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git && (cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git && @@ -214,6 +254,14 @@ test_expect_success 'fetch notices corrupt pack' ' ) ' +test_expect_success 'http-fetch --packfile with corrupt pack' ' + rm -rf packfileclient && + git init packfileclient && + p=$(cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad1.git && ls objects/pack/pack-*.pack) && + test_must_fail git -C packfileclient http-fetch --packfile \ + "$HTTPD_URL"/dumb/repo_bad1.git/$p +' + test_expect_success 'fetch notices corrupt idx' ' cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad2.git && (cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_bad2.git && diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh index be01cf7bb2..e40e9ed52f 100755 --- a/t/t5551-http-fetch-smart.sh +++ b/t/t5551-http-fetch-smart.sh @@ -209,6 +209,16 @@ test_expect_success 'GIT_CURL_VERBOSE redacts auth details' ' grep "Authorization: Basic <redacted>" trace ' +test_expect_success 'GIT_TRACE_CURL does not redact auth details if GIT_TRACE_REDACT=0' ' + rm -rf redact-auth trace && + set_askpass user@host pass@host && + GIT_TRACE_REDACT=0 GIT_TRACE_CURL="$(pwd)/trace" \ + git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth && + expect_askpass both user@host && + + grep "Authorization: Basic [0-9a-zA-Z+/]" trace +' + test_expect_success 'disable dumb http on server' ' git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/repo.git" \ config http.getanyfile false @@ -454,37 +464,37 @@ test_expect_success 'fetch by SHA-1 without tag following' ' --no-tags origin $(cat bar_hash) ' -test_expect_success 'GIT_REDACT_COOKIES redacts cookies' ' +test_expect_success 'cookies are redacted by default' ' rm -rf clone && echo "Set-Cookie: Foo=1" >cookies && echo "Set-Cookie: Bar=2" >>cookies && - GIT_TRACE_CURL=true GIT_REDACT_COOKIES=Bar,Baz \ + GIT_TRACE_CURL=true \ git -c "http.cookieFile=$(pwd)/cookies" clone \ $HTTPD_URL/smart/repo.git clone 2>err && - grep "Cookie:.*Foo=1" err && + grep "Cookie:.*Foo=<redacted>" err && grep "Cookie:.*Bar=<redacted>" err && + ! grep "Cookie:.*Foo=1" err && ! grep "Cookie:.*Bar=2" err ' -test_expect_success 'GIT_REDACT_COOKIES redacts cookies when GIT_CURL_VERBOSE=1' ' +test_expect_success 'empty values of cookies are also redacted' ' rm -rf clone && - echo "Set-Cookie: Foo=1" >cookies && - echo "Set-Cookie: Bar=2" >>cookies && - GIT_CURL_VERBOSE=1 GIT_REDACT_COOKIES=Bar,Baz \ + echo "Set-Cookie: Foo=" >cookies && + GIT_TRACE_CURL=true \ git -c "http.cookieFile=$(pwd)/cookies" clone \ $HTTPD_URL/smart/repo.git clone 2>err && - grep "Cookie:.*Foo=1" err && - grep "Cookie:.*Bar=<redacted>" err && - ! grep "Cookie:.*Bar=2" err + grep "Cookie:.*Foo=<redacted>" err ' -test_expect_success 'GIT_REDACT_COOKIES handles empty values' ' +test_expect_success 'GIT_TRACE_REDACT=0 disables cookie redaction' ' rm -rf clone && - echo "Set-Cookie: Foo=" >cookies && - GIT_TRACE_CURL=true GIT_REDACT_COOKIES=Foo \ + echo "Set-Cookie: Foo=1" >cookies && + echo "Set-Cookie: Bar=2" >>cookies && + GIT_TRACE_REDACT=0 GIT_TRACE_CURL=true \ git -c "http.cookieFile=$(pwd)/cookies" clone \ $HTTPD_URL/smart/repo.git clone 2>err && - grep "Cookie:.*Foo=<redacted>" err + grep "Cookie:.*Foo=1" err && + grep "Cookie:.*Bar=2" err ' test_expect_success 'GIT_TRACE_CURL_NO_DATA prevents data from being traced' ' diff --git a/t/t5562-http-backend-content-length.sh b/t/t5562-http-backend-content-length.sh index 3f4ac71f83..c6ec625497 100755 --- a/t/t5562-http-backend-content-length.sh +++ b/t/t5562-http-backend-content-length.sh @@ -46,6 +46,7 @@ ssize_b100dots() { } test_expect_success 'setup' ' + test_oid_init && HTTP_CONTENT_ENCODING="identity" && export HTTP_CONTENT_ENCODING && git config http.receivepack true && @@ -62,8 +63,8 @@ test_expect_success 'setup' ' test_copy_bytes 10 <fetch_body >fetch_body.trunc && hash_next=$(git commit-tree -p HEAD -m next HEAD^{tree}) && { - printf "%s %s refs/heads/newbranch\\0report-status\\n" \ - "$ZERO_OID" "$hash_next" | packetize && + printf "%s %s refs/heads/newbranch\\0report-status object-format=%s\\n" \ + "$ZERO_OID" "$hash_next" "$(test_oid algo)" | packetize && printf 0000 && echo "$hash_next" | git pack-objects --stdout } >push_body && diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh index ffb9613885..a1f5fdc9fd 100755 --- a/t/t5701-git-serve.sh +++ b/t/t5701-git-serve.sh @@ -5,12 +5,17 @@ test_description='test protocol v2 server commands' . ./test-lib.sh test_expect_success 'test capability advertisement' ' + test_oid_cache <<-EOF && + wrong_algo sha1:sha256 + wrong_algo sha256:sha1 + EOF cat >expect <<-EOF && version 2 agent=git/$(git version | cut -d" " -f3) ls-refs fetch=shallow server-option + object-format=$(test_oid algo) 0000 EOF @@ -45,6 +50,7 @@ test_expect_success 'request invalid capability' ' test_expect_success 'request with no command' ' test-tool pkt-line pack >in <<-EOF && agent=git/test + object-format=$(test_oid algo) 0000 EOF test_must_fail test-tool serve-v2 --stateless-rpc 2>err <in && @@ -54,6 +60,7 @@ test_expect_success 'request with no command' ' test_expect_success 'request invalid command' ' test-tool pkt-line pack >in <<-EOF && command=foo + object-format=$(test_oid algo) agent=git/test 0000 EOF @@ -61,6 +68,17 @@ test_expect_success 'request invalid command' ' test_i18ngrep "invalid command" err ' +test_expect_success 'wrong object-format' ' + test-tool pkt-line pack >in <<-EOF && + command=fetch + agent=git/test + object-format=$(test_oid wrong_algo) + 0000 + EOF + test_must_fail test-tool serve-v2 --stateless-rpc 2>err <in && + test_i18ngrep "mismatched object format" err +' + # Test the basics of ls-refs # test_expect_success 'setup some refs and tags' ' @@ -74,6 +92,7 @@ test_expect_success 'setup some refs and tags' ' test_expect_success 'basics of ls-refs' ' test-tool pkt-line pack >in <<-EOF && command=ls-refs + object-format=$(test_oid algo) 0000 EOF @@ -96,6 +115,7 @@ test_expect_success 'basics of ls-refs' ' test_expect_success 'basic ref-prefixes' ' test-tool pkt-line pack >in <<-EOF && command=ls-refs + object-format=$(test_oid algo) 0001 ref-prefix refs/heads/master ref-prefix refs/tags/one @@ -116,6 +136,7 @@ test_expect_success 'basic ref-prefixes' ' test_expect_success 'refs/heads prefix' ' test-tool pkt-line pack >in <<-EOF && command=ls-refs + object-format=$(test_oid algo) 0001 ref-prefix refs/heads/ 0000 @@ -136,6 +157,7 @@ test_expect_success 'refs/heads prefix' ' test_expect_success 'peel parameter' ' test-tool pkt-line pack >in <<-EOF && command=ls-refs + object-format=$(test_oid algo) 0001 peel ref-prefix refs/tags/ @@ -157,6 +179,7 @@ test_expect_success 'peel parameter' ' test_expect_success 'symrefs parameter' ' test-tool pkt-line pack >in <<-EOF && command=ls-refs + object-format=$(test_oid algo) 0001 symrefs ref-prefix refs/heads/ @@ -178,6 +201,7 @@ test_expect_success 'symrefs parameter' ' test_expect_success 'sending server-options' ' test-tool pkt-line pack >in <<-EOF && command=ls-refs + object-format=$(test_oid algo) server-option=hello server-option=world 0001 @@ -200,6 +224,7 @@ test_expect_success 'unexpected lines are not allowed in fetch request' ' test-tool pkt-line pack >in <<-EOF && command=fetch + object-format=$(test_oid algo) 0001 this-is-not-a-command 0000 diff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh index 8da65e60de..1b54c35b01 100755 --- a/t/t5702-protocol-v2.sh +++ b/t/t5702-protocol-v2.sh @@ -13,6 +13,7 @@ start_git_daemon --export-all --enable=receive-pack daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent test_expect_success 'create repo to be served by git-daemon' ' + test_oid_init && git init "$daemon_parent" && test_commit -C "$daemon_parent" one ' @@ -348,7 +349,6 @@ test_expect_success 'partial fetch' ' rm -rf client "$(pwd)/trace" && git init client && SERVER="file://$(pwd)/server" && - test_config -C client extensions.partialClone "$SERVER" && GIT_TRACE_PACKET="$(pwd)/trace" git -C client -c protocol.version=2 \ fetch --filter=blob:none "$SERVER" master:refs/heads/other && @@ -394,6 +394,7 @@ test_expect_success 'even with handcrafted request, filter does not work if not # Custom request that tries to filter even though it is not advertised. test-tool pkt-line pack >in <<-EOF && command=fetch + object-format=$(test_oid algo) 0001 want $(git -C server rev-parse master) filter blob:none @@ -795,6 +796,94 @@ test_expect_success 'when server does not send "ready", expect FLUSH' ' test_i18ngrep "expected no other sections to be sent after no .ready." err ' +configure_exclusion () { + git -C "$1" hash-object "$2" >objh && + git -C "$1" pack-objects "$HTTPD_DOCUMENT_ROOT_PATH/mypack" <objh >packh && + git -C "$1" config --add \ + "uploadpack.blobpackfileuri" \ + "$(cat objh) $(cat packh) $HTTPD_URL/dumb/mypack-$(cat packh).pack" && + cat objh +} + +test_expect_success 'part of packfile response provided as URI' ' + P="$HTTPD_DOCUMENT_ROOT_PATH/http_parent" && + rm -rf "$P" http_child log && + + git init "$P" && + git -C "$P" config "uploadpack.allowsidebandall" "true" && + + echo my-blob >"$P/my-blob" && + git -C "$P" add my-blob && + echo other-blob >"$P/other-blob" && + git -C "$P" add other-blob && + git -C "$P" commit -m x && + + configure_exclusion "$P" my-blob >h && + configure_exclusion "$P" other-blob >h2 && + + GIT_TRACE=1 GIT_TRACE_PACKET="$(pwd)/log" GIT_TEST_SIDEBAND_ALL=1 \ + git -c protocol.version=2 \ + -c fetch.uriprotocols=http,https \ + clone "$HTTPD_URL/smart/http_parent" http_child && + + # Ensure that my-blob and other-blob are in separate packfiles. + for idx in http_child/.git/objects/pack/*.idx + do + git verify-pack --verbose $idx >out && + { + grep "^[0-9a-f]\{16,\} " out || : + } >out.objectlist && + if test_line_count = 1 out.objectlist + then + if grep $(cat h) out + then + >hfound + fi && + if grep $(cat h2) out + then + >h2found + fi + fi + done && + test -f hfound && + test -f h2found && + + # Ensure that there are exactly 6 files (3 .pack and 3 .idx). + ls http_child/.git/objects/pack/* >filelist && + test_line_count = 6 filelist +' + +test_expect_success 'fetching with valid packfile URI but invalid hash fails' ' + P="$HTTPD_DOCUMENT_ROOT_PATH/http_parent" && + rm -rf "$P" http_child log && + + git init "$P" && + git -C "$P" config "uploadpack.allowsidebandall" "true" && + + echo my-blob >"$P/my-blob" && + git -C "$P" add my-blob && + echo other-blob >"$P/other-blob" && + git -C "$P" add other-blob && + git -C "$P" commit -m x && + + configure_exclusion "$P" my-blob >h && + # Configure a URL for other-blob. Just reuse the hash of the object as + # the hash of the packfile, since the hash does not matter for this + # test as long as it is not the hash of the pack, and it is of the + # expected length. + git -C "$P" hash-object other-blob >objh && + git -C "$P" pack-objects "$HTTPD_DOCUMENT_ROOT_PATH/mypack" <objh >packh && + git -C "$P" config --add \ + "uploadpack.blobpackfileuri" \ + "$(cat objh) $(cat objh) $HTTPD_URL/dumb/mypack-$(cat packh).pack" && + + test_must_fail env GIT_TEST_SIDEBAND_ALL=1 \ + git -c protocol.version=2 \ + -c fetch.uriprotocols=http,https \ + clone "$HTTPD_URL/smart/http_parent" http_child 2>err && + test_i18ngrep "pack downloaded from.*does not match expected hash" err +' + # DO NOT add non-httpd-specific tests here, because the last part of this # test script is only executed when httpd is available and enabled. diff --git a/t/t5703-upload-pack-ref-in-want.sh b/t/t5703-upload-pack-ref-in-want.sh index 92ad5eeec0..748282f058 100755 --- a/t/t5703-upload-pack-ref-in-want.sh +++ b/t/t5703-upload-pack-ref-in-want.sh @@ -27,6 +27,15 @@ check_output () { test_cmp sorted_commits actual_commits } +write_command () { + echo "command=$1" + + if test "$(test_oid algo)" != sha1 + then + echo "object-format=$(test_oid algo)" + fi +} + # c(o/foo) d(o/bar) # \ / # b e(baz) f(master) @@ -65,7 +74,7 @@ test_expect_success 'config controls ref-in-want advertisement' ' test_expect_success 'invalid want-ref line' ' test-tool pkt-line pack >in <<-EOF && - command=fetch + $(write_command fetch) 0001 no-progress want-ref refs/heads/non-existent @@ -86,7 +95,7 @@ test_expect_success 'basic want-ref' ' oid=$(git rev-parse a) && test-tool pkt-line pack >in <<-EOF && - command=fetch + $(write_command fetch) 0001 no-progress want-ref refs/heads/master @@ -110,7 +119,7 @@ test_expect_success 'multiple want-ref lines' ' oid=$(git rev-parse b) && test-tool pkt-line pack >in <<-EOF && - command=fetch + $(write_command fetch) 0001 no-progress want-ref refs/heads/o/foo @@ -132,7 +141,7 @@ test_expect_success 'mix want and want-ref' ' git rev-parse e f >expected_commits && test-tool pkt-line pack >in <<-EOF && - command=fetch + $(write_command fetch) 0001 no-progress want-ref refs/heads/master @@ -155,7 +164,7 @@ test_expect_success 'want-ref with ref we already have commit for' ' oid=$(git rev-parse c) && test-tool pkt-line pack >in <<-EOF && - command=fetch + $(write_command fetch) 0001 no-progress want-ref refs/heads/o/foo diff --git a/t/t5704-protocol-violations.sh b/t/t5704-protocol-violations.sh index 950cfb21fe..5c941949b9 100755 --- a/t/t5704-protocol-violations.sh +++ b/t/t5704-protocol-violations.sh @@ -9,6 +9,7 @@ making sure that we do not segfault or otherwise behave badly.' test_expect_success 'extra delim packet in v2 ls-refs args' ' { packetize command=ls-refs && + packetize "object-format=$(test_oid algo)" && printf 0001 && # protocol expects 0000 flush here printf 0001 @@ -21,6 +22,7 @@ test_expect_success 'extra delim packet in v2 ls-refs args' ' test_expect_success 'extra delim packet in v2 fetch args' ' { packetize command=fetch && + packetize "object-format=$(test_oid algo)" && printf 0001 && # protocol expects 0000 flush here printf 0001 diff --git a/t/t5801/git-remote-testgit b/t/t5801/git-remote-testgit index 6b9f0b5dc7..1544d6dc6b 100755 --- a/t/t5801/git-remote-testgit +++ b/t/t5801/git-remote-testgit @@ -52,9 +52,11 @@ do test -n "$GIT_REMOTE_TESTGIT_SIGNED_TAGS" && echo "signed-tags" test -n "$GIT_REMOTE_TESTGIT_NO_PRIVATE_UPDATE" && echo "no-private-update" echo 'option' + echo 'object-format' echo ;; list) + echo ":object-format $(git rev-parse --show-object-format=storage)" git for-each-ref --format='? %(refname)' 'refs/heads/' 'refs/tags/' head=$(git symbolic-ref HEAD) echo "@$head HEAD" @@ -139,6 +141,10 @@ do test $val = "true" && force="true" || force= echo "ok" ;; + object-format) + test $val = "true" && object_format="true" || object_format= + echo "ok" + ;; *) echo "unsupported" ;; diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index 3c44af6940..8f434a0931 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -1240,6 +1240,461 @@ test_expect_success '__git_complete_fetch_refspecs - fully qualified & prefix' ' test_cmp expected out ' +test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' ' + test_completion "git switch " <<-\EOF + branch-in-other Z + master Z + master-in-other Z + matching-branch Z + EOF +' + +test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' ' + test_completion "git checkout " <<-\EOF + HEAD Z + branch-in-other Z + master Z + master-in-other Z + matching-branch Z + matching-tag Z + other/branch-in-other Z + other/master-in-other Z + EOF +' + +test_expect_success 'git switch - with --no-guess, complete only local branches' ' + test_completion "git switch --no-guess " <<-\EOF + master Z + matching-branch Z + EOF +' + +test_expect_success 'git switch - with GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete only local branches' ' + GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git switch " <<-\EOF + master Z + matching-branch Z + EOF +' + +test_expect_success 'git switch - --guess overrides GIT_COMPLETION_CHECKOUT_NO_GUESS=1, complete local branches and unique remote names for DWIM logic' ' + GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git switch --guess " <<-\EOF + branch-in-other Z + master Z + master-in-other Z + matching-branch Z + EOF +' + +test_expect_success 'git switch - a later --guess overrides previous --no-guess, complete local and remote unique branches for DWIM' ' + test_completion "git switch --no-guess --guess " <<-\EOF + branch-in-other Z + master Z + master-in-other Z + matching-branch Z + EOF +' + +test_expect_success 'git switch - a later --no-guess overrides previous --guess, complete only local branches' ' + test_completion "git switch --guess --no-guess " <<-\EOF + master Z + matching-branch Z + EOF +' + +test_expect_success 'git checkout - with GIT_COMPLETION_NO_GUESS=1 only completes refs' ' + GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git checkout " <<-\EOF + HEAD Z + master Z + matching-branch Z + matching-tag Z + other/branch-in-other Z + other/master-in-other Z + EOF +' + +test_expect_success 'git checkout - --guess overrides GIT_COMPLETION_NO_GUESS=1, complete refs and unique remote branches for DWIM' ' + GIT_COMPLETION_CHECKOUT_NO_GUESS=1 test_completion "git checkout --guess " <<-\EOF + HEAD Z + branch-in-other Z + master Z + master-in-other Z + matching-branch Z + matching-tag Z + other/branch-in-other Z + other/master-in-other Z + EOF +' + +test_expect_success 'git checkout - with --no-guess, only completes refs' ' + test_completion "git checkout --no-guess " <<-\EOF + HEAD Z + master Z + matching-branch Z + matching-tag Z + other/branch-in-other Z + other/master-in-other Z + EOF +' + +test_expect_success 'git checkout - a later --guess overrides previous --no-guess, complete refs and unique remote branches for DWIM' ' + test_completion "git checkout --no-guess --guess " <<-\EOF + HEAD Z + branch-in-other Z + master Z + master-in-other Z + matching-branch Z + matching-tag Z + other/branch-in-other Z + other/master-in-other Z + EOF +' + +test_expect_success 'git checkout - a later --no-guess overrides previous --guess, complete only refs' ' + test_completion "git checkout --guess --no-guess " <<-\EOF + HEAD Z + master Z + matching-branch Z + matching-tag Z + other/branch-in-other Z + other/master-in-other Z + EOF +' + +test_expect_success 'git switch - with --detach, complete all references' ' + test_completion "git switch --detach " <<-\EOF + HEAD Z + master Z + matching-branch Z + matching-tag Z + other/branch-in-other Z + other/master-in-other Z + EOF +' + +test_expect_success 'git checkout - with --detach, complete only references' ' + test_completion "git checkout --detach " <<-\EOF + HEAD Z + master Z + matching-branch Z + matching-tag Z + other/branch-in-other Z + other/master-in-other Z + EOF +' + +test_expect_success 'git switch - with -d, complete all references' ' + test_completion "git switch -d " <<-\EOF + HEAD Z + master Z + matching-branch Z + matching-tag Z + other/branch-in-other Z + other/master-in-other Z + EOF +' + +test_expect_success 'git checkout - with -d, complete only references' ' + test_completion "git checkout -d " <<-\EOF + HEAD Z + master Z + matching-branch Z + matching-tag Z + other/branch-in-other Z + other/master-in-other Z + EOF +' + +test_expect_success 'git switch - with --track, complete only remote branches' ' + test_completion "git switch --track " <<-\EOF + other/branch-in-other Z + other/master-in-other Z + EOF +' + +test_expect_success 'git checkout - with --track, complete only remote branches' ' + test_completion "git checkout --track " <<-\EOF + other/branch-in-other Z + other/master-in-other Z + EOF +' + +test_expect_success 'git switch - with --no-track, complete only local branch names' ' + test_completion "git switch --no-track " <<-\EOF + master Z + matching-branch Z + EOF +' + +test_expect_success 'git checkout - with --no-track, complete only local references' ' + test_completion "git checkout --no-track " <<-\EOF + HEAD Z + master Z + matching-branch Z + matching-tag Z + other/branch-in-other Z + other/master-in-other Z + EOF +' + +test_expect_success 'git switch - with -c, complete all references' ' + test_completion "git switch -c new-branch " <<-\EOF + HEAD Z + master Z + matching-branch Z + matching-tag Z + other/branch-in-other Z + other/master-in-other Z + EOF +' + +test_expect_success 'git switch - with -C, complete all references' ' + test_completion "git switch -C new-branch " <<-\EOF + HEAD Z + master Z + matching-branch Z + matching-tag Z + other/branch-in-other Z + other/master-in-other Z + EOF +' + +test_expect_success 'git switch - with -c and --track, complete all references' ' + test_completion "git switch -c new-branch --track " <<-EOF + HEAD Z + master Z + matching-branch Z + matching-tag Z + other/branch-in-other Z + other/master-in-other Z + EOF +' + +test_expect_success 'git switch - with -C and --track, complete all references' ' + test_completion "git switch -C new-branch --track " <<-EOF + HEAD Z + master Z + matching-branch Z + matching-tag Z + other/branch-in-other Z + other/master-in-other Z + EOF +' + +test_expect_success 'git switch - with -c and --no-track, complete all references' ' + test_completion "git switch -c new-branch --no-track " <<-\EOF + HEAD Z + master Z + matching-branch Z + matching-tag Z + other/branch-in-other Z + other/master-in-other Z + EOF +' + +test_expect_success 'git switch - with -C and --no-track, complete all references' ' + test_completion "git switch -C new-branch --no-track " <<-\EOF + HEAD Z + master Z + matching-branch Z + matching-tag Z + other/branch-in-other Z + other/master-in-other Z + EOF +' + +test_expect_success 'git checkout - with -b, complete all references' ' + test_completion "git checkout -b new-branch " <<-\EOF + HEAD Z + master Z + matching-branch Z + matching-tag Z + other/branch-in-other Z + other/master-in-other Z + EOF +' + +test_expect_success 'git checkout - with -B, complete all references' ' + test_completion "git checkout -B new-branch " <<-\EOF + HEAD Z + master Z + matching-branch Z + matching-tag Z + other/branch-in-other Z + other/master-in-other Z + EOF +' + +test_expect_success 'git checkout - with -b and --track, complete all references' ' + test_completion "git checkout -b new-branch --track " <<-EOF + HEAD Z + master Z + matching-branch Z + matching-tag Z + other/branch-in-other Z + other/master-in-other Z + EOF +' + +test_expect_success 'git checkout - with -B and --track, complete all references' ' + test_completion "git checkout -B new-branch --track " <<-EOF + HEAD Z + master Z + matching-branch Z + matching-tag Z + other/branch-in-other Z + other/master-in-other Z + EOF +' + +test_expect_success 'git checkout - with -b and --no-track, complete all references' ' + test_completion "git checkout -b new-branch --no-track " <<-\EOF + HEAD Z + master Z + matching-branch Z + matching-tag Z + other/branch-in-other Z + other/master-in-other Z + EOF +' + +test_expect_success 'git checkout - with -B and --no-track, complete all references' ' + test_completion "git checkout -B new-branch --no-track " <<-\EOF + HEAD Z + master Z + matching-branch Z + matching-tag Z + other/branch-in-other Z + other/master-in-other Z + EOF +' + +test_expect_success 'git switch - for -c, complete local branches and unique remote branches' ' + test_completion "git switch -c " <<-\EOF + branch-in-other Z + master Z + master-in-other Z + matching-branch Z + EOF +' + +test_expect_success 'git switch - for -C, complete local branches and unique remote branches' ' + test_completion "git switch -C " <<-\EOF + branch-in-other Z + master Z + master-in-other Z + matching-branch Z + EOF +' + +test_expect_success 'git switch - for -c with --no-guess, complete local branches only' ' + test_completion "git switch --no-guess -c " <<-\EOF + master Z + matching-branch Z + EOF +' + +test_expect_success 'git switch - for -C with --no-guess, complete local branches only' ' + test_completion "git switch --no-guess -C " <<-\EOF + master Z + matching-branch Z + EOF +' + +test_expect_success 'git switch - for -c with --no-track, complete local branches only' ' + test_completion "git switch --no-track -c " <<-\EOF + master Z + matching-branch Z + EOF +' + +test_expect_success 'git switch - for -C with --no-track, complete local branches only' ' + test_completion "git switch --no-track -C " <<-\EOF + master Z + matching-branch Z + EOF +' + +test_expect_success 'git checkout - for -b, complete local branches and unique remote branches' ' + test_completion "git checkout -b " <<-\EOF + branch-in-other Z + master Z + master-in-other Z + matching-branch Z + EOF +' + +test_expect_success 'git checkout - for -B, complete local branches and unique remote branches' ' + test_completion "git checkout -B " <<-\EOF + branch-in-other Z + master Z + master-in-other Z + matching-branch Z + EOF +' + +test_expect_success 'git checkout - for -b with --no-guess, complete local branches only' ' + test_completion "git checkout --no-guess -b " <<-\EOF + master Z + matching-branch Z + EOF +' + +test_expect_success 'git checkout - for -B with --no-guess, complete local branches only' ' + test_completion "git checkout --no-guess -B " <<-\EOF + master Z + matching-branch Z + EOF +' + +test_expect_success 'git checkout - for -b with --no-track, complete local branches only' ' + test_completion "git checkout --no-track -b " <<-\EOF + master Z + matching-branch Z + EOF +' + +test_expect_success 'git checkout - for -B with --no-track, complete local branches only' ' + test_completion "git checkout --no-track -B " <<-\EOF + master Z + matching-branch Z + EOF +' + +test_expect_success 'git switch - with --orphan completes local branch names and unique remote branch names' ' + test_completion "git switch --orphan " <<-\EOF + branch-in-other Z + master Z + master-in-other Z + matching-branch Z + EOF +' + +test_expect_success 'git switch - --orphan with branch already provided completes nothing else' ' + test_completion "git switch --orphan master " <<-\EOF + + EOF +' + +test_expect_success 'git checkout - with --orphan completes local branch names and unique remote branch names' ' + test_completion "git checkout --orphan " <<-\EOF + branch-in-other Z + master Z + master-in-other Z + matching-branch Z + EOF +' + +test_expect_success 'git checkout - --orphan with branch already provided completes local refs for a start-point' ' + test_completion "git checkout --orphan master " <<-\EOF + HEAD Z + master Z + matching-branch Z + matching-tag Z + other/branch-in-other Z + other/master-in-other Z + EOF +' + test_expect_success 'teardown after ref completion' ' git branch -d matching-branch && git tag -d matching-tag && diff --git a/t/test-lib.sh b/t/test-lib.sh index dbc027ff26..618a7c8d5b 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -1414,6 +1414,7 @@ test_oid_init ZERO_OID=$(test_oid zero) OID_REGEX=$(echo $ZERO_OID | sed -e 's/0/[0-9a-f]/g') +OIDPATH_REGEX=$(test_oid_to_path $ZERO_OID | sed -e 's/0/[0-9a-f]/g') EMPTY_TREE=$(test_oid empty_tree) EMPTY_BLOB=$(test_oid empty_blob) _z40=$ZERO_OID diff --git a/transport-helper.c b/transport-helper.c index a46afcb69d..ad0bf8e18d 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -32,7 +32,8 @@ struct helper_data { signed_tags : 1, check_connectivity : 1, no_disconnect_req : 1, - no_private_update : 1; + no_private_update : 1, + object_format : 1; /* * As an optimization, the transport code may invoke fetch before @@ -207,6 +208,8 @@ static struct child_process *get_helper(struct transport *transport) data->import_marks = xstrdup(arg); } else if (starts_with(capname, "no-private-update")) { data->no_private_update = 1; + } else if (starts_with(capname, "object-format")) { + data->object_format = 1; } else if (mandatory) { die(_("unknown mandatory capability %s; this remote " "helper probably needs newer version of Git"), @@ -410,10 +413,11 @@ static int fetch_with_fetch(struct transport *transport, exit(128); if (skip_prefix(buf.buf, "lock ", &name)) { - if (transport->pack_lockfile) + if (transport->pack_lockfiles.nr) warning(_("%s also locked %s"), data->name, name); else - transport->pack_lockfile = xstrdup(name); + string_list_append(&transport->pack_lockfiles, + name); } else if (data->check_connectivity && data->transport_options.check_self_contained_and_connected && @@ -1103,6 +1107,12 @@ static struct ref *get_refs_list_using_list(struct transport *transport, data->get_refs_list_called = 1; helper = get_helper(transport); + if (data->object_format) { + write_str_in_full(helper->in, "option object-format\n"); + if (recvline(data, &buf) || strcmp(buf.buf, "ok")) + exit(128); + } + if (data->push && for_push) write_str_in_full(helper->in, "list for-push\n"); else @@ -1115,6 +1125,17 @@ static struct ref *get_refs_list_using_list(struct transport *transport, if (!*buf.buf) break; + else if (buf.buf[0] == ':') { + const char *value; + if (skip_prefix(buf.buf, ":object-format ", &value)) { + int algo = hash_algo_by_name(value); + if (algo == GIT_HASH_UNKNOWN) + die(_("unsupported object format '%s'"), + value); + transport->hash_algo = &hash_algos[algo]; + } + continue; + } eov = strchr(buf.buf, ' '); if (!eov) @@ -1127,7 +1148,7 @@ static struct ref *get_refs_list_using_list(struct transport *transport, if (buf.buf[0] == '@') (*tail)->symref = xstrdup(buf.buf + 1); else if (buf.buf[0] != '?') - get_oid_hex(buf.buf, &(*tail)->old_oid); + get_oid_hex_algop(buf.buf, &(*tail)->old_oid, transport->hash_algo); if (eon) { if (has_attribute(eon + 1, "unchanged")) { (*tail)->status |= REF_STATUS_UPTODATE; diff --git a/transport.c b/transport.c index 7d50c502ad..b41386eccb 100644 --- a/transport.c +++ b/transport.c @@ -143,6 +143,9 @@ static struct ref *get_refs_from_bundle(struct transport *transport, data->fd = read_bundle_header(transport->url, &data->header); if (data->fd < 0) die(_("could not read bundle '%s'"), transport->url); + + transport->hash_algo = data->header.hash_algo; + for (i = 0; i < data->header.references.nr; i++) { struct ref_list_entry *e = data->header.references.list + i; struct ref *ref = alloc_ref(e->name); @@ -157,11 +160,14 @@ static int fetch_refs_from_bundle(struct transport *transport, int nr_heads, struct ref **to_fetch) { struct bundle_transport_data *data = transport->data; + int ret; if (!data->get_refs_from_bundle_called) get_refs_from_bundle(transport, 0, NULL); - return unbundle(the_repository, &data->header, data->fd, - transport->progress ? BUNDLE_VERBOSE : 0); + ret = unbundle(the_repository, &data->header, data->fd, + transport->progress ? BUNDLE_VERBOSE : 0); + transport->hash_algo = data->header.hash_algo; + return ret; } static int close_bundle(struct transport *transport) @@ -312,6 +318,7 @@ static struct ref *handshake(struct transport *transport, int for_push, BUG("unknown protocol version"); } data->got_remote_heads = 1; + transport->hash_algo = reader.hash_algo; if (reader.line_peeked) BUG("buffer must be empty at the end of handshake()"); @@ -378,7 +385,7 @@ static int fetch_refs_via_pack(struct transport *transport, refs = fetch_pack(&args, data->fd, refs_tmp ? refs_tmp : transport->remote_refs, to_fetch, nr_heads, &data->shallow, - &transport->pack_lockfile, data->version); + &transport->pack_lockfiles, data->version); close(data->fd[0]); close(data->fd[1]); @@ -921,6 +928,7 @@ struct transport *transport_get(struct remote *remote, const char *url) struct transport *ret = xcalloc(1, sizeof(*ret)); ret->progress = isatty(2); + string_list_init(&ret->pack_lockfiles, 1); if (!remote) BUG("No remote provided to transport_get()"); @@ -988,9 +996,16 @@ struct transport *transport_get(struct remote *remote, const char *url) ret->smart_options->receivepack = remote->receivepack; } + ret->hash_algo = &hash_algos[GIT_HASH_SHA1]; + return ret; } +const struct git_hash_algo *transport_get_hash_algo(struct transport *transport) +{ + return transport->hash_algo; +} + int transport_set_option(struct transport *transport, const char *name, const char *value) { @@ -1316,10 +1331,11 @@ int transport_fetch_refs(struct transport *transport, struct ref *refs) void transport_unlock_pack(struct transport *transport) { - if (transport->pack_lockfile) { - unlink_or_warn(transport->pack_lockfile); - FREE_AND_NULL(transport->pack_lockfile); - } + int i; + + for (i = 0; i < transport->pack_lockfiles.nr; i++) + unlink_or_warn(transport->pack_lockfiles.items[i].string); + string_list_clear(&transport->pack_lockfiles, 0); } int transport_connect(struct transport *transport, const char *name, diff --git a/transport.h b/transport.h index 4298c855be..b3c30133ea 100644 --- a/transport.h +++ b/transport.h @@ -5,8 +5,7 @@ #include "run-command.h" #include "remote.h" #include "list-objects-filter-options.h" - -struct string_list; +#include "string-list.h" struct git_transport_options { unsigned thin : 1; @@ -98,7 +97,8 @@ struct transport { */ const struct string_list *server_options; - char *pack_lockfile; + struct string_list pack_lockfiles; + signed verbose : 3; /** * Transports should not set this directly, and should use this @@ -115,6 +115,8 @@ struct transport { struct git_transport_options *smart_options; enum transport_family family; + + const struct git_hash_algo *hash_algo; }; #define TRANSPORT_PUSH_ALL (1<<0) @@ -243,6 +245,12 @@ int transport_push(struct repository *repo, const struct ref *transport_get_remote_refs(struct transport *transport, const struct argv_array *ref_prefixes); +/* + * Fetch the hash algorithm used by a remote. + * + * This can only be called after fetching the remote refs. + */ +const struct git_hash_algo *transport_get_hash_algo(struct transport *transport); int transport_fetch_refs(struct transport *transport, struct ref *refs); void transport_unlock_pack(struct transport *transport); int transport_disconnect(struct transport *transport); diff --git a/upload-pack.c b/upload-pack.c index 401c9e6c4b..951a2b23aa 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -42,60 +42,71 @@ #define ALL_FLAGS (THEY_HAVE | OUR_REF | WANTED | COMMON_KNOWN | SHALLOW | \ NOT_SHALLOW | CLIENT_SHALLOW | HIDDEN_REF) -static timestamp_t oldest_have; - -static int multi_ack; -static int no_done; -static int use_thin_pack, use_ofs_delta, use_include_tag; -static int no_progress, daemon_mode; -/* Allow specifying sha1 if it is a ref tip. */ -#define ALLOW_TIP_SHA1 01 -/* Allow request of a sha1 if it is reachable from a ref (possibly hidden ref). */ -#define ALLOW_REACHABLE_SHA1 02 -/* Allow request of any sha1. Implies ALLOW_TIP_SHA1 and ALLOW_REACHABLE_SHA1. */ -#define ALLOW_ANY_SHA1 07 -static unsigned int allow_unadvertised_object_request; -static int shallow_nr; -static struct object_array extra_edge_obj; -static unsigned int timeout; -static int keepalive = 5; -/* 0 for no sideband, - * otherwise maximum packet size (up to 65520 bytes). - */ -static int use_sideband; -static const char *pack_objects_hook; - -static int filter_capability_requested; -static int allow_filter; -static int allow_ref_in_want; - -static int allow_sideband_all; +/* Enum for allowed unadvertised object request (UOR) */ +enum allow_uor { + /* Allow specifying sha1 if it is a ref tip. */ + ALLOW_TIP_SHA1 = 0x01, + /* Allow request of a sha1 if it is reachable from a ref (possibly hidden ref). */ + ALLOW_REACHABLE_SHA1 = 0x02, + /* Allow request of any sha1. Implies ALLOW_TIP_SHA1 and ALLOW_REACHABLE_SHA1. */ + ALLOW_ANY_SHA1 = 0x07 +}; +/* + * Please annotate, and if possible group together, fields used only + * for protocol v0 or only for protocol v2. + */ struct upload_pack_data { - struct string_list symref; - struct string_list wanted_refs; + struct string_list symref; /* v0 only */ struct object_array want_obj; struct object_array have_obj; - struct oid_array haves; + struct oid_array haves; /* v2 only */ + struct string_list wanted_refs; /* v2 only */ struct object_array shallows; struct string_list deepen_not; + struct object_array extra_edge_obj; int depth; timestamp_t deepen_since; int deepen_rev_list; int deepen_relative; + int keepalive; + int shallow_nr; + timestamp_t oldest_have; + + unsigned int timeout; /* v0 only */ + enum { + NO_MULTI_ACK = 0, + MULTI_ACK = 1, + MULTI_ACK_DETAILED = 2 + } multi_ack; /* v0 only */ + + /* 0 for no sideband, otherwise DEFAULT_PACKET_MAX or LARGE_PACKET_MAX */ + int use_sideband; + + struct string_list uri_protocols; + enum allow_uor allow_uor; struct list_objects_filter_options filter_options; struct packet_writer writer; - unsigned stateless_rpc : 1; + const char *pack_objects_hook; + + unsigned stateless_rpc : 1; /* v0 only */ + unsigned no_done : 1; /* v0 only */ + unsigned daemon_mode : 1; /* v0 only */ + unsigned filter_capability_requested : 1; /* v0 only */ unsigned use_thin_pack : 1; unsigned use_ofs_delta : 1; unsigned no_progress : 1; unsigned use_include_tag : 1; - unsigned done : 1; + unsigned allow_filter : 1; + + unsigned done : 1; /* v2 only */ + unsigned allow_ref_in_want : 1; /* v2 only */ + unsigned allow_sideband_all : 1; /* v2 only */ }; static void upload_pack_data_init(struct upload_pack_data *data) @@ -107,6 +118,8 @@ static void upload_pack_data_init(struct upload_pack_data *data) struct oid_array haves = OID_ARRAY_INIT; struct object_array shallows = OBJECT_ARRAY_INIT; struct string_list deepen_not = STRING_LIST_INIT_DUP; + struct string_list uri_protocols = STRING_LIST_INIT_DUP; + struct object_array extra_edge_obj = OBJECT_ARRAY_INIT; memset(data, 0, sizeof(*data)); data->symref = symref; @@ -116,7 +129,11 @@ static void upload_pack_data_init(struct upload_pack_data *data) data->haves = haves; data->shallows = shallows; data->deepen_not = deepen_not; + data->uri_protocols = uri_protocols; + data->extra_edge_obj = extra_edge_obj; packet_writer_init(&data->writer, 1); + + data->keepalive = 5; } static void upload_pack_data_clear(struct upload_pack_data *data) @@ -128,15 +145,19 @@ static void upload_pack_data_clear(struct upload_pack_data *data) oid_array_clear(&data->haves); object_array_clear(&data->shallows); string_list_clear(&data->deepen_not, 0); + object_array_clear(&data->extra_edge_obj); list_objects_filter_release(&data->filter_options); + + free((char *)data->pack_objects_hook); } -static void reset_timeout(void) +static void reset_timeout(unsigned int timeout) { alarm(timeout); } -static void send_client_data(int fd, const char *data, ssize_t sz) +static void send_client_data(int fd, const char *data, ssize_t sz, + int use_sideband) { if (use_sideband) { send_sideband(1, fd, data, sz, use_sideband); @@ -161,42 +182,115 @@ static int write_one_shallow(const struct commit_graft *graft, void *cb_data) return 0; } -static void create_pack_file(struct upload_pack_data *pack_data) +struct output_state { + char buffer[8193]; + int used; + unsigned packfile_uris_started : 1; + unsigned packfile_started : 1; +}; + +static int relay_pack_data(int pack_objects_out, struct output_state *os, + int use_sideband, int write_packfile_line) +{ + /* + * We keep the last byte to ourselves + * in case we detect broken rev-list, so that we + * can leave the stream corrupted. This is + * unfortunate -- unpack-objects would happily + * accept a valid packdata with trailing garbage, + * so appending garbage after we pass all the + * pack data is not good enough to signal + * breakage to downstream. + */ + ssize_t readsz; + + readsz = xread(pack_objects_out, os->buffer + os->used, + sizeof(os->buffer) - os->used); + if (readsz < 0) { + return readsz; + } + os->used += readsz; + + while (!os->packfile_started) { + char *p; + if (os->used >= 4 && !memcmp(os->buffer, "PACK", 4)) { + os->packfile_started = 1; + if (write_packfile_line) { + if (os->packfile_uris_started) + packet_delim(1); + packet_write_fmt(1, "\1packfile\n"); + } + break; + } + if ((p = memchr(os->buffer, '\n', os->used))) { + if (!os->packfile_uris_started) { + os->packfile_uris_started = 1; + if (!write_packfile_line) + BUG("packfile_uris requires sideband-all"); + packet_write_fmt(1, "\1packfile-uris\n"); + } + *p = '\0'; + packet_write_fmt(1, "\1%s\n", os->buffer); + + os->used -= p - os->buffer + 1; + memmove(os->buffer, p + 1, os->used); + } else { + /* + * Incomplete line. + */ + return readsz; + } + } + + if (os->used > 1) { + send_client_data(1, os->buffer, os->used - 1, use_sideband); + os->buffer[0] = os->buffer[os->used - 1]; + os->used = 1; + } else { + send_client_data(1, os->buffer, os->used, use_sideband); + os->used = 0; + } + + return readsz; +} + +static void create_pack_file(struct upload_pack_data *pack_data, + const struct string_list *uri_protocols) { struct child_process pack_objects = CHILD_PROCESS_INIT; - char data[8193], progress[128]; + struct output_state output_state = { { 0 } }; + char progress[128]; char abort_msg[] = "aborting due to possible repository " "corruption on the remote side."; - int buffered = -1; ssize_t sz; int i; FILE *pipe_fd; - if (!pack_objects_hook) + if (!pack_data->pack_objects_hook) pack_objects.git_cmd = 1; else { - argv_array_push(&pack_objects.args, pack_objects_hook); + argv_array_push(&pack_objects.args, pack_data->pack_objects_hook); argv_array_push(&pack_objects.args, "git"); pack_objects.use_shell = 1; } - if (shallow_nr) { + if (pack_data->shallow_nr) { argv_array_push(&pack_objects.args, "--shallow-file"); argv_array_push(&pack_objects.args, ""); } argv_array_push(&pack_objects.args, "pack-objects"); argv_array_push(&pack_objects.args, "--revs"); - if (use_thin_pack) + if (pack_data->use_thin_pack) argv_array_push(&pack_objects.args, "--thin"); argv_array_push(&pack_objects.args, "--stdout"); - if (shallow_nr) + if (pack_data->shallow_nr) argv_array_push(&pack_objects.args, "--shallow"); - if (!no_progress) + if (!pack_data->no_progress) argv_array_push(&pack_objects.args, "--progress"); - if (use_ofs_delta) + if (pack_data->use_ofs_delta) argv_array_push(&pack_objects.args, "--delta-base-offset"); - if (use_include_tag) + if (pack_data->use_include_tag) argv_array_push(&pack_objects.args, "--include-tag"); if (pack_data->filter_options.choice) { const char *spec = @@ -211,6 +305,11 @@ static void create_pack_file(struct upload_pack_data *pack_data) spec); } } + if (uri_protocols) { + for (i = 0; i < uri_protocols->nr; i++) + argv_array_pushf(&pack_objects.args, "--uri-protocol=%s", + uri_protocols->items[i].string); + } pack_objects.in = -1; pack_objects.out = -1; @@ -221,7 +320,7 @@ static void create_pack_file(struct upload_pack_data *pack_data) pipe_fd = xfdopen(pack_objects.in, "w"); - if (shallow_nr) + if (pack_data->shallow_nr) for_each_commit_graft(write_one_shallow, pipe_fd); for (i = 0; i < pack_data->want_obj.nr; i++) @@ -231,9 +330,9 @@ static void create_pack_file(struct upload_pack_data *pack_data) for (i = 0; i < pack_data->have_obj.nr; i++) fprintf(pipe_fd, "%s\n", oid_to_hex(&pack_data->have_obj.objects[i].item->oid)); - for (i = 0; i < extra_edge_obj.nr; i++) + for (i = 0; i < pack_data->extra_edge_obj.nr; i++) fprintf(pipe_fd, "%s\n", - oid_to_hex(&extra_edge_obj.objects[i].item->oid)); + oid_to_hex(&pack_data->extra_edge_obj.objects[i].item->oid)); fprintf(pipe_fd, "\n"); fflush(pipe_fd); fclose(pipe_fd); @@ -244,10 +343,10 @@ static void create_pack_file(struct upload_pack_data *pack_data) while (1) { struct pollfd pfd[2]; - int pe, pu, pollsize; + int pe, pu, pollsize, polltimeout; int ret; - reset_timeout(); + reset_timeout(pack_data->timeout); pollsize = 0; pe = pu = -1; @@ -268,8 +367,11 @@ static void create_pack_file(struct upload_pack_data *pack_data) if (!pollsize) break; - ret = poll(pfd, pollsize, - keepalive < 0 ? -1 : 1000 * keepalive); + polltimeout = pack_data->keepalive < 0 + ? -1 + : 1000 * pack_data->keepalive; + + ret = poll(pfd, pollsize, polltimeout); if (ret < 0) { if (errno != EINTR) { @@ -285,7 +387,8 @@ static void create_pack_file(struct upload_pack_data *pack_data) sz = xread(pack_objects.err, progress, sizeof(progress)); if (0 < sz) - send_client_data(2, progress, sz); + send_client_data(2, progress, sz, + pack_data->use_sideband); else if (sz == 0) { close(pack_objects.err); pack_objects.err = -1; @@ -296,39 +399,17 @@ static void create_pack_file(struct upload_pack_data *pack_data) continue; } if (0 <= pu && (pfd[pu].revents & (POLLIN|POLLHUP))) { - /* Data ready; we keep the last byte to ourselves - * in case we detect broken rev-list, so that we - * can leave the stream corrupted. This is - * unfortunate -- unpack-objects would happily - * accept a valid packdata with trailing garbage, - * so appending garbage after we pass all the - * pack data is not good enough to signal - * breakage to downstream. - */ - char *cp = data; - ssize_t outsz = 0; - if (0 <= buffered) { - *cp++ = buffered; - outsz++; - } - sz = xread(pack_objects.out, cp, - sizeof(data) - outsz); - if (0 < sz) - ; - else if (sz == 0) { + int result = relay_pack_data(pack_objects.out, + &output_state, + pack_data->use_sideband, + !!uri_protocols); + + if (result == 0) { close(pack_objects.out); pack_objects.out = -1; - } - else + } else if (result < 0) { goto fail; - sz += outsz; - if (1 < sz) { - buffered = data[sz-1] & 0xFF; - sz--; } - else - buffered = -1; - send_client_data(1, data, sz); } /* @@ -341,7 +422,7 @@ static void create_pack_file(struct upload_pack_data *pack_data) * protocol to say anything, so those clients are just out of * luck. */ - if (!ret && use_sideband) { + if (!ret && pack_data->use_sideband) { static const char buf[] = "0005\1"; write_or_die(1, buf, 5); } @@ -353,32 +434,26 @@ static void create_pack_file(struct upload_pack_data *pack_data) } /* flush the data */ - if (0 <= buffered) { - data[0] = buffered; - send_client_data(1, data, 1); + if (output_state.used > 0) { + send_client_data(1, output_state.buffer, output_state.used, + pack_data->use_sideband); fprintf(stderr, "flushed.\n"); } - if (use_sideband) + if (pack_data->use_sideband) packet_flush(1); return; fail: - send_client_data(3, abort_msg, sizeof(abort_msg)); + send_client_data(3, abort_msg, sizeof(abort_msg), + pack_data->use_sideband); die("git upload-pack: %s", abort_msg); } -static int got_oid(const char *hex, struct object_id *oid, - struct object_array *have_obj) +static int do_got_oid(struct upload_pack_data *data, const struct object_id *oid) { - struct object *o; int we_knew_they_have = 0; + struct object *o = parse_object(the_repository, oid); - if (get_oid_hex(hex, oid)) - die("git upload-pack: expected SHA1 object, got '%s'", hex); - if (!has_object_file(oid)) - return -1; - - o = parse_object(the_repository, oid); if (!o) die("oops (%s)", oid_to_hex(oid)); if (o->type == OBJ_COMMIT) { @@ -388,30 +463,39 @@ static int got_oid(const char *hex, struct object_id *oid, we_knew_they_have = 1; else o->flags |= THEY_HAVE; - if (!oldest_have || (commit->date < oldest_have)) - oldest_have = commit->date; + if (!data->oldest_have || (commit->date < data->oldest_have)) + data->oldest_have = commit->date; for (parents = commit->parents; parents; parents = parents->next) parents->item->object.flags |= THEY_HAVE; } if (!we_knew_they_have) { - add_object_array(o, NULL, have_obj); + add_object_array(o, NULL, &data->have_obj); return 1; } return 0; } -static int ok_to_give_up(const struct object_array *have_obj, - struct object_array *want_obj) +static int got_oid(struct upload_pack_data *data, + const char *hex, struct object_id *oid) +{ + if (get_oid_hex(hex, oid)) + die("git upload-pack: expected SHA1 object, got '%s'", hex); + if (!has_object_file(oid)) + return -1; + return do_got_oid(data, oid); +} + +static int ok_to_give_up(struct upload_pack_data *data) { uint32_t min_generation = GENERATION_NUMBER_ZERO; - if (!have_obj->nr) + if (!data->have_obj.nr) return 0; - return can_all_from_reach_with_flag(want_obj, THEY_HAVE, - COMMON_KNOWN, oldest_have, + return can_all_from_reach_with_flag(&data->want_obj, THEY_HAVE, + COMMON_KNOWN, data->oldest_have, min_generation); } @@ -429,20 +513,20 @@ static int get_common_commits(struct upload_pack_data *data, for (;;) { const char *arg; - reset_timeout(); + reset_timeout(data->timeout); if (packet_reader_read(reader) != PACKET_READ_NORMAL) { - if (multi_ack == 2 + if (data->multi_ack == MULTI_ACK_DETAILED && got_common && !got_other - && ok_to_give_up(&data->have_obj, &data->want_obj)) { + && ok_to_give_up(data)) { sent_ready = 1; packet_write_fmt(1, "ACK %s ready\n", last_hex); } - if (data->have_obj.nr == 0 || multi_ack) + if (data->have_obj.nr == 0 || data->multi_ack) packet_write_fmt(1, "NAK\n"); - if (no_done && sent_ready) { + if (data->no_done && sent_ready) { packet_write_fmt(1, "ACK %s\n", last_hex); return 0; } @@ -453,13 +537,13 @@ static int get_common_commits(struct upload_pack_data *data, continue; } if (skip_prefix(reader->line, "have ", &arg)) { - switch (got_oid(arg, &oid, &data->have_obj)) { + switch (got_oid(data, arg, &oid)) { case -1: /* they have what we do not */ got_other = 1; - if (multi_ack - && ok_to_give_up(&data->have_obj, &data->want_obj)) { + if (data->multi_ack + && ok_to_give_up(data)) { const char *hex = oid_to_hex(&oid); - if (multi_ack == 2) { + if (data->multi_ack == MULTI_ACK_DETAILED) { sent_ready = 1; packet_write_fmt(1, "ACK %s ready\n", hex); } else @@ -469,9 +553,9 @@ static int get_common_commits(struct upload_pack_data *data, default: got_common = 1; oid_to_hex_r(last_hex, &oid); - if (multi_ack == 2) + if (data->multi_ack == MULTI_ACK_DETAILED) packet_write_fmt(1, "ACK %s common\n", last_hex); - else if (multi_ack) + else if (data->multi_ack) packet_write_fmt(1, "ACK %s continue\n", last_hex); else if (data->have_obj.nr == 1) packet_write_fmt(1, "ACK %s\n", last_hex); @@ -481,7 +565,7 @@ static int get_common_commits(struct upload_pack_data *data, } if (!strcmp(reader->line, "done")) { if (data->have_obj.nr > 0) { - if (multi_ack) + if (data->multi_ack) packet_write_fmt(1, "ACK %s\n", last_hex); return 0; } @@ -492,10 +576,10 @@ static int get_common_commits(struct upload_pack_data *data, } } -static int is_our_ref(struct object *o) +static int is_our_ref(struct object *o, enum allow_uor allow_uor) { - int allow_hidden_ref = (allow_unadvertised_object_request & - (ALLOW_TIP_SHA1 | ALLOW_REACHABLE_SHA1)); + int allow_hidden_ref = (allow_uor & + (ALLOW_TIP_SHA1 | ALLOW_REACHABLE_SHA1)); return o->flags & ((allow_hidden_ref ? HIDDEN_REF : 0) | OUR_REF); } @@ -504,7 +588,8 @@ static int is_our_ref(struct object *o) */ static int do_reachable_revlist(struct child_process *cmd, struct object_array *src, - struct object_array *reachable) + struct object_array *reachable, + enum allow_uor allow_uor) { static const char *argv[] = { "rev-list", "--stdin", NULL, @@ -538,7 +623,7 @@ static int do_reachable_revlist(struct child_process *cmd, continue; if (reachable && o->type == OBJ_COMMIT) o->flags &= ~TMP_MARK; - if (!is_our_ref(o)) + if (!is_our_ref(o, allow_uor)) continue; memcpy(namebuf + 1, oid_to_hex(&o->oid), hexsz); if (write_in_full(cmd->in, namebuf, hexsz + 2) < 0) @@ -547,7 +632,7 @@ static int do_reachable_revlist(struct child_process *cmd, namebuf[hexsz] = '\n'; for (i = 0; i < src->nr; i++) { o = src->objects[i].item; - if (is_our_ref(o)) { + if (is_our_ref(o, allow_uor)) { if (reachable) add_object_array(o, NULL, reachable); continue; @@ -574,7 +659,7 @@ error: return -1; } -static int get_reachable_list(struct object_array *src, +static int get_reachable_list(struct upload_pack_data *data, struct object_array *reachable) { struct child_process cmd = CHILD_PROCESS_INIT; @@ -583,7 +668,8 @@ static int get_reachable_list(struct object_array *src, char namebuf[GIT_MAX_HEXSZ + 2]; /* ^ + hash + LF */ const unsigned hexsz = the_hash_algo->hexsz; - if (do_reachable_revlist(&cmd, src, reachable) < 0) + if (do_reachable_revlist(&cmd, &data->shallows, reachable, + data->allow_uor) < 0) return -1; while ((i = read_in_full(cmd.out, namebuf, hexsz + 1)) == hexsz + 1) { @@ -614,13 +700,13 @@ static int get_reachable_list(struct object_array *src, return 0; } -static int has_unreachable(struct object_array *src) +static int has_unreachable(struct object_array *src, enum allow_uor allow_uor) { struct child_process cmd = CHILD_PROCESS_INIT; char buf[1]; int i; - if (do_reachable_revlist(&cmd, src, NULL) < 0) + if (do_reachable_revlist(&cmd, src, NULL, allow_uor) < 0) return 1; /* @@ -660,10 +746,9 @@ static void check_non_tip(struct upload_pack_data *data) * uploadpack.allowReachableSHA1InWant, * non-tip requests can never happen. */ - if (!data->stateless_rpc - && !(allow_unadvertised_object_request & ALLOW_REACHABLE_SHA1)) + if (!data->stateless_rpc && !(data->allow_uor & ALLOW_REACHABLE_SHA1)) goto error; - if (!has_unreachable(&data->want_obj)) + if (!has_unreachable(&data->want_obj, data->allow_uor)) /* All the non-tip ones are ancestors of what we advertised */ return; @@ -671,7 +756,7 @@ error: /* Pick one of them (we know there at least is one) */ for (i = 0; i < data->want_obj.nr; i++) { struct object *o = data->want_obj.objects[i].item; - if (!is_our_ref(o)) { + if (!is_our_ref(o, data->allow_uor)) { packet_writer_error(&data->writer, "upload-pack: not our ref %s", oid_to_hex(&o->oid)); @@ -681,32 +766,30 @@ error: } } -static void send_shallow(struct packet_writer *writer, +static void send_shallow(struct upload_pack_data *data, struct commit_list *result) { while (result) { struct object *object = &result->item->object; if (!(object->flags & (CLIENT_SHALLOW|NOT_SHALLOW))) { - packet_writer_write(writer, "shallow %s", + packet_writer_write(&data->writer, "shallow %s", oid_to_hex(&object->oid)); register_shallow(the_repository, &object->oid); - shallow_nr++; + data->shallow_nr++; } result = result->next; } } -static void send_unshallow(struct packet_writer *writer, - const struct object_array *shallows, - struct object_array *want_obj) +static void send_unshallow(struct upload_pack_data *data) { int i; - for (i = 0; i < shallows->nr; i++) { - struct object *object = shallows->objects[i].item; + for (i = 0; i < data->shallows.nr; i++) { + struct object *object = data->shallows.objects[i].item; if (object->flags & NOT_SHALLOW) { struct commit_list *parents; - packet_writer_write(writer, "unshallow %s", + packet_writer_write(&data->writer, "unshallow %s", oid_to_hex(&object->oid)); object->flags &= ~CLIENT_SHALLOW; /* @@ -722,10 +805,10 @@ static void send_unshallow(struct packet_writer *writer, parents = ((struct commit *)object)->parents; while (parents) { add_object_array(&parents->item->object, - NULL, want_obj); + NULL, &data->want_obj); parents = parents->next; } - add_object_array(object, NULL, &extra_edge_obj); + add_object_array(object, NULL, &data->extra_edge_obj); } /* make sure commit traversal conforms to client */ register_shallow(the_repository, &object->oid); @@ -734,17 +817,16 @@ static void send_unshallow(struct packet_writer *writer, static int check_ref(const char *refname_full, const struct object_id *oid, int flag, void *cb_data); -static void deepen(struct packet_writer *writer, int depth, int deepen_relative, - struct object_array *shallows, struct object_array *want_obj) +static void deepen(struct upload_pack_data *data, int depth) { if (depth == INFINITE_DEPTH && !is_repository_shallow(the_repository)) { int i; - for (i = 0; i < shallows->nr; i++) { - struct object *object = shallows->objects[i].item; + for (i = 0; i < data->shallows.nr; i++) { + struct object *object = data->shallows.objects[i].item; object->flags |= NOT_SHALLOW; } - } else if (deepen_relative) { + } else if (data->deepen_relative) { struct object_array reachable_shallows = OBJECT_ARRAY_INIT; struct commit_list *result; @@ -755,87 +837,80 @@ static void deepen(struct packet_writer *writer, int depth, int deepen_relative, head_ref_namespaced(check_ref, NULL); for_each_namespaced_ref(check_ref, NULL); - get_reachable_list(shallows, &reachable_shallows); + get_reachable_list(data, &reachable_shallows); result = get_shallow_commits(&reachable_shallows, depth + 1, SHALLOW, NOT_SHALLOW); - send_shallow(writer, result); + send_shallow(data, result); free_commit_list(result); object_array_clear(&reachable_shallows); } else { struct commit_list *result; - result = get_shallow_commits(want_obj, depth, + result = get_shallow_commits(&data->want_obj, depth, SHALLOW, NOT_SHALLOW); - send_shallow(writer, result); + send_shallow(data, result); free_commit_list(result); } - send_unshallow(writer, shallows, want_obj); + send_unshallow(data); } -static void deepen_by_rev_list(struct packet_writer *writer, int ac, - const char **av, - struct object_array *shallows, - struct object_array *want_obj) +static void deepen_by_rev_list(struct upload_pack_data *data, + int ac, + const char **av) { struct commit_list *result; disable_commit_graph(the_repository); result = get_shallow_commits_by_rev_list(ac, av, SHALLOW, NOT_SHALLOW); - send_shallow(writer, result); + send_shallow(data, result); free_commit_list(result); - send_unshallow(writer, shallows, want_obj); + send_unshallow(data); } /* Returns 1 if a shallow list is sent or 0 otherwise */ -static int send_shallow_list(struct packet_writer *writer, - int depth, int deepen_rev_list, - timestamp_t deepen_since, - struct string_list *deepen_not, - int deepen_relative, - struct object_array *shallows, - struct object_array *want_obj) +static int send_shallow_list(struct upload_pack_data *data) { int ret = 0; - if (depth > 0 && deepen_rev_list) + if (data->depth > 0 && data->deepen_rev_list) die("git upload-pack: deepen and deepen-since (or deepen-not) cannot be used together"); - if (depth > 0) { - deepen(writer, depth, deepen_relative, shallows, want_obj); + if (data->depth > 0) { + deepen(data, data->depth); ret = 1; - } else if (deepen_rev_list) { + } else if (data->deepen_rev_list) { struct argv_array av = ARGV_ARRAY_INIT; int i; argv_array_push(&av, "rev-list"); - if (deepen_since) - argv_array_pushf(&av, "--max-age=%"PRItime, deepen_since); - if (deepen_not->nr) { + if (data->deepen_since) + argv_array_pushf(&av, "--max-age=%"PRItime, data->deepen_since); + if (data->deepen_not.nr) { argv_array_push(&av, "--not"); - for (i = 0; i < deepen_not->nr; i++) { - struct string_list_item *s = deepen_not->items + i; + for (i = 0; i < data->deepen_not.nr; i++) { + struct string_list_item *s = data->deepen_not.items + i; argv_array_push(&av, s->string); } argv_array_push(&av, "--not"); } - for (i = 0; i < want_obj->nr; i++) { - struct object *o = want_obj->objects[i].item; + for (i = 0; i < data->want_obj.nr; i++) { + struct object *o = data->want_obj.objects[i].item; argv_array_push(&av, oid_to_hex(&o->oid)); } - deepen_by_rev_list(writer, av.argc, av.argv, shallows, want_obj); + deepen_by_rev_list(data, av.argc, av.argv); argv_array_clear(&av); ret = 1; } else { - if (shallows->nr > 0) { + if (data->shallows.nr > 0) { int i; - for (i = 0; i < shallows->nr; i++) + for (i = 0; i < data->shallows.nr; i++) register_shallow(the_repository, - &shallows->objects[i].item->oid); + &data->shallows.objects[i].item->oid); } } - shallow_nr += shallows->nr; + data->shallow_nr += data->shallows.nr; return ret; } @@ -913,14 +988,14 @@ static void receive_needs(struct upload_pack_data *data, { int has_non_tip = 0; - shallow_nr = 0; + data->shallow_nr = 0; for (;;) { struct object *o; const char *features; struct object_id oid_buf; const char *arg; - reset_timeout(); + reset_timeout(data->timeout); if (packet_reader_read(reader) != PACKET_READ_NORMAL) break; @@ -934,7 +1009,7 @@ static void receive_needs(struct upload_pack_data *data, continue; if (skip_prefix(reader->line, "filter ", &arg)) { - if (!filter_capability_requested) + if (!data->filter_capability_requested) die("git upload-pack: filtering capability not negotiated"); list_objects_filter_die_if_populated(&data->filter_options); parse_list_objects_filter(&data->filter_options, arg); @@ -949,25 +1024,26 @@ static void receive_needs(struct upload_pack_data *data, if (parse_feature_request(features, "deepen-relative")) data->deepen_relative = 1; if (parse_feature_request(features, "multi_ack_detailed")) - multi_ack = 2; + data->multi_ack = MULTI_ACK_DETAILED; else if (parse_feature_request(features, "multi_ack")) - multi_ack = 1; + data->multi_ack = MULTI_ACK; if (parse_feature_request(features, "no-done")) - no_done = 1; + data->no_done = 1; if (parse_feature_request(features, "thin-pack")) - use_thin_pack = 1; + data->use_thin_pack = 1; if (parse_feature_request(features, "ofs-delta")) - use_ofs_delta = 1; + data->use_ofs_delta = 1; if (parse_feature_request(features, "side-band-64k")) - use_sideband = LARGE_PACKET_MAX; + data->use_sideband = LARGE_PACKET_MAX; else if (parse_feature_request(features, "side-band")) - use_sideband = DEFAULT_PACKET_MAX; + data->use_sideband = DEFAULT_PACKET_MAX; if (parse_feature_request(features, "no-progress")) - no_progress = 1; + data->no_progress = 1; if (parse_feature_request(features, "include-tag")) - use_include_tag = 1; - if (allow_filter && parse_feature_request(features, "filter")) - filter_capability_requested = 1; + data->use_include_tag = 1; + if (data->allow_filter && + parse_feature_request(features, "filter")) + data->filter_capability_requested = 1; o = parse_object(the_repository, &oid_buf); if (!o) { @@ -979,8 +1055,8 @@ static void receive_needs(struct upload_pack_data *data, } if (!(o->flags & WANTED)) { o->flags |= WANTED; - if (!((allow_unadvertised_object_request & ALLOW_ANY_SHA1) == ALLOW_ANY_SHA1 - || is_our_ref(o))) + if (!((data->allow_uor & ALLOW_ANY_SHA1) == ALLOW_ANY_SHA1 + || is_our_ref(o, data->allow_uor))) has_non_tip = 1; add_object_array(o, NULL, &data->want_obj); } @@ -996,20 +1072,13 @@ static void receive_needs(struct upload_pack_data *data, if (has_non_tip) check_non_tip(data); - if (!use_sideband && daemon_mode) - no_progress = 1; + if (!data->use_sideband && data->daemon_mode) + data->no_progress = 1; if (data->depth == 0 && !data->deepen_rev_list && data->shallows.nr == 0) return; - if (send_shallow_list(&data->writer, - data->depth, - data->deepen_rev_list, - data->deepen_since, - &data->deepen_not, - data->deepen_relative, - &data->shallows, - &data->want_obj)) + if (send_shallow_list(data)) packet_flush(1); } @@ -1063,16 +1132,17 @@ static int send_ref(const char *refname, const struct object_id *oid, struct strbuf symref_info = STRBUF_INIT; format_symref_info(&symref_info, &data->symref); - packet_write_fmt(1, "%s %s%c%s%s%s%s%s%s agent=%s\n", + packet_write_fmt(1, "%s %s%c%s%s%s%s%s%s object-format=%s agent=%s\n", oid_to_hex(oid), refname_nons, 0, capabilities, - (allow_unadvertised_object_request & ALLOW_TIP_SHA1) ? + (data->allow_uor & ALLOW_TIP_SHA1) ? " allow-tip-sha1-in-want" : "", - (allow_unadvertised_object_request & ALLOW_REACHABLE_SHA1) ? + (data->allow_uor & ALLOW_REACHABLE_SHA1) ? " allow-reachable-sha1-in-want" : "", data->stateless_rpc ? " no-done" : "", symref_info.buf, - allow_filter ? " filter" : "", + data->allow_filter ? " filter" : "", + the_hash_algo->name, git_user_agent_sanitized()); strbuf_release(&symref_info); } else { @@ -1100,33 +1170,35 @@ static int find_symref(const char *refname, const struct object_id *oid, return 0; } -static int upload_pack_config(const char *var, const char *value, void *unused) +static int upload_pack_config(const char *var, const char *value, void *cb_data) { + struct upload_pack_data *data = cb_data; + if (!strcmp("uploadpack.allowtipsha1inwant", var)) { if (git_config_bool(var, value)) - allow_unadvertised_object_request |= ALLOW_TIP_SHA1; + data->allow_uor |= ALLOW_TIP_SHA1; else - allow_unadvertised_object_request &= ~ALLOW_TIP_SHA1; + data->allow_uor &= ~ALLOW_TIP_SHA1; } else if (!strcmp("uploadpack.allowreachablesha1inwant", var)) { if (git_config_bool(var, value)) - allow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1; + data->allow_uor |= ALLOW_REACHABLE_SHA1; else - allow_unadvertised_object_request &= ~ALLOW_REACHABLE_SHA1; + data->allow_uor &= ~ALLOW_REACHABLE_SHA1; } else if (!strcmp("uploadpack.allowanysha1inwant", var)) { if (git_config_bool(var, value)) - allow_unadvertised_object_request |= ALLOW_ANY_SHA1; + data->allow_uor |= ALLOW_ANY_SHA1; else - allow_unadvertised_object_request &= ~ALLOW_ANY_SHA1; + data->allow_uor &= ~ALLOW_ANY_SHA1; } else if (!strcmp("uploadpack.keepalive", var)) { - keepalive = git_config_int(var, value); - if (!keepalive) - keepalive = -1; + data->keepalive = git_config_int(var, value); + if (!data->keepalive) + data->keepalive = -1; } else if (!strcmp("uploadpack.allowfilter", var)) { - allow_filter = git_config_bool(var, value); + data->allow_filter = git_config_bool(var, value); } else if (!strcmp("uploadpack.allowrefinwant", var)) { - allow_ref_in_want = git_config_bool(var, value); + data->allow_ref_in_want = git_config_bool(var, value); } else if (!strcmp("uploadpack.allowsidebandall", var)) { - allow_sideband_all = git_config_bool(var, value); + data->allow_sideband_all = git_config_bool(var, value); } else if (!strcmp("core.precomposeunicode", var)) { precomposed_unicode = git_config_bool(var, value); } @@ -1134,7 +1206,7 @@ static int upload_pack_config(const char *var, const char *value, void *unused) if (current_config_scope() != CONFIG_SCOPE_LOCAL && current_config_scope() != CONFIG_SCOPE_WORKTREE) { if (!strcmp("uploadpack.packobjectshook", var)) - return git_config_string(&pack_objects_hook, var, value); + return git_config_string(&data->pack_objects_hook, var, value); } return parse_hide_refs_config(var, value, "uploadpack"); @@ -1145,19 +1217,18 @@ void upload_pack(struct upload_pack_options *options) struct packet_reader reader; struct upload_pack_data data; - timeout = options->timeout; - daemon_mode = options->daemon_mode; - - git_config(upload_pack_config, NULL); - upload_pack_data_init(&data); + git_config(upload_pack_config, &data); + data.stateless_rpc = options->stateless_rpc; + data.daemon_mode = options->daemon_mode; + data.timeout = options->timeout; head_ref_namespaced(find_symref, &data.symref); if (options->advertise_refs || !data.stateless_rpc) { - reset_timeout(); + reset_timeout(data.timeout); head_ref_namespaced(send_ref, &data); for_each_namespaced_ref(send_ref, &data); advertise_shallow_grafts(1); @@ -1175,7 +1246,7 @@ void upload_pack(struct upload_pack_options *options) receive_needs(&data, &reader); if (data.want_obj.nr) { get_common_commits(&data, &reader); - create_pack_file(&data); + create_pack_file(&data, NULL); } } @@ -1269,7 +1340,7 @@ static void process_args(struct packet_reader *request, /* process want */ if (parse_want(&data->writer, arg, &data->want_obj)) continue; - if (allow_ref_in_want && + if (data->allow_ref_in_want && parse_want_ref(&data->writer, arg, &data->wanted_refs, &data->want_obj)) continue; @@ -1279,19 +1350,19 @@ static void process_args(struct packet_reader *request, /* process args like thin-pack */ if (!strcmp(arg, "thin-pack")) { - use_thin_pack = 1; + data->use_thin_pack = 1; continue; } if (!strcmp(arg, "ofs-delta")) { - use_ofs_delta = 1; + data->use_ofs_delta = 1; continue; } if (!strcmp(arg, "no-progress")) { - no_progress = 1; + data->no_progress = 1; continue; } if (!strcmp(arg, "include-tag")) { - use_include_tag = 1; + data->use_include_tag = 1; continue; } if (!strcmp(arg, "done")) { @@ -1315,87 +1386,72 @@ static void process_args(struct packet_reader *request, continue; } - if (allow_filter && skip_prefix(arg, "filter ", &p)) { + if (data->allow_filter && skip_prefix(arg, "filter ", &p)) { list_objects_filter_die_if_populated(&data->filter_options); parse_list_objects_filter(&data->filter_options, p); continue; } if ((git_env_bool("GIT_TEST_SIDEBAND_ALL", 0) || - allow_sideband_all) && + data->allow_sideband_all) && !strcmp(arg, "sideband-all")) { data->writer.use_sideband = 1; continue; } + if (skip_prefix(arg, "packfile-uris ", &p)) { + string_list_split(&data->uri_protocols, p, ',', -1); + continue; + } + /* ignore unknown lines maybe? */ die("unexpected line: '%s'", arg); } + if (data->uri_protocols.nr && !data->writer.use_sideband) + string_list_clear(&data->uri_protocols, 0); + if (request->status != PACKET_READ_FLUSH) die(_("expected flush after fetch arguments")); } -static int process_haves(struct oid_array *haves, struct oid_array *common, - struct object_array *have_obj) +static int process_haves(struct upload_pack_data *data, struct oid_array *common) { int i; /* Process haves */ - for (i = 0; i < haves->nr; i++) { - const struct object_id *oid = &haves->oid[i]; - struct object *o; - int we_knew_they_have = 0; + for (i = 0; i < data->haves.nr; i++) { + const struct object_id *oid = &data->haves.oid[i]; if (!has_object_file(oid)) continue; oid_array_append(common, oid); - o = parse_object(the_repository, oid); - if (!o) - die("oops (%s)", oid_to_hex(oid)); - if (o->type == OBJ_COMMIT) { - struct commit_list *parents; - struct commit *commit = (struct commit *)o; - if (o->flags & THEY_HAVE) - we_knew_they_have = 1; - else - o->flags |= THEY_HAVE; - if (!oldest_have || (commit->date < oldest_have)) - oldest_have = commit->date; - for (parents = commit->parents; - parents; - parents = parents->next) - parents->item->object.flags |= THEY_HAVE; - } - if (!we_knew_they_have) - add_object_array(o, NULL, have_obj); + do_got_oid(data, oid); } return 0; } -static int send_acks(struct packet_writer *writer, struct oid_array *acks, - const struct object_array *have_obj, - struct object_array *want_obj) +static int send_acks(struct upload_pack_data *data, struct oid_array *acks) { int i; - packet_writer_write(writer, "acknowledgments\n"); + packet_writer_write(&data->writer, "acknowledgments\n"); /* Send Acks */ if (!acks->nr) - packet_writer_write(writer, "NAK\n"); + packet_writer_write(&data->writer, "NAK\n"); for (i = 0; i < acks->nr; i++) { - packet_writer_write(writer, "ACK %s\n", + packet_writer_write(&data->writer, "ACK %s\n", oid_to_hex(&acks->oid[i])); } - if (ok_to_give_up(have_obj, want_obj)) { + if (ok_to_give_up(data)) { /* Send Ready */ - packet_writer_write(writer, "ready\n"); + packet_writer_write(&data->writer, "ready\n"); return 1; } @@ -1407,11 +1463,10 @@ static int process_haves_and_send_acks(struct upload_pack_data *data) struct oid_array common = OID_ARRAY_INIT; int ret = 0; - process_haves(&data->haves, &common, &data->have_obj); + process_haves(data, &common); if (data->done) { ret = 1; - } else if (send_acks(&data->writer, &common, - &data->have_obj, &data->want_obj)) { + } else if (send_acks(data, &common)) { packet_writer_delim(&data->writer); ret = 1; } else { @@ -1452,14 +1507,9 @@ static void send_shallow_info(struct upload_pack_data *data) packet_writer_write(&data->writer, "shallow-info\n"); - if (!send_shallow_list(&data->writer, data->depth, - data->deepen_rev_list, - data->deepen_since, &data->deepen_not, - data->deepen_relative, - &data->shallows, &data->want_obj) && + if (!send_shallow_list(data) && is_repository_shallow(the_repository)) - deepen(&data->writer, INFINITE_DEPTH, data->deepen_relative, - &data->shallows, &data->want_obj); + deepen(data, INFINITE_DEPTH); packet_delim(1); } @@ -1479,10 +1529,10 @@ int upload_pack_v2(struct repository *r, struct argv_array *keys, clear_object_flags(ALL_FLAGS); - git_config(upload_pack_config, NULL); - upload_pack_data_init(&data); - use_sideband = LARGE_PACKET_MAX; + data.use_sideband = LARGE_PACKET_MAX; + + git_config(upload_pack_config, &data); while (state != FETCH_DONE) { switch (state) { @@ -1518,8 +1568,12 @@ int upload_pack_v2(struct repository *r, struct argv_array *keys, send_wanted_ref_info(&data); send_shallow_info(&data); - packet_writer_write(&data.writer, "packfile\n"); - create_pack_file(&data); + if (data.uri_protocols.nr) { + create_pack_file(&data, &data.uri_protocols); + } else { + packet_writer_write(&data.writer, "packfile\n"); + create_pack_file(&data, NULL); + } state = FETCH_DONE; break; case FETCH_DONE: @@ -1538,6 +1592,7 @@ int upload_pack_advertise(struct repository *r, int allow_filter_value; int allow_ref_in_want; int allow_sideband_all_value; + char *str = NULL; strbuf_addstr(value, "shallow"); @@ -1559,6 +1614,14 @@ int upload_pack_advertise(struct repository *r, &allow_sideband_all_value) && allow_sideband_all_value)) strbuf_addstr(value, " sideband-all"); + + if (!repo_config_get_string(the_repository, + "uploadpack.blobpackfileuri", + &str) && + str) { + strbuf_addstr(value, " packfile-uris"); + free(str); + } } return 1; @@ -105,6 +105,14 @@ char *xstrndup(const char *str, size_t len) return xmemdupz(str, p ? p - str : len); } +int xstrncmpz(const char *s, const char *t, size_t len) +{ + int res = strncmp(s, t, len); + if (res) + return res; + return s[len] == '\0' ? 0 : 1; +} + void *xrealloc(void *ptr, size_t size) { void *ret; |
