diff options
142 files changed, 6517 insertions, 849 deletions
diff --git a/.gitignore b/.gitignore index 9673e792db..800b812c6a 100644 --- a/.gitignore +++ b/.gitignore @@ -90,6 +90,7 @@ /git-ls-tree /git-mailinfo /git-mailsplit +/git-maintenance /git-merge /git-merge-base /git-merge-index @@ -196,6 +197,7 @@ /git.spec *.exe *.[aos] +*.o.json *.py[co] .depend/ *.gcda @@ -217,6 +219,7 @@ /tags /TAGS /cscope* +/compile_commands.json *.hcc *.obj *.lib diff --git a/Documentation/RelNotes/2.29.0.txt b/Documentation/RelNotes/2.29.0.txt index 4adb9ce124..ccbe9b74d0 100644 --- a/Documentation/RelNotes/2.29.0.txt +++ b/Documentation/RelNotes/2.29.0.txt @@ -64,6 +64,33 @@ UI, Workflows & Features learned a few more tricks, e.g. the ":short" suffix that applies to "objectname" now also can be used for "parent", "tree", etc. + * "git worktree add" learns that the "-d" is a synonym to "--detach" + option to create a new worktree without being on a branch. + + * "format-patch --range-diff=<prev> <origin>..HEAD" has been taught + not to ignore <origin> when <prev> is a single version. + + * "add -p" now allows editing paths that were only added in intent. + + * The 'meld' backend of the "git mergetool" learned to give the + underlying 'meld' the '--auto-merge' option, which would help + reduce the amount of text that requires manual merging. + + * "git for-each-ref" and friends that list refs used to allow only + one --merged or --no-merged to filter them; they learned to take + combination of both kind of filtering. + + * "git maintenance", a "git gc"'s big brother, has been introduced to + take care of more repository maintenance tasks, not limited to the + object database cleaning. + + * "git receive-pack" that accepts requests by "git push" learned to + outsource most of the ref updates to the new "proc-receive" hook. + + * "git push" that wants to be atomic and wants to send push + certificate learned not to prepare and sign the push certificate + when it fails the local check (hence due to atomicity it is known + that no certificate is needed). Performance, Internal Implementation, Development Support etc. @@ -145,6 +172,19 @@ Performance, Internal Implementation, Development Support etc. * Internal API clean-up to handle two options "diff-index" and "log" have, which happen to share the same short form, more sensibly. + * The "add -i/-p" machinery has been written in C but it is not used + by default yet. It is made default to those who are participating + in feature.experimental experiment. + + * Allow maintainers to tweak $(TAR) invocations done while making + distribution tarballs. + + * "git index-pack" learned to resolve deltified objects with greater + parallelism. + + * "diff-highlight" (in contrib/) had a logic to flush its output upon + seeing a blank line but the way it detected a blank line was broken. + Fixes since v2.28 ----------------- @@ -304,7 +344,7 @@ Fixes since v2.28 (merge 6479ea4a8a jk/xrealloc-avoid-use-after-free later to maint). * "git status" has trouble showing where it came from by interpreting - reflog entries that recordcertain events, e.g. "checkout @{u}", and + reflog entries that record certain events, e.g. "checkout @{u}", and gives a hard/fatal error. Even though it inherently is impossible to give a correct answer because the reflog entries lose some information (e.g. "@{u}" does not record what branch the user was @@ -313,6 +353,38 @@ Fixes since v2.28 have changed), at least hide the error to allow "status" show its output. + * "git status --short" quoted a path with SP in it when tracked, but + not those that are untracked, ignored or unmerged. They are all + shown quoted consistently. + + * "git diff/show" on a change that involves a submodule used to read + the information on commits in the submodule from a wrong repository + and gave a wrong information when the commit-graph is involved. + (merge 85a1ec2c32 mf/submodule-summary-with-correct-repository later to maint). + + * Unlike "git config --local", "git config --worktree" did not fail + early and cleanly when started outside a git repository. + (merge 378fe5fc3d mt/config-fail-nongit-early later to maint). + + * There is a logic to estimate how many objects are in the + repository, which is mean to run once per process invocation, but + it ran every time the estimated value was requested. + (merge 67bb65de5d jk/dont-count-existing-objects-twice later to maint). + + * "git remote set-head" that failed still said something that hints + the operation went through, which was misleading. + (merge 5a07c6c3c2 cs/don-t-pretend-a-failed-remote-set-head-succeeded later to maint). + + * "git fetch --all --ipv4/--ipv6" forgot to pass the protocol options + to instances of the "git fetch" that talk to individual remotes, + which has been corrected. + (merge 4e735c1326 ar/fetch-ipversion-in-all later to maint). + + * The "unshelve" subcommand of "git p4" used incorrectly used + commit^N where it meant to say commit~N to name the Nth generation + ancestor, which has been corrected. + (merge 0acbf5997f ld/p4-unshelve-fix later to maint). + * Other code cleanup, docfix, build fix, etc. (merge 84544f2ea3 sk/typofixes later to maint). (merge b17f411ab5 ar/help-guides-doc later to maint). @@ -339,3 +411,8 @@ Fixes since v2.28 (merge 3100fd5588 jc/post-checkout-doc later to maint). (merge 17bae89476 pb/doc-external-diff-env later to maint). (merge 27ed6ccc12 jk/worktree-check-clean-leakfix later to maint). + (merge 1302badd16 ea/blame-use-oideq later to maint). + (merge e6d5a11fed al/t3200-back-on-a-branch later to maint). + (merge 324efcf6b6 pw/add-p-leakfix later to maint). + (merge 1c6ffb546b jk/add-i-fixes later to maint). + (merge e40e936551 cd/commit-graph-doc later to maint). diff --git a/Documentation/config.txt b/Documentation/config.txt index 3042d80978..f93b6837e4 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -398,6 +398,8 @@ include::config/mailinfo.txt[] include::config/mailmap.txt[] +include::config/maintenance.txt[] + include::config/man.txt[] include::config/merge.txt[] diff --git a/Documentation/config/core.txt b/Documentation/config/core.txt index 74619a9c03..02002cf109 100644 --- a/Documentation/config/core.txt +++ b/Documentation/config/core.txt @@ -399,7 +399,7 @@ the largest projects. You probably do not need to adjust this value. Common unit suffixes of 'k', 'm', or 'g' are supported. core.deltaBaseCacheLimit:: - Maximum number of bytes to reserve for caching base objects + Maximum number of bytes per thread to reserve for caching base objects that may be referenced by multiple deltified objects. By storing the entire decompressed base objects in a cache Git is able to avoid unpacking and decompressing frequently used base diff --git a/Documentation/config/maintenance.txt b/Documentation/config/maintenance.txt new file mode 100644 index 0000000000..7cc6700d57 --- /dev/null +++ b/Documentation/config/maintenance.txt @@ -0,0 +1,16 @@ +maintenance.<task>.enabled:: + This boolean config option controls whether the maintenance task + with name `<task>` is run when no `--task` option is specified to + `git maintenance run`. These config values are ignored if a + `--task` option exists. By default, only `maintenance.gc.enabled` + is true. + +maintenance.commit-graph.auto:: + This integer config option controls how often the `commit-graph` task + should be run as part of `git maintenance run --auto`. If zero, then + the `commit-graph` task will not run with the `--auto` option. A + negative value will force the task to run every time. Otherwise, a + positive value implies the command should run when the number of + reachable commits that are not in the commit-graph file is at least + the value of `maintenance.commit-graph.auto`. The default value is + 100. diff --git a/Documentation/config/mergetool.txt b/Documentation/config/mergetool.txt index 09ed31dbfa..16a27443a3 100644 --- a/Documentation/config/mergetool.txt +++ b/Documentation/config/mergetool.txt @@ -30,6 +30,16 @@ mergetool.meld.hasOutput:: to `true` tells Git to unconditionally use the `--output` option, and `false` avoids using `--output`. +mergetool.meld.useAutoMerge:: + When the `--auto-merge` is given, meld will merge all non-conflicting + parts automatically, highlight the conflicting parts and wait for + user decision. Setting `mergetool.meld.useAutoMerge` to `true` tells + Git to unconditionally use the `--auto-merge` option with `meld`. + Setting this value to `auto` makes git detect whether `--auto-merge` + is supported and will only use `--auto-merge` when available. A + value of `false` avoids using `--auto-merge` altogether, and is the + default value. + mergetool.keepBackup:: After performing a merge, the original file with conflict markers can be saved as a file with a `.orig` extension. If this variable diff --git a/Documentation/config/receive.txt b/Documentation/config/receive.txt index 65f78aac37..85d5b5a3d2 100644 --- a/Documentation/config/receive.txt +++ b/Documentation/config/receive.txt @@ -114,6 +114,28 @@ receive.hideRefs:: An attempt to update or delete a hidden ref by `git push` is rejected. +receive.procReceiveRefs:: + This is a multi-valued variable that defines reference prefixes + to match the commands in `receive-pack`. Commands matching the + prefixes will be executed by an external hook "proc-receive", + instead of the internal `execute_commands` function. If this + variable is not defined, the "proc-receive" hook will never be + used, and all commands will be executed by the internal + `execute_commands` function. ++ +For example, if this variable is set to "refs/for", pushing to reference +such as "refs/for/master" will not create or update a reference named +"refs/for/master", but may create or update a pull request directly by +running the hook "proc-receive". ++ +Optional modifiers can be provided in the beginning of the value to filter +commands for specific actions: create (a), modify (m), delete (d). +A `!` can be included in the modifiers to negate the reference prefix entry. +E.g.: ++ + git config --system --add receive.procReceiveRefs ad:refs/heads + git config --system --add receive.procReceiveRefs !:refs/heads + receive.updateServerInfo:: If set to true, git-receive-pack will run git-update-server-info after receiving data from git-push and updating refs. diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt index e8104c082e..2bf77b46fd 100644 --- a/Documentation/fetch-options.txt +++ b/Documentation/fetch-options.txt @@ -95,9 +95,11 @@ ifndef::git-pull[] Allow several <repository> and <group> arguments to be specified. No <refspec>s may be specified. +--[no-]auto-maintenance:: --[no-]auto-gc:: - Run `git gc --auto` at the end to perform garbage collection - if needed. This is enabled by default. + Run `git maintenance run --auto` at the end to perform automatic + repository maintenance if needed. (`--[no-]auto-gc` is a synonym.) + This is enabled by default. --[no-]write-commit-graph:: Write a commit-graph after fetching. This overrides the config diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 03c0824d52..ace4ad3da8 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -11,7 +11,7 @@ SYNOPSIS 'git branch' [--color[=<when>] | --no-color] [--show-current] [-v [--abbrev=<length> | --no-abbrev]] [--column[=<options>] | --no-column] [--sort=<key>] - [(--merged | --no-merged) [<commit>]] + [--merged [<commit>]] [--no-merged [<commit>]] [--contains [<commit>]] [--no-contains [<commit>]] [--points-at <object>] [--format=<format>] [(-r | --remotes) | (-a | --all)] @@ -252,13 +252,11 @@ start-point is either a local or remote-tracking branch. --merged [<commit>]:: Only list branches whose tips are reachable from the - specified commit (HEAD if not specified). Implies `--list`, - incompatible with `--no-merged`. + specified commit (HEAD if not specified). Implies `--list`. --no-merged [<commit>]:: Only list branches whose tips are not reachable from the - specified commit (HEAD if not specified). Implies `--list`, - incompatible with `--merged`. + specified commit (HEAD if not specified). Implies `--list`. <branchname>:: The name of the branch to create or delete. @@ -370,6 +368,8 @@ serve four related but different purposes: - `--no-merged` is used to find branches which are candidates for merging into HEAD, since those branches are not fully contained by HEAD. +include::ref-reachability-filters.txt[] + SEE ALSO -------- linkgit:git-check-ref-format[1], diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 5b697eee1b..afa5c11fd3 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -198,6 +198,7 @@ Use `--no-guess` to disable this. Create the new branch's reflog; see linkgit:git-branch[1] for details. +-d:: --detach:: Rather than checking out a branch to work on it, check out a commit for inspection and discardable experiments. diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index c898310099..097e6a86c5 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -78,9 +78,9 @@ repository using this option and then delete branches (or use any other Git command that makes any existing commit unreferenced) in the source repository, some objects may become unreferenced (or dangling). These objects may be removed by normal Git operations (such as `git commit`) -which automatically call `git gc --auto`. (See linkgit:git-gc[1].) -If these objects are removed and were referenced by the cloned repository, -then the cloned repository will become corrupt. +which automatically call `git maintenance run --auto`. (See +linkgit:git-maintenance[1].) If these objects are removed and were referenced +by the cloned repository, then the cloned repository will become corrupt. + Note that running `git repack` without the `--local` option in a repository cloned with `--shared` will copy objects from the source repository into a pack diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 616ce46087..2962f85a50 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -11,7 +11,7 @@ SYNOPSIS 'git for-each-ref' [--count=<count>] [--shell|--perl|--python|--tcl] [(--sort=<key>)...] [--format=<format>] [<pattern>...] [--points-at=<object>] - (--merged[=<object>] | --no-merged[=<object>]) + [--merged[=<object>]] [--no-merged[=<object>]] [--contains[=<object>]] [--no-contains[=<object>]] DESCRIPTION @@ -76,13 +76,11 @@ OPTIONS --merged[=<object>]:: Only list refs whose tips are reachable from the - specified commit (HEAD if not specified), - incompatible with `--no-merged`. + specified commit (HEAD if not specified). --no-merged[=<object>]:: Only list refs whose tips are not reachable from the - specified commit (HEAD if not specified), - incompatible with `--merged`. + specified commit (HEAD if not specified). --contains[=<object>]:: Only list refs which contain the specified commit (HEAD if not @@ -408,6 +406,11 @@ Note also that multiple copies of an object may be present in the object database; in this case, it is undefined which copy's size or delta base will be reported. +NOTES +----- + +include::ref-reachability-filters.txt[] + SEE ALSO -------- linkgit:git-show-ref[1] diff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt new file mode 100644 index 0000000000..6abcb8255a --- /dev/null +++ b/Documentation/git-maintenance.txt @@ -0,0 +1,79 @@ +git-maintenance(1) +================== + +NAME +---- +git-maintenance - Run tasks to optimize Git repository data + + +SYNOPSIS +-------- +[verse] +'git maintenance' run [<options>] + + +DESCRIPTION +----------- +Run tasks to optimize Git repository data, speeding up other Git commands +and reducing storage requirements for the repository. + +Git commands that add repository data, such as `git add` or `git fetch`, +are optimized for a responsive user experience. These commands do not take +time to optimize the Git data, since such optimizations scale with the full +size of the repository while these user commands each perform a relatively +small action. + +The `git maintenance` command provides flexibility for how to optimize the +Git repository. + +SUBCOMMANDS +----------- + +run:: + Run one or more maintenance tasks. If one or more `--task` options + are specified, then those tasks are run in that order. Otherwise, + the tasks are determined by which `maintenance.<task>.enabled` + config options are true. By default, only `maintenance.gc.enabled` + is true. + +TASKS +----- + +commit-graph:: + The `commit-graph` job updates the `commit-graph` files incrementally, + then verifies that the written data is correct. The incremental + write is safe to run alongside concurrent Git processes since it + will not expire `.graph` files that were in the previous + `commit-graph-chain` file. They will be deleted by a later run based + on the expiration delay. + +gc:: + Clean up unnecessary files and optimize the local repository. "GC" + stands for "garbage collection," but this task performs many + smaller tasks. This task can be expensive for large repositories, + as it repacks all Git objects into a single pack-file. It can also + be disruptive in some situations, as it deletes stale data. See + linkgit:git-gc[1] for more details on garbage collection in Git. + +OPTIONS +------- +--auto:: + When combined with the `run` subcommand, run maintenance tasks + only if certain thresholds are met. For example, the `gc` task + runs when the number of loose objects exceeds the number stored + in the `gc.auto` config setting, or when the number of pack-files + exceeds the `gc.autoPackLimit` config setting. + +--quiet:: + Do not report progress or other information over `stderr`. + +--task=<task>:: + If this option is specified one or more times, then only run the + specified tasks in the specified order. If no `--task=<task>` + arguments are specified, then only the tasks with + `maintenance.<task>.enabled` configured as `true` are considered. + See the 'TASKS' section for the list of accepted `<task>` values. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index f6d9791780..56656d1be6 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -15,7 +15,7 @@ SYNOPSIS 'git tag' [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>] [--points-at <object>] [--column[=<options>] | --no-column] [--create-reflog] [--sort=<key>] [--format=<format>] - [--[no-]merged [<commit>]] [<pattern>...] + [--merged <commit>] [--no-merged <commit>] [<pattern>...] 'git tag' -v [--format=<format>] <tagname>... DESCRIPTION @@ -149,11 +149,11 @@ This option is only applicable when listing tags without annotation lines. --merged [<commit>]:: Only list tags whose commits are reachable from the specified - commit (`HEAD` if not specified), incompatible with `--no-merged`. + commit (`HEAD` if not specified). --no-merged [<commit>]:: Only list tags whose commits are not reachable from the specified - commit (`HEAD` if not specified), incompatible with `--merged`. + commit (`HEAD` if not specified). --points-at <object>:: Only list tags of the given object (HEAD if not @@ -377,6 +377,11 @@ $ GIT_COMMITTER_DATE="2006-10-02 10:31" git tag -s v1.0.1 include::date-formats.txt[] +NOTES +----- + +include::ref-reachability-filters.txt[] + SEE ALSO -------- linkgit:git-check-ref-format[1]. diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt index f70cda4b36..32e8440cde 100644 --- a/Documentation/git-worktree.txt +++ b/Documentation/git-worktree.txt @@ -32,6 +32,18 @@ A repository has one main working tree (if it's not a bare repository) and zero or more linked working trees. When you are done with a linked working tree, remove it with `git worktree remove`. +In its simplest form, `git worktree add <path>` automatically creates a +new branch whose name is the final component of `<path>`, which is +convenient if you plan to work on a new topic. For instance, `git +worktree add ../hotfix` creates new branch `hotfix` and checks it out at +path `../hotfix`. To instead work on an existing branch in a new working +tree, use `git worktree add <path> <branch>`. On the other hand, if you +just plan to make some experimental changes or do testing without +disturbing existing development, it is often convenient to create a +'throwaway' working tree not associated with any branch. For instance, +`git worktree add -d <path>` creates a new working tree with a detached +`HEAD` at the same commit as the current branch. + If a working tree is deleted without using `git worktree remove`, then its associated administrative files, which reside in the repository (see "DETAILS" below), will eventually be removed automatically (see @@ -164,6 +176,7 @@ To remove a locked working tree, specify `--force` twice. exists. `-B` overrides this safeguard, resetting `<new-branch>` to `<commit-ish>`. +-d:: --detach:: With `add`, detach `HEAD` in the new working tree. See "DETACHED HEAD" in linkgit:git-checkout[1]. diff --git a/Documentation/git.txt b/Documentation/git.txt index 2f72b10224..c463b937a8 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -722,6 +722,10 @@ of clones and fetches. time of each Git command. See `GIT_TRACE` for available trace output options. +`GIT_TRACE_REFS`:: + Enables trace messages for operations on the ref database. + See `GIT_TRACE` for available trace output options. + `GIT_TRACE_SETUP`:: Enables trace messages printing the .git, working tree and current working directory after Git has completed its setup phase. diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt index cf95d6d02b..6e461ace6e 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -335,6 +335,68 @@ The default 'update' hook, when enabled--and with `hooks.allowunannotated` config option unset or set to false--prevents unannotated tags to be pushed. +[[proc-receive]] +proc-receive +~~~~~~~~~~~~ + +This hook is invoked by linkgit:git-receive-pack[1]. If the server has +set the multi-valued config variable `receive.procReceiveRefs`, and the +commands sent to 'receive-pack' have matching reference names, these +commands will be executed by this hook, instead of by the internal +`execute_commands()` function. This hook is responsible for updating +the relevant references and reporting the results back to 'receive-pack'. + +This hook executes once for the receive operation. It takes no +arguments, but uses a pkt-line format protocol to communicate with +'receive-pack' to read commands, push-options and send results. In the +following example for the protocol, the letter 'S' stands for +'receive-pack' and the letter 'H' stands for this hook. + + # Version and features negotiation. + S: PKT-LINE(version=1\0push-options atomic...) + S: flush-pkt + H: PKT-LINE(version=1\0push-options...) + H: flush-pkt + + # Send commands from server to the hook. + S: PKT-LINE(<old-oid> <new-oid> <ref>) + S: ... ... + S: flush-pkt + # Send push-options only if the 'push-options' feature is enabled. + S: PKT-LINE(push-option) + S: ... ... + S: flush-pkt + + # Receive result from the hook. + # OK, run this command successfully. + H: PKT-LINE(ok <ref>) + # NO, I reject it. + H: PKT-LINE(ng <ref> <reason>) + # Fall through, let 'receive-pack' to execute it. + H: PKT-LINE(ok <ref>) + H: PKT-LINE(option fall-through) + # OK, but has an alternate reference. The alternate reference name + # and other status can be given in option directives. + H: PKT-LINE(ok <ref>) + H: PKT-LINE(option refname <refname>) + H: PKT-LINE(option old-oid <old-oid>) + H: PKT-LINE(option new-oid <new-oid>) + H: PKT-LINE(option forced-update) + H: ... ... + H: flush-pkt + +Each command for the 'proc-receive' hook may point to a pseudo-reference +and always has a zero-old as its old-oid, while the 'proc-receive' hook +may update an alternate reference and the alternate reference may exist +already with a non-zero old-oid. For this case, this hook will use +"option" directives to report extended attributes for the reference given +by the leading "ok" directive. + +The report of the commands of this hook should have the same order as +the input. The exit status of the 'proc-receive' hook only determines +the success or failure of the group of commands sent to it, unless +atomic push is in use. + [[post-receive]] post-receive ~~~~~~~~~~~~ diff --git a/Documentation/ref-reachability-filters.txt b/Documentation/ref-reachability-filters.txt new file mode 100644 index 0000000000..9bae46d84c --- /dev/null +++ b/Documentation/ref-reachability-filters.txt @@ -0,0 +1,7 @@ +When combining multiple `--contains` and `--no-contains` filters, only +references that contain at least one of the `--contains` commits and +contain none of the `--no-contains` commits are shown. + +When combining multiple `--merged` and `--no-merged` filters, only +references that are reachable from at least one of the `--merged` +commits and from none of the `--no-merged` commits are shown. diff --git a/Documentation/technical/commit-graph-format.txt b/Documentation/technical/commit-graph-format.txt index 6ddbceba15..1f86031333 100644 --- a/Documentation/technical/commit-graph-format.txt +++ b/Documentation/technical/commit-graph-format.txt @@ -82,7 +82,7 @@ CHUNK DATA: Commit Data (ID: {'C', 'D', 'A', 'T' }) (N * (H + 16) bytes) * The first H bytes are for the OID of the root tree. * The next 8 bytes are for the positions of the first two parents - of the ith commit. Stores value 0x7000000 if no parent in that + of the ith commit. Stores value 0x70000000 if no parent in that position. If there are more than two parents, the second value has its most-significant bit on and the other bits store an array position into the Extra Edge List chunk. diff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt index a4573d12ce..e13a2c064d 100644 --- a/Documentation/technical/pack-protocol.txt +++ b/Documentation/technical/pack-protocol.txt @@ -503,8 +503,8 @@ The reference discovery phase is done nearly the same way as it is in the fetching protocol. Each reference obj-id and name on the server is sent in packet-line format to the client, followed by a flush-pkt. The only real difference is that the capability listing is different - the only -possible values are 'report-status', 'delete-refs', 'ofs-delta' and -'push-options'. +possible values are 'report-status', 'report-status-v2', 'delete-refs', +'ofs-delta', 'atomic' and 'push-options'. Reference Update Request and Packfile Transfer ---------------------------------------------- @@ -625,7 +625,7 @@ Report Status ------------- After receiving the pack data from the sender, the receiver sends a -report if 'report-status' capability is in effect. +report if 'report-status' or 'report-status-v2' capability is in effect. It is a short listing of what happened in that update. It will first list the status of the packfile unpacking as either 'unpack ok' or 'unpack [error]'. Then it will list the status for each of the references @@ -647,6 +647,41 @@ update was successful, or 'ng [refname] [error]' if the update was not. error-msg = 1*(OCTET) ; where not "ok" ---- +The 'report-status-v2' capability extends the protocol by adding new option +lines in order to support reporting of reference rewritten by the +'proc-receive' hook. The 'proc-receive' hook may handle a command for a +pseudo-reference which may create or update one or more references, and each +reference may have different name, different new-oid, and different old-oid. + +---- + report-status-v2 = unpack-status + 1*(command-status-v2) + flush-pkt + + unpack-status = PKT-LINE("unpack" SP unpack-result) + unpack-result = "ok" / error-msg + + command-status-v2 = command-ok-v2 / command-fail + command-ok-v2 = command-ok + *option-line + + command-ok = PKT-LINE("ok" SP refname) + command-fail = PKT-LINE("ng" SP refname SP error-msg) + + error-msg = 1*(OCTET) ; where not "ok" + + option-line = *1(option-refname) + *1(option-old-oid) + *1(option-new-oid) + *1(option-forced-update) + + option-refname = PKT-LINE("option" SP "refname" SP refname) + option-old-oid = PKT-LINE("option" SP "old-oid" SP obj-id) + option-new-oid = PKT-LINE("option" SP "new-oid" SP obj-id) + option-force = PKT-LINE("option" SP "forced-update") + +---- + Updates can be unsuccessful for a number of reasons. The reference can have changed since the reference discovery phase was originally sent, meaning someone pushed in the meantime. The reference being pushed could be a diff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt index 124d716807..ba869a7d36 100644 --- a/Documentation/technical/protocol-capabilities.txt +++ b/Documentation/technical/protocol-capabilities.txt @@ -22,9 +22,9 @@ was sent. Server MUST NOT ignore capabilities that client requested and server advertised. As a consequence of these rules, server MUST NOT advertise capabilities it does not understand. -The 'atomic', 'report-status', 'delete-refs', 'quiet', and 'push-cert' -capabilities are sent and recognized by the receive-pack (push to server) -process. +The 'atomic', 'report-status', 'report-status-v2', 'delete-refs', 'quiet', +and 'push-cert' capabilities are sent and recognized by the receive-pack +(push to server) process. The 'ofs-delta' and 'side-band-64k' capabilities are sent and recognized by both upload-pack and receive-pack protocols. The 'agent' capability @@ -284,6 +284,17 @@ each reference was updated successfully. If any of those were not successful, it will send back an error message. See pack-protocol.txt for example messages. +report-status-v2 +---------------- + +Capability 'report-status-v2' extends capability 'report-status' by +adding new "option" directives in order to support reference rewritten by +the "proc-receive" hook. The "proc-receive" hook may handle a command +for a pseudo-reference which may create or update a reference with +different name, new-oid, and old-oid. While the capability +'report-status' cannot report for such case. See pack-protocol.txt +for details. + delete-refs ----------- @@ -462,6 +462,12 @@ all:: # the global variable _wpgmptr containing the absolute path of the current # executable (this is the case on Windows). # +# Define GENERATE_COMPILATION_DATABASE to "yes" to generate JSON compilation +# database entries during compilation if your compiler supports it, using the +# `-MJ` flag. The JSON entries will be placed in the `compile_commands/` +# directory, and the JSON compilation database 'compile_commands.json' will be +# created at the root of the repository. +# # Define DEVELOPER to enable more compiler warnings. Compiler version # and family are auto detected, but could be overridden by defining # COMPILER_FEATURES (see config.mak.dev). You can still set @@ -714,6 +720,7 @@ TEST_BUILTINS_OBJS += test-parse-pathspec-file.o TEST_BUILTINS_OBJS += test-path-utils.o TEST_BUILTINS_OBJS += test-pkt-line.o TEST_BUILTINS_OBJS += test-prio-queue.o +TEST_BUILTINS_OBJS += test-proc-receive.o TEST_BUILTINS_OBJS += test-progress.o TEST_BUILTINS_OBJS += test-reach.o TEST_BUILTINS_OBJS += test-read-cache.o @@ -883,7 +890,6 @@ LIB_OBJS += hashmap.o LIB_OBJS += help.o LIB_OBJS += hex.o LIB_OBJS += ident.o -LIB_OBJS += interdiff.o LIB_OBJS += json-writer.o LIB_OBJS += kwset.o LIB_OBJS += levenshtein.o @@ -950,6 +956,7 @@ LIB_OBJS += rebase.o LIB_OBJS += ref-filter.o LIB_OBJS += reflog-walk.o LIB_OBJS += refs.o +LIB_OBJS += refs/debug.o LIB_OBJS += refs/files-backend.o LIB_OBJS += refs/iterator.o LIB_OBJS += refs/packed-backend.o @@ -1256,6 +1263,27 @@ $(error please set COMPUTE_HEADER_DEPENDENCIES to yes, no, or auto \ endif endif +ifndef GENERATE_COMPILATION_DATABASE +GENERATE_COMPILATION_DATABASE = no +endif + +ifeq ($(GENERATE_COMPILATION_DATABASE),yes) +compdb_check = $(shell $(CC) $(ALL_CFLAGS) \ + -c -MJ /dev/null \ + -x c /dev/null -o /dev/null 2>&1; \ + echo $$?) +ifneq ($(compdb_check),0) +override GENERATE_COMPILATION_DATABASE = no +$(warning GENERATE_COMPILATION_DATABASE is set to "yes", but your compiler does not \ +support generating compilation database entries) +endif +else +ifneq ($(GENERATE_COMPILATION_DATABASE),no) +$(error please set GENERATE_COMPILATION_DATABASE to "yes" or "no" \ +(not "$(GENERATE_COMPILATION_DATABASE)")) +endif +endif + ifdef SANE_TOOL_PATH SANE_TOOL_PATH_SQ = $(subst ','\'',$(SANE_TOOL_PATH)) BROKEN_PATH_FIX = 's|^\# @@BROKEN_PATH_FIX@@$$|git_broken_path_fix "$(SANE_TOOL_PATH_SQ)"|' @@ -2369,16 +2397,30 @@ missing_dep_dirs = dep_args = endif +compdb_dir = compile_commands + +ifeq ($(GENERATE_COMPILATION_DATABASE),yes) +missing_compdb_dir = $(compdb_dir) +$(missing_compdb_dir): + @mkdir -p $@ + +compdb_file = $(compdb_dir)/$(subst /,-,$@.json) +compdb_args = -MJ $(compdb_file) +else +missing_compdb_dir = +compdb_args = +endif + ASM_SRC := $(wildcard $(OBJECTS:o=S)) ASM_OBJ := $(ASM_SRC:S=o) C_OBJ := $(filter-out $(ASM_OBJ),$(OBJECTS)) .SUFFIXES: -$(C_OBJ): %.o: %.c GIT-CFLAGS $(missing_dep_dirs) - $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $< -$(ASM_OBJ): %.o: %.S GIT-CFLAGS $(missing_dep_dirs) - $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $< +$(C_OBJ): %.o: %.c GIT-CFLAGS $(missing_dep_dirs) $(missing_compdb_dir) + $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(compdb_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $< +$(ASM_OBJ): %.o: %.S GIT-CFLAGS $(missing_dep_dirs) $(missing_compdb_dir) + $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(compdb_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $< %.s: %.c GIT-CFLAGS FORCE $(QUIET_CC)$(CC) -o $@ -S $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $< @@ -2401,6 +2443,14 @@ else $(OBJECTS): $(LIB_H) $(GENERATED_H) endif +ifeq ($(GENERATE_COMPILATION_DATABASE),yes) +all:: compile_commands.json +compile_commands.json: + @$(RM) $@ + $(QUIET_GEN)sed -e '1s/^/[/' -e '$$s/,$$/]/' $(compdb_dir)/*.o.json > $@+ + @if test -s $@+; then mv $@+ $@; else $(RM) $@+; fi +endif + exec-cmd.sp exec-cmd.s exec-cmd.o: GIT-PREFIX exec-cmd.sp exec-cmd.s exec-cmd.o: EXTRA_CPPFLAGS = \ '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' \ @@ -2992,6 +3042,9 @@ quick-install-html: ### Maintainer's dist rules +# Allow tweaking to hide local environment effects, like perm bits. +# With GNU tar, "--mode=u+rwX,og+rX,og-w" would be a good idea, for example. +TAR_DIST_EXTRA_OPTS = GIT_TARNAME = git-$(GIT_VERSION) dist: git-archive$(X) configure ./git-archive --format=tar \ @@ -3000,7 +3053,7 @@ dist: git-archive$(X) configure @cp configure $(GIT_TARNAME) @echo $(GIT_VERSION) > $(GIT_TARNAME)/version @$(MAKE) -C git-gui TARDIR=../$(GIT_TARNAME)/git-gui dist-version - $(TAR) rf $(GIT_TARNAME).tar \ + $(TAR) rf $(GIT_TARNAME).tar $(TAR_DIST_EXTRA_OPTS) \ $(GIT_TARNAME)/configure \ $(GIT_TARNAME)/version \ $(GIT_TARNAME)/git-gui/version @@ -3014,7 +3067,7 @@ ifdef DC_SHA1_SUBMODULE $(GIT_TARNAME)/sha1collisiondetection/lib/ @cp sha1collisiondetection/lib/ubc_check.[ch] \ $(GIT_TARNAME)/sha1collisiondetection/lib/ - $(TAR) rf $(GIT_TARNAME).tar \ + $(TAR) rf $(GIT_TARNAME).tar $(TAR_DIST_EXTRA_OPTS) \ $(GIT_TARNAME)/sha1collisiondetection/LICENSE.txt \ $(GIT_TARNAME)/sha1collisiondetection/lib/sha1.[ch] \ $(GIT_TARNAME)/sha1collisiondetection/lib/ubc_check.[ch] @@ -3048,7 +3101,7 @@ dist-doc: $(RM) -r .doc-tmp-dir mkdir .doc-tmp-dir $(MAKE) -C Documentation WEBDOC_DEST=../.doc-tmp-dir install-webdoc - cd .doc-tmp-dir && $(TAR) cf ../$(htmldocs).tar . + cd .doc-tmp-dir && $(TAR) cf ../$(htmldocs).tar $(TAR_DIST_EXTRA_OPTS) . gzip -n -9 -f $(htmldocs).tar : $(RM) -r .doc-tmp-dir @@ -3058,7 +3111,7 @@ dist-doc: man5dir=../.doc-tmp-dir/man5 \ man7dir=../.doc-tmp-dir/man7 \ install - cd .doc-tmp-dir && $(TAR) cf ../$(manpages).tar . + cd .doc-tmp-dir && $(TAR) cf ../$(manpages).tar $(TAR_DIST_EXTRA_OPTS) . gzip -n -9 -f $(manpages).tar $(RM) -r .doc-tmp-dir @@ -3085,7 +3138,7 @@ clean: profile-clean coverage-clean cocciclean $(RM) $(TEST_PROGRAMS) $(RM) $(FUZZ_PROGRAMS) $(RM) $(HCC) - $(RM) -r bin-wrappers $(dep_dirs) + $(RM) -r bin-wrappers $(dep_dirs) $(compdb_dir) compile_commands.json $(RM) -r po/build/ $(RM) *.pyc *.pyo */*.pyc */*.pyo $(GENERATED_H) $(ETAGS_TARGET) tags cscope* $(RM) -r $(GIT_TARNAME) .doc-tmp-dir diff --git a/add-patch.c b/add-patch.c index 457b8c550e..bd94bd3a7c 100644 --- a/add-patch.c +++ b/add-patch.c @@ -266,6 +266,20 @@ struct add_p_state { const char *revision; }; +static void add_p_state_clear(struct add_p_state *s) +{ + size_t i; + + strbuf_release(&s->answer); + strbuf_release(&s->buf); + strbuf_release(&s->plain); + strbuf_release(&s->colored); + for (i = 0; i < s->file_diff_nr; i++) + free(s->file_diff[i].hunk); + free(s->file_diff); + clear_add_i_state(&s->s); +} + static void err(struct add_p_state *s, const char *fmt, ...) { va_list args; @@ -451,7 +465,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) pend = p + plain->len; while (p != pend) { char *eol = memchr(p, '\n', pend - p); - const char *deleted = NULL, *added = NULL, *mode_change = NULL; + const char *deleted = NULL, *mode_change = NULL; if (!eol) eol = pend; @@ -468,12 +482,11 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) } else if (p == plain->buf) BUG("diff starts with unexpected line:\n" "%.*s\n", (int)(eol - p), p); - else if (file_diff->deleted || file_diff->added) + else if (file_diff->deleted) ; /* keep the rest of the file in a single "hunk" */ else if (starts_with(p, "@@ ") || (hunk == &file_diff->head && - (skip_prefix(p, "deleted file", &deleted) || - skip_prefix(p, "new file", &added)))) { + (skip_prefix(p, "deleted file", &deleted)))) { if (marker == '-' || marker == '+') /* * Should not happen; previous hunk did not end @@ -491,8 +504,6 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) if (deleted) file_diff->deleted = 1; - else if (added) - file_diff->added = 1; else if (parse_hunk_header(s, hunk) < 0) return -1; @@ -502,6 +513,9 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) */ marker = *p; } else if (hunk == &file_diff->head && + starts_with(p, "new file")) { + file_diff->added = 1; + } else if (hunk == &file_diff->head && skip_prefix(p, "old mode ", &mode_change) && is_octal(mode_change, eol - mode_change)) { if (file_diff->mode_change) @@ -1362,7 +1376,8 @@ static int patch_update_file(struct add_p_state *s, ALLOW_EDIT = 1 << 6 } permitted = 0; - if (!file_diff->hunk_nr) + /* Empty added files have no hunks */ + if (!file_diff->hunk_nr && !file_diff->added) return 0; strbuf_reset(&s->buf); @@ -1371,21 +1386,25 @@ static int patch_update_file(struct add_p_state *s, for (;;) { if (hunk_index >= file_diff->hunk_nr) hunk_index = 0; - hunk = file_diff->hunk + hunk_index; - + hunk = file_diff->hunk_nr + ? file_diff->hunk + hunk_index + : &file_diff->head; undecided_previous = -1; - for (i = hunk_index - 1; i >= 0; i--) - if (file_diff->hunk[i].use == UNDECIDED_HUNK) { - undecided_previous = i; - break; - } - undecided_next = -1; - for (i = hunk_index + 1; i < file_diff->hunk_nr; i++) - if (file_diff->hunk[i].use == UNDECIDED_HUNK) { - undecided_next = i; - break; - } + + if (file_diff->hunk_nr) { + for (i = hunk_index - 1; i >= 0; i--) + if (file_diff->hunk[i].use == UNDECIDED_HUNK) { + undecided_previous = i; + break; + } + + for (i = hunk_index + 1; i < file_diff->hunk_nr; i++) + if (file_diff->hunk[i].use == UNDECIDED_HUNK) { + undecided_next = i; + break; + } + } /* Everything decided? */ if (undecided_previous < 0 && undecided_next < 0 && @@ -1393,38 +1412,40 @@ static int patch_update_file(struct add_p_state *s, break; strbuf_reset(&s->buf); - render_hunk(s, hunk, 0, colored, &s->buf); - fputs(s->buf.buf, stdout); + if (file_diff->hunk_nr) { + render_hunk(s, hunk, 0, colored, &s->buf); + fputs(s->buf.buf, stdout); - strbuf_reset(&s->buf); - if (undecided_previous >= 0) { - permitted |= ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK; - strbuf_addstr(&s->buf, ",k"); - } - if (hunk_index) { - permitted |= ALLOW_GOTO_PREVIOUS_HUNK; - strbuf_addstr(&s->buf, ",K"); - } - if (undecided_next >= 0) { - permitted |= ALLOW_GOTO_NEXT_UNDECIDED_HUNK; - strbuf_addstr(&s->buf, ",j"); - } - if (hunk_index + 1 < file_diff->hunk_nr) { - permitted |= ALLOW_GOTO_NEXT_HUNK; - strbuf_addstr(&s->buf, ",J"); - } - if (file_diff->hunk_nr > 1) { - permitted |= ALLOW_SEARCH_AND_GOTO; - strbuf_addstr(&s->buf, ",g,/"); - } - if (hunk->splittable_into > 1) { - permitted |= ALLOW_SPLIT; - strbuf_addstr(&s->buf, ",s"); - } - if (hunk_index + 1 > file_diff->mode_change && - !file_diff->deleted) { - permitted |= ALLOW_EDIT; - strbuf_addstr(&s->buf, ",e"); + strbuf_reset(&s->buf); + if (undecided_previous >= 0) { + permitted |= ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK; + strbuf_addstr(&s->buf, ",k"); + } + if (hunk_index) { + permitted |= ALLOW_GOTO_PREVIOUS_HUNK; + strbuf_addstr(&s->buf, ",K"); + } + if (undecided_next >= 0) { + permitted |= ALLOW_GOTO_NEXT_UNDECIDED_HUNK; + strbuf_addstr(&s->buf, ",j"); + } + if (hunk_index + 1 < file_diff->hunk_nr) { + permitted |= ALLOW_GOTO_NEXT_HUNK; + strbuf_addstr(&s->buf, ",J"); + } + if (file_diff->hunk_nr > 1) { + permitted |= ALLOW_SEARCH_AND_GOTO; + strbuf_addstr(&s->buf, ",g,/"); + } + if (hunk->splittable_into > 1) { + permitted |= ALLOW_SPLIT; + strbuf_addstr(&s->buf, ",s"); + } + if (hunk_index + 1 > file_diff->mode_change && + !file_diff->deleted) { + permitted |= ALLOW_EDIT; + strbuf_addstr(&s->buf, ",e"); + } } if (file_diff->deleted) prompt_mode_type = PROMPT_DELETION; @@ -1438,7 +1459,9 @@ static int patch_update_file(struct add_p_state *s, color_fprintf(stdout, s->s.prompt_color, "(%"PRIuMAX"/%"PRIuMAX") ", (uintmax_t)hunk_index + 1, - (uintmax_t)file_diff->hunk_nr); + (uintmax_t)(file_diff->hunk_nr + ? file_diff->hunk_nr + : 1)); color_fprintf(stdout, s->s.prompt_color, _(s->mode->prompt_mode[prompt_mode_type]), s->buf.buf); @@ -1458,16 +1481,24 @@ soft_increment: hunk->use = SKIP_HUNK; goto soft_increment; } else if (ch == 'a') { - for (; hunk_index < file_diff->hunk_nr; hunk_index++) { - hunk = file_diff->hunk + hunk_index; - if (hunk->use == UNDECIDED_HUNK) - hunk->use = USE_HUNK; + if (file_diff->hunk_nr) { + for (; hunk_index < file_diff->hunk_nr; hunk_index++) { + hunk = file_diff->hunk + hunk_index; + if (hunk->use == UNDECIDED_HUNK) + hunk->use = USE_HUNK; + } + } else if (hunk->use == UNDECIDED_HUNK) { + hunk->use = USE_HUNK; } } else if (ch == 'd' || ch == 'q') { - for (; hunk_index < file_diff->hunk_nr; hunk_index++) { - hunk = file_diff->hunk + hunk_index; - if (hunk->use == UNDECIDED_HUNK) - hunk->use = SKIP_HUNK; + if (file_diff->hunk_nr) { + for (; hunk_index < file_diff->hunk_nr; hunk_index++) { + hunk = file_diff->hunk + hunk_index; + if (hunk->use == UNDECIDED_HUNK) + hunk->use = SKIP_HUNK; + } + } else if (hunk->use == UNDECIDED_HUNK) { + hunk->use = SKIP_HUNK; } if (ch == 'q') { quit = 1; @@ -1625,7 +1656,8 @@ soft_increment: if (file_diff->hunk[i].use == USE_HUNK) break; - if (i < file_diff->hunk_nr) { + if (i < file_diff->hunk_nr || + (!file_diff->hunk_nr && file_diff->head.use == USE_HUNK)) { /* At least one hunk selected: apply */ strbuf_reset(&s->buf); reassemble_patch(s, file_diff, 0, &s->buf); @@ -1641,7 +1673,7 @@ soft_increment: NULL, 0, NULL, 0)) error(_("'git apply' failed")); } - if (!repo_read_index(s->s.r)) + if (repo_read_index(s->s.r) >= 0) repo_refresh_and_write_index(s->s.r, REFRESH_QUIET, 0, 1, NULL, NULL, NULL); } @@ -1690,9 +1722,7 @@ int run_add_p(struct repository *r, enum add_p_mode mode, repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1, NULL, NULL, NULL) < 0) || parse_diff(&s, ps) < 0) { - strbuf_release(&s.plain); - strbuf_release(&s.colored); - clear_add_i_state(&s.s); + add_p_state_clear(&s); return -1; } @@ -1707,10 +1737,6 @@ int run_add_p(struct repository *r, enum add_p_mode mode, else if (binary_count == s.file_diff_nr) fprintf(stderr, _("Only binary files changed.\n")); - strbuf_release(&s.answer); - strbuf_release(&s.buf); - strbuf_release(&s.plain); - strbuf_release(&s.colored); - clear_add_i_state(&s.s); + add_p_state_clear(&s); return 0; } @@ -1353,8 +1353,8 @@ static struct blame_origin *find_origin(struct repository *r, else { int compute_diff = 1; if (origin->commit->parents && - !oidcmp(&parent->object.oid, - &origin->commit->parents->item->object.oid)) + oideq(&parent->object.oid, + &origin->commit->parents->item->object.oid)) compute_diff = maybe_changed_path(r, origin, bd); if (compute_diff) @@ -172,6 +172,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix); int cmd_ls_remote(int argc, const char **argv, const char *prefix); int cmd_mailinfo(int argc, const char **argv, const char *prefix); int cmd_mailsplit(int argc, const char **argv, const char *prefix); +int cmd_maintenance(int argc, const char **argv, const char *prefix); int cmd_merge(int argc, const char **argv, const char *prefix); int cmd_merge_base(int argc, const char **argv, const char *prefix); int cmd_merge_index(int argc, const char **argv, const char *prefix); diff --git a/builtin/add.c b/builtin/add.c index b36a99eb7c..26b6ced09e 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -192,9 +192,15 @@ int run_add_interactive(const char *revision, const char *patch_mode, int use_builtin_add_i = git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1); - if (use_builtin_add_i < 0) - git_config_get_bool("add.interactive.usebuiltin", - &use_builtin_add_i); + if (use_builtin_add_i < 0) { + int experimental; + if (!git_config_get_bool("add.interactive.usebuiltin", + &use_builtin_add_i)) + ; /* ok */ + else if (!git_config_get_bool("feature.experimental", &experimental) && + experimental) + use_builtin_add_i = 1; + } if (use_builtin_add_i == 1) { enum add_p_mode mode; diff --git a/builtin/am.c b/builtin/am.c index b5c63ddf1d..7259186408 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -1813,7 +1813,7 @@ next: if (!state->rebasing) { am_destroy(state); close_object_store(the_repository->objects); - run_auto_gc(state->quiet); + run_auto_maintenance(state->quiet); } } diff --git a/builtin/branch.c b/builtin/branch.c index e82301fb1b..efb30b8820 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -26,7 +26,7 @@ #include "commit-reach.h" static const char * const builtin_branch_usage[] = { - N_("git branch [<options>] [-r | -a] [--merged | --no-merged]"), + N_("git branch [<options>] [-r | -a] [--merged] [--no-merged]"), N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"), N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."), N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"), @@ -688,8 +688,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix) !show_current && !unset_upstream && argc == 0) list = 1; - if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr || - filter.no_commit) + if (filter.with_commit || filter.no_commit || + filter.reachable_from || filter.unreachable_from || filter.points_at.nr) list = 1; if (!!delete + !!rename + !!copy + !!new_upstream + !!show_current + diff --git a/builtin/clean.c b/builtin/clean.c index e53ea52d89..687ab473c2 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -162,7 +162,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) && is_nonbare_repository_dir(path)) { if (!quiet) { - quote_path_relative(path->buf, prefix, "ed); + quote_path(path->buf, prefix, "ed, 0); printf(dry_run ? _(msg_would_skip_git_dir) : _(msg_skip_git_dir), quoted.buf); } @@ -177,7 +177,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, res = dry_run ? 0 : rmdir(path->buf); if (res) { int saved_errno = errno; - quote_path_relative(path->buf, prefix, "ed); + quote_path(path->buf, prefix, "ed, 0); errno = saved_errno; warning_errno(_(msg_warn_remove_failed), quoted.buf); *dir_gone = 0; @@ -202,7 +202,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, if (remove_dirs(path, prefix, force_flag, dry_run, quiet, &gone)) ret = 1; if (gone) { - quote_path_relative(path->buf, prefix, "ed); + quote_path(path->buf, prefix, "ed, 0); string_list_append(&dels, quoted.buf); } else *dir_gone = 0; @@ -210,11 +210,11 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, } else { res = dry_run ? 0 : unlink(path->buf); if (!res) { - quote_path_relative(path->buf, prefix, "ed); + quote_path(path->buf, prefix, "ed, 0); string_list_append(&dels, quoted.buf); } else { int saved_errno = errno; - quote_path_relative(path->buf, prefix, "ed); + quote_path(path->buf, prefix, "ed, 0); errno = saved_errno; warning_errno(_(msg_warn_remove_failed), quoted.buf); *dir_gone = 0; @@ -238,7 +238,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, *dir_gone = 1; else { int saved_errno = errno; - quote_path_relative(path->buf, prefix, "ed); + quote_path(path->buf, prefix, "ed, 0); errno = saved_errno; warning_errno(_(msg_warn_remove_failed), quoted.buf); *dir_gone = 0; @@ -266,7 +266,7 @@ static void pretty_print_dels(void) struct column_options copts; for_each_string_list_item(item, &del_list) { - qname = quote_path_relative(item->string, NULL, &buf); + qname = quote_path(item->string, NULL, &buf, 0); string_list_append(&list, qname); } @@ -753,7 +753,7 @@ static int ask_each_cmd(void) for_each_string_list_item(item, &del_list) { /* Ctrl-D should stop removing files */ if (!eof) { - qname = quote_path_relative(item->string, NULL, &buf); + qname = quote_path(item->string, NULL, &buf, 0); /* TRANSLATORS: Make sure to keep [y/N] as is */ printf(_("Remove %s [y/N]? "), qname); if (git_read_line_interactively(&confirm) == EOF) { @@ -1047,19 +1047,19 @@ int cmd_clean(int argc, const char **argv, const char *prefix) if (remove_dirs(&abs_path, prefix, rm_flags, dry_run, quiet, &gone)) errors++; if (gone && !quiet) { - qname = quote_path_relative(item->string, NULL, &buf); + qname = quote_path(item->string, NULL, &buf, 0); printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname); } } else { res = dry_run ? 0 : unlink(abs_path.buf); if (res) { int saved_errno = errno; - qname = quote_path_relative(item->string, NULL, &buf); + qname = quote_path(item->string, NULL, &buf, 0); errno = saved_errno; warning_errno(_(msg_warn_remove_failed), qname); errors++; } else if (!quiet) { - qname = quote_path_relative(item->string, NULL, &buf); + qname = quote_path(item->string, NULL, &buf, 0); printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname); } } diff --git a/builtin/clone.c b/builtin/clone.c index b087ee40c2..fbfd6568cd 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -953,7 +953,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) struct ref *mapped_refs; const struct ref *ref; struct strbuf key = STRBUF_INIT; - struct strbuf default_refspec = STRBUF_INIT; struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT; struct transport *transport = NULL; const char *src_ref_prefix = "refs/heads/"; @@ -1157,9 +1156,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) remote = remote_get(option_origin); - strbuf_addf(&default_refspec, "+%s*:%s*", src_ref_prefix, - branch_top.buf); - refspec_append(&remote->fetch, default_refspec.buf); + refspec_appendf(&remote->fetch, "+%s*:%s*", src_ref_prefix, + branch_top.buf); transport = transport_get(remote, remote->url[0]); transport_set_verbosity(transport, option_verbosity, option_progress); @@ -1332,7 +1330,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) strbuf_release(&reflog_msg); strbuf_release(&branch_top); strbuf_release(&key); - strbuf_release(&default_refspec); junk_mode = JUNK_LEAVE_ALL; strvec_clear(&ref_prefixes); diff --git a/builtin/commit.c b/builtin/commit.c index 5d91b13a5c..42b964e0ca 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1700,7 +1700,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) git_test_write_commit_graph_or_die(); repo_rerere(the_repository, 0); - run_auto_gc(quiet); + run_auto_maintenance(quiet); run_commit_hook(use_editor, get_index_file(), "post-commit", NULL); if (amend && !no_post_rewrite) { commit_post_rewrite(the_repository, current_head, &oid); diff --git a/builtin/config.c b/builtin/config.c index 5e39f61885..963d65fd3f 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -65,6 +65,7 @@ static int show_scope; #define TYPE_PATH 4 #define TYPE_EXPIRY_DATE 5 #define TYPE_COLOR 6 +#define TYPE_BOOL_OR_STR 7 #define OPT_CALLBACK_VALUE(s, l, v, h, i) \ { OPTION_CALLBACK, (s), (l), (v), NULL, (h), PARSE_OPT_NOARG | \ @@ -94,6 +95,8 @@ static int option_parse_type(const struct option *opt, const char *arg, new_type = TYPE_INT; else if (!strcmp(arg, "bool-or-int")) new_type = TYPE_BOOL_OR_INT; + else if (!strcmp(arg, "bool-or-str")) + new_type = TYPE_BOOL_OR_STR; else if (!strcmp(arg, "path")) new_type = TYPE_PATH; else if (!strcmp(arg, "expiry-date")) @@ -149,6 +152,7 @@ static struct option builtin_config_options[] = { OPT_CALLBACK_VALUE(0, "bool", &type, N_("value is \"true\" or \"false\""), TYPE_BOOL), OPT_CALLBACK_VALUE(0, "int", &type, N_("value is decimal number"), TYPE_INT), OPT_CALLBACK_VALUE(0, "bool-or-int", &type, N_("value is --bool or --int"), TYPE_BOOL_OR_INT), + OPT_CALLBACK_VALUE(0, "bool-or-str", &type, N_("value is --bool or string"), TYPE_BOOL_OR_STR), OPT_CALLBACK_VALUE(0, "path", &type, N_("value is a path (file or directory name)"), TYPE_PATH), OPT_CALLBACK_VALUE(0, "expiry-date", &type, N_("value is an expiry date"), TYPE_EXPIRY_DATE), OPT_GROUP(N_("Other")), @@ -250,6 +254,12 @@ static int format_config(struct strbuf *buf, const char *key_, const char *value strbuf_addstr(buf, v ? "true" : "false"); else strbuf_addf(buf, "%d", v); + } else if (type == TYPE_BOOL_OR_STR) { + int v = git_parse_maybe_bool(value_); + if (v < 0) + strbuf_addstr(buf, value_); + else + strbuf_addstr(buf, v ? "true" : "false"); } else if (type == TYPE_PATH) { const char *v; if (git_config_pathname(&v, key_, value_) < 0) @@ -411,6 +421,13 @@ static char *normalize_value(const char *key, const char *value) else return xstrdup(v ? "true" : "false"); } + if (type == TYPE_BOOL_OR_STR) { + int v = git_parse_maybe_bool(value); + if (v < 0) + return xstrdup(value); + else + return xstrdup(v ? "true" : "false"); + } if (type == TYPE_COLOR) { char v[COLOR_MAXLEN]; if (git_config_color(v, key, value)) @@ -628,11 +645,15 @@ int cmd_config(int argc, const char **argv, const char *prefix) usage_builtin_config(); } - if (use_local_config && nongit) - die(_("--local can only be used inside a git repository")); + if (nongit) { + if (use_local_config) + die(_("--local can only be used inside a git repository")); + if (given_config_source.blob) + die(_("--blob can only be used inside a git repository")); + if (use_worktree_config) + die(_("--worktree can only be used inside a git repository")); - if (given_config_source.blob && nongit) - die(_("--blob can only be used inside a git repository")); + } if (given_config_source.file && !strcmp(given_config_source.file, "-")) { diff --git a/builtin/fast-import.c b/builtin/fast-import.c index 1c85eafe43..1bf50a73dc 100644 --- a/builtin/fast-import.c +++ b/builtin/fast-import.c @@ -739,7 +739,6 @@ static void start_packfile(void) { struct strbuf tmp_file = STRBUF_INIT; struct packed_git *p; - struct pack_header hdr; int pack_fd; pack_fd = odb_mkstemp(&tmp_file, "pack/tmp_pack_XXXXXX"); @@ -750,13 +749,8 @@ static void start_packfile(void) p->do_not_close = 1; pack_file = hashfd(pack_fd, p->pack_name); - hdr.hdr_signature = htonl(PACK_SIGNATURE); - hdr.hdr_version = htonl(2); - hdr.hdr_entries = 0; - hashwrite(pack_file, &hdr, sizeof(hdr)); - pack_data = p; - pack_size = sizeof(hdr); + pack_size = write_pack_header(pack_file, 0); object_count = 0; REALLOC_ARRAY(all_packs, pack_id + 1); diff --git a/builtin/fetch.c b/builtin/fetch.c index a6d3268661..d5bf526277 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -200,8 +200,10 @@ static struct option builtin_fetch_options[] = { OPT_STRING_LIST(0, "negotiation-tip", &negotiation_tip, N_("revision"), N_("report that we have only objects reachable from this object")), OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), + OPT_BOOL(0, "auto-maintenance", &enable_auto_gc, + N_("run 'maintenance --auto' after fetching")), OPT_BOOL(0, "auto-gc", &enable_auto_gc, - N_("run 'gc --auto' after fetching")), + N_("run 'maintenance --auto' after fetching")), OPT_BOOL(0, "show-forced-updates", &fetch_show_forced_updates, N_("check for forced-updates on all updated branches")), OPT_BOOL(0, "write-commit-graph", &fetch_write_commit_graph, @@ -966,8 +968,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, ref->force = rm->peer_ref->force; } - if (recurse_submodules != RECURSE_SUBMODULES_OFF) + if (recurse_submodules != RECURSE_SUBMODULES_OFF && + (!rm->peer_ref || !oideq(&ref->old_oid, &ref->new_oid))) { check_for_new_submodule_commits(&rm->old_oid); + } if (!strcmp(rm->name, "HEAD")) { kind = ""; @@ -1550,7 +1554,10 @@ static void add_options_to_argv(struct strvec *argv) strvec_push(argv, "-v"); else if (verbosity < 0) strvec_push(argv, "-q"); - + if (family == TRANSPORT_FAMILY_IPV4) + strvec_push(argv, "--ipv4"); + else if (family == TRANSPORT_FAMILY_IPV6) + strvec_push(argv, "--ipv6"); } /* Fetch multiple remotes in parallel */ @@ -1738,15 +1745,12 @@ static int fetch_one(struct remote *remote, int argc, const char **argv, for (i = 0; i < argc; i++) { if (!strcmp(argv[i], "tag")) { - char *tag; i++; if (i >= argc) die(_("You need to specify a tag name.")); - tag = xstrfmt("refs/tags/%s:refs/tags/%s", - argv[i], argv[i]); - refspec_append(&rs, tag); - free(tag); + refspec_appendf(&rs, "refs/tags/%s:refs/tags/%s", + argv[i], argv[i]); } else { refspec_append(&rs, argv[i]); } @@ -1923,7 +1927,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) close_object_store(the_repository->objects); if (enable_auto_gc) - run_auto_gc(verbosity < 0); + run_auto_maintenance(verbosity < 0); return result; } diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 57489e4eab..9d1ecda2b8 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -9,7 +9,7 @@ static char const * const for_each_ref_usage[] = { N_("git for-each-ref [<options>] [<pattern>]"), N_("git for-each-ref [--points-at <object>]"), - N_("git for-each-ref [(--merged | --no-merged) [<commit>]]"), + N_("git for-each-ref [--merged [<commit>]] [--no-merged [<commit>]]"), N_("git for-each-ref [--contains [<commit>]] [--no-contains [<commit>]]"), NULL }; diff --git a/builtin/gc.c b/builtin/gc.c index aafa0946f5..090959350e 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -28,6 +28,7 @@ #include "blob.h" #include "tree.h" #include "promisor-remote.h" +#include "refs.h" #define FAILED_RUN "failed to run %s" @@ -699,3 +700,339 @@ int cmd_gc(int argc, const char **argv, const char *prefix) return 0; } + +static const char * const builtin_maintenance_run_usage[] = { + N_("git maintenance run [--auto] [--[no-]quiet] [--task=<task>]"), + NULL +}; + +struct maintenance_run_opts { + int auto_flag; + int quiet; +}; + +/* Remember to update object flag allocation in object.h */ +#define SEEN (1u<<0) + +struct cg_auto_data { + int num_not_in_graph; + int limit; +}; + +static int dfs_on_ref(const char *refname, + const struct object_id *oid, int flags, + void *cb_data) +{ + struct cg_auto_data *data = (struct cg_auto_data *)cb_data; + int result = 0; + struct object_id peeled; + struct commit_list *stack = NULL; + struct commit *commit; + + if (!peel_ref(refname, &peeled)) + oid = &peeled; + if (oid_object_info(the_repository, oid, NULL) != OBJ_COMMIT) + return 0; + + commit = lookup_commit(the_repository, oid); + if (!commit) + return 0; + if (parse_commit(commit)) + return 0; + + commit_list_append(commit, &stack); + + while (!result && stack) { + struct commit_list *parent; + + commit = pop_commit(&stack); + + for (parent = commit->parents; parent; parent = parent->next) { + if (parse_commit(parent->item) || + commit_graph_position(parent->item) != COMMIT_NOT_FROM_GRAPH || + parent->item->object.flags & SEEN) + continue; + + parent->item->object.flags |= SEEN; + data->num_not_in_graph++; + + if (data->num_not_in_graph >= data->limit) { + result = 1; + break; + } + + commit_list_append(parent->item, &stack); + } + } + + free_commit_list(stack); + return result; +} + +static int should_write_commit_graph(void) +{ + int result; + struct cg_auto_data data; + + data.num_not_in_graph = 0; + data.limit = 100; + git_config_get_int("maintenance.commit-graph.auto", + &data.limit); + + if (!data.limit) + return 0; + if (data.limit < 0) + return 1; + + result = for_each_ref(dfs_on_ref, &data); + + clear_commit_marks_all(SEEN); + + return result; +} + +static int run_write_commit_graph(struct maintenance_run_opts *opts) +{ + struct child_process child = CHILD_PROCESS_INIT; + + child.git_cmd = 1; + strvec_pushl(&child.args, "commit-graph", "write", + "--split", "--reachable", NULL); + + if (opts->quiet) + strvec_push(&child.args, "--no-progress"); + + return !!run_command(&child); +} + +static int maintenance_task_commit_graph(struct maintenance_run_opts *opts) +{ + close_object_store(the_repository->objects); + if (run_write_commit_graph(opts)) { + error(_("failed to write commit-graph")); + return 1; + } + + return 0; +} + +static int maintenance_task_gc(struct maintenance_run_opts *opts) +{ + struct child_process child = CHILD_PROCESS_INIT; + + child.git_cmd = 1; + strvec_push(&child.args, "gc"); + + if (opts->auto_flag) + strvec_push(&child.args, "--auto"); + if (opts->quiet) + strvec_push(&child.args, "--quiet"); + else + strvec_push(&child.args, "--no-quiet"); + + close_object_store(the_repository->objects); + return run_command(&child); +} + +typedef int maintenance_task_fn(struct maintenance_run_opts *opts); + +/* + * An auto condition function returns 1 if the task should run + * and 0 if the task should NOT run. See needs_to_gc() for an + * example. + */ +typedef int maintenance_auto_fn(void); + +struct maintenance_task { + const char *name; + maintenance_task_fn *fn; + maintenance_auto_fn *auto_condition; + unsigned enabled:1; + + /* -1 if not selected. */ + int selected_order; +}; + +enum maintenance_task_label { + TASK_GC, + TASK_COMMIT_GRAPH, + + /* Leave as final value */ + TASK__COUNT +}; + +static struct maintenance_task tasks[] = { + [TASK_GC] = { + "gc", + maintenance_task_gc, + need_to_gc, + 1, + }, + [TASK_COMMIT_GRAPH] = { + "commit-graph", + maintenance_task_commit_graph, + should_write_commit_graph, + }, +}; + +static int compare_tasks_by_selection(const void *a_, const void *b_) +{ + const struct maintenance_task *a, *b; + + a = (const struct maintenance_task *)&a_; + b = (const struct maintenance_task *)&b_; + + return b->selected_order - a->selected_order; +} + +static int maintenance_run_tasks(struct maintenance_run_opts *opts) +{ + int i, found_selected = 0; + int result = 0; + struct lock_file lk; + struct repository *r = the_repository; + char *lock_path = xstrfmt("%s/maintenance", r->objects->odb->path); + + if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) { + /* + * Another maintenance command is running. + * + * If --auto was provided, then it is likely due to a + * recursive process stack. Do not report an error in + * that case. + */ + if (!opts->auto_flag && !opts->quiet) + warning(_("lock file '%s' exists, skipping maintenance"), + lock_path); + free(lock_path); + return 0; + } + free(lock_path); + + for (i = 0; !found_selected && i < TASK__COUNT; i++) + found_selected = tasks[i].selected_order >= 0; + + if (found_selected) + QSORT(tasks, TASK__COUNT, compare_tasks_by_selection); + + for (i = 0; i < TASK__COUNT; i++) { + if (found_selected && tasks[i].selected_order < 0) + continue; + + if (!found_selected && !tasks[i].enabled) + continue; + + if (opts->auto_flag && + (!tasks[i].auto_condition || + !tasks[i].auto_condition())) + continue; + + trace2_region_enter("maintenance", tasks[i].name, r); + if (tasks[i].fn(opts)) { + error(_("task '%s' failed"), tasks[i].name); + result = 1; + } + trace2_region_leave("maintenance", tasks[i].name, r); + } + + rollback_lock_file(&lk); + return result; +} + +static void initialize_task_config(void) +{ + int i; + struct strbuf config_name = STRBUF_INIT; + gc_config(); + + for (i = 0; i < TASK__COUNT; i++) { + int config_value; + + strbuf_setlen(&config_name, 0); + strbuf_addf(&config_name, "maintenance.%s.enabled", + tasks[i].name); + + if (!git_config_get_bool(config_name.buf, &config_value)) + tasks[i].enabled = config_value; + } + + strbuf_release(&config_name); +} + +static int task_option_parse(const struct option *opt, + const char *arg, int unset) +{ + int i, num_selected = 0; + struct maintenance_task *task = NULL; + + BUG_ON_OPT_NEG(unset); + + for (i = 0; i < TASK__COUNT; i++) { + if (tasks[i].selected_order >= 0) + num_selected++; + if (!strcasecmp(tasks[i].name, arg)) { + task = &tasks[i]; + } + } + + if (!task) { + error(_("'%s' is not a valid task"), arg); + return 1; + } + + if (task->selected_order >= 0) { + error(_("task '%s' cannot be selected multiple times"), arg); + return 1; + } + + task->selected_order = num_selected + 1; + + return 0; +} + +static int maintenance_run(int argc, const char **argv, const char *prefix) +{ + int i; + struct maintenance_run_opts opts; + struct option builtin_maintenance_run_options[] = { + OPT_BOOL(0, "auto", &opts.auto_flag, + N_("run tasks based on the state of the repository")), + OPT_BOOL(0, "quiet", &opts.quiet, + N_("do not report progress or other information over stderr")), + OPT_CALLBACK_F(0, "task", NULL, N_("task"), + N_("run a specific task"), + PARSE_OPT_NONEG, task_option_parse), + OPT_END() + }; + memset(&opts, 0, sizeof(opts)); + + opts.quiet = !isatty(2); + initialize_task_config(); + + for (i = 0; i < TASK__COUNT; i++) + tasks[i].selected_order = -1; + + argc = parse_options(argc, argv, prefix, + builtin_maintenance_run_options, + builtin_maintenance_run_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + if (argc != 0) + usage_with_options(builtin_maintenance_run_usage, + builtin_maintenance_run_options); + return maintenance_run_tasks(&opts); +} + +static const char builtin_maintenance_usage[] = N_("git maintenance run [<options>]"); + +int cmd_maintenance(int argc, const char **argv, const char *prefix) +{ + if (argc < 2 || + (argc == 2 && !strcmp(argv[1], "-h"))) + usage(builtin_maintenance_usage); + + if (!strcmp(argv[1], "run")) + return maintenance_run(argc - 1, argv + 1, prefix); + + die(_("invalid subcommand: %s"), argv[1]); +} diff --git a/builtin/grep.c b/builtin/grep.c index f58979bc3f..c8037388c6 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -319,7 +319,7 @@ static void grep_source_name(struct grep_opt *opt, const char *filename, } if (opt->relative && opt->prefix_length) - quote_path_relative(filename + tree_name_len, opt->prefix, out); + quote_path(filename + tree_name_len, opt->prefix, out, 0); else quote_c_style(filename + tree_name_len, out, NULL, 0); diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 9721bf1ffe..8acd078aa0 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -33,19 +33,61 @@ struct object_stat { }; struct base_data { + /* Initialized by make_base(). */ struct base_data *base; - struct base_data *child; struct object_entry *obj; - void *data; - unsigned long size; int ref_first, ref_last; int ofs_first, ofs_last; + /* + * Threads should increment retain_data if they are about to call + * patch_delta() using this struct's data as a base, and decrement this + * when they are done. While retain_data is nonzero, this struct's data + * will not be freed even if the delta base cache limit is exceeded. + */ + int retain_data; + /* + * The number of direct children that have not been fully processed + * (entered work_head, entered done_head, left done_head). When this + * number reaches zero, this struct base_data can be freed. + */ + int children_remaining; + + /* Not initialized by make_base(). */ + struct list_head list; + void *data; + unsigned long size; }; +/* + * Stack of struct base_data that have unprocessed children. + * threaded_second_pass() uses this as a source of work (the other being the + * objects array). + * + * Guarded by work_mutex. + */ +static LIST_HEAD(work_head); + +/* + * Stack of struct base_data that have children, all of whom have been + * processed or are being processed, and at least one child is being processed. + * These struct base_data must be kept around until the last child is + * processed. + * + * Guarded by work_mutex. + */ +static LIST_HEAD(done_head); + +/* + * All threads share one delta base cache. + * + * base_cache_used is guarded by work_mutex, and base_cache_limit is read-only + * in a thread. + */ +static size_t base_cache_used; +static size_t base_cache_limit; + struct thread_local { pthread_t thread; - struct base_data *base_cache; - size_t base_cache_used; int pack_fd; }; @@ -364,56 +406,42 @@ static void set_thread_data(struct thread_local *data) pthread_setspecific(key, data); } -static struct base_data *alloc_base_data(void) -{ - struct base_data *base = xcalloc(1, sizeof(struct base_data)); - base->ref_last = -1; - base->ofs_last = -1; - return base; -} - static void free_base_data(struct base_data *c) { if (c->data) { FREE_AND_NULL(c->data); - get_thread_data()->base_cache_used -= c->size; + base_cache_used -= c->size; } } static void prune_base_data(struct base_data *retain) { - struct base_data *b; - struct thread_local *data = get_thread_data(); - for (b = data->base_cache; - data->base_cache_used > delta_base_cache_limit && b; - b = b->child) { - if (b->data && b != retain) - free_base_data(b); - } -} + struct list_head *pos; -static void link_base_data(struct base_data *base, struct base_data *c) -{ - if (base) - base->child = c; - else - get_thread_data()->base_cache = c; + if (base_cache_used <= base_cache_limit) + return; - c->base = base; - c->child = NULL; - if (c->data) - get_thread_data()->base_cache_used += c->size; - prune_base_data(c); -} + list_for_each_prev(pos, &done_head) { + struct base_data *b = list_entry(pos, struct base_data, list); + if (b->retain_data || b == retain) + continue; + if (b->data) { + free_base_data(b); + if (base_cache_used <= base_cache_limit) + return; + } + } -static void unlink_base_data(struct base_data *c) -{ - struct base_data *base = c->base; - if (base) - base->child = NULL; - else - get_thread_data()->base_cache = NULL; - free_base_data(c); + list_for_each_prev(pos, &work_head) { + struct base_data *b = list_entry(pos, struct base_data, list); + if (b->retain_data || b == retain) + continue; + if (b->data) { + free_base_data(b); + if (base_cache_used <= base_cache_limit) + return; + } + } } static int is_delta_type(enum object_type type) @@ -614,7 +642,7 @@ static int compare_ofs_delta_bases(off_t offset1, off_t offset2, 0; } -static int find_ofs_delta(const off_t offset, enum object_type type) +static int find_ofs_delta(const off_t offset) { int first = 0, last = nr_ofs_deltas; @@ -624,7 +652,8 @@ static int find_ofs_delta(const off_t offset, enum object_type type) int cmp; cmp = compare_ofs_delta_bases(offset, delta->offset, - type, objects[delta->obj_no].type); + OBJ_OFS_DELTA, + objects[delta->obj_no].type); if (!cmp) return next; if (cmp < 0) { @@ -637,10 +666,9 @@ static int find_ofs_delta(const off_t offset, enum object_type type) } static void find_ofs_delta_children(off_t offset, - int *first_index, int *last_index, - enum object_type type) + int *first_index, int *last_index) { - int first = find_ofs_delta(offset, type); + int first = find_ofs_delta(offset); int last = first; int end = nr_ofs_deltas - 1; @@ -668,7 +696,7 @@ static int compare_ref_delta_bases(const struct object_id *oid1, return oidcmp(oid1, oid2); } -static int find_ref_delta(const struct object_id *oid, enum object_type type) +static int find_ref_delta(const struct object_id *oid) { int first = 0, last = nr_ref_deltas; @@ -678,7 +706,8 @@ static int find_ref_delta(const struct object_id *oid, enum object_type type) int cmp; cmp = compare_ref_delta_bases(oid, &delta->oid, - type, objects[delta->obj_no].type); + OBJ_REF_DELTA, + objects[delta->obj_no].type); if (!cmp) return next; if (cmp < 0) { @@ -691,10 +720,9 @@ static int find_ref_delta(const struct object_id *oid, enum object_type type) } static void find_ref_delta_children(const struct object_id *oid, - int *first_index, int *last_index, - enum object_type type) + int *first_index, int *last_index) { - int first = find_ref_delta(oid, type); + int first = find_ref_delta(oid); int last = first; int end = nr_ref_deltas - 1; @@ -866,15 +894,7 @@ static void sha1_object(const void *data, struct object_entry *obj_entry, } /* - * This function is part of find_unresolved_deltas(). There are two - * walkers going in the opposite ways. - * - * The first one in find_unresolved_deltas() traverses down from - * parent node to children, deflating nodes along the way. However, - * memory for deflated nodes is limited by delta_base_cache_limit, so - * at some point parent node's deflated content may be freed. - * - * The second walker is this function, which goes from current node up + * Walk from current node up * to top parent if necessary to deflate the node. In normal * situation, its parent node would be already deflated, so it just * needs to apply delta. @@ -902,7 +922,7 @@ static void *get_base_data(struct base_data *c) if (!delta_nr) { c->data = get_data_from_pack(obj); c->size = obj->size; - get_thread_data()->base_cache_used += c->size; + base_cache_used += c->size; prune_base_data(c); } for (; delta_nr > 0; delta_nr--) { @@ -918,7 +938,7 @@ static void *get_base_data(struct base_data *c) free(raw); if (!c->data) bad_object(obj->idx.offset, _("failed to apply delta")); - get_thread_data()->base_cache_used += c->size; + base_cache_used += c->size; prune_base_data(c); } free(delta); @@ -926,10 +946,27 @@ static void *get_base_data(struct base_data *c) return c->data; } -static void resolve_delta(struct object_entry *delta_obj, - struct base_data *base, struct base_data *result) +static struct base_data *make_base(struct object_entry *obj, + struct base_data *parent) +{ + struct base_data *base = xcalloc(1, sizeof(struct base_data)); + base->base = parent; + base->obj = obj; + find_ref_delta_children(&obj->idx.oid, + &base->ref_first, &base->ref_last); + find_ofs_delta_children(obj->idx.offset, + &base->ofs_first, &base->ofs_last); + base->children_remaining = base->ref_last - base->ref_first + + base->ofs_last - base->ofs_first + 2; + return base; +} + +static struct base_data *resolve_delta(struct object_entry *delta_obj, + struct base_data *base) { - void *base_data, *delta_data; + void *delta_data, *result_data; + struct base_data *result; + unsigned long result_size; if (show_stat) { int i = delta_obj - objects; @@ -942,115 +979,26 @@ static void resolve_delta(struct object_entry *delta_obj, obj_stat[i].base_object_no = j; } delta_data = get_data_from_pack(delta_obj); - base_data = get_base_data(base); - result->obj = delta_obj; - result->data = patch_delta(base_data, base->size, - delta_data, delta_obj->size, &result->size); + assert(base->data); + result_data = patch_delta(base->data, base->size, + delta_data, delta_obj->size, &result_size); free(delta_data); - if (!result->data) + if (!result_data) bad_object(delta_obj->idx.offset, _("failed to apply delta")); - hash_object_file(the_hash_algo, result->data, result->size, + hash_object_file(the_hash_algo, result_data, result_size, type_name(delta_obj->real_type), &delta_obj->idx.oid); - sha1_object(result->data, NULL, result->size, delta_obj->real_type, + sha1_object(result_data, NULL, result_size, delta_obj->real_type, &delta_obj->idx.oid); + + result = make_base(delta_obj, base); + result->data = result_data; + result->size = result_size; + counter_lock(); nr_resolved_deltas++; counter_unlock(); -} - -/* - * Standard boolean compare-and-swap: atomically check whether "*type" is - * "want"; if so, swap in "set" and return true. Otherwise, leave it untouched - * and return false. - */ -static int compare_and_swap_type(signed char *type, - enum object_type want, - enum object_type set) -{ - enum object_type old; - - type_cas_lock(); - old = *type; - if (old == want) - *type = set; - type_cas_unlock(); - return old == want; -} - -static struct base_data *find_unresolved_deltas_1(struct base_data *base, - struct base_data *prev_base) -{ - if (base->ref_last == -1 && base->ofs_last == -1) { - find_ref_delta_children(&base->obj->idx.oid, - &base->ref_first, &base->ref_last, - OBJ_REF_DELTA); - - find_ofs_delta_children(base->obj->idx.offset, - &base->ofs_first, &base->ofs_last, - OBJ_OFS_DELTA); - - if (base->ref_last == -1 && base->ofs_last == -1) { - free(base->data); - return NULL; - } - - link_base_data(prev_base, base); - } - - if (base->ref_first <= base->ref_last) { - struct object_entry *child = objects + ref_deltas[base->ref_first].obj_no; - struct base_data *result = alloc_base_data(); - - if (!compare_and_swap_type(&child->real_type, OBJ_REF_DELTA, - base->obj->real_type)) - die("REF_DELTA at offset %"PRIuMAX" already resolved (duplicate base %s?)", - (uintmax_t)child->idx.offset, - oid_to_hex(&base->obj->idx.oid)); - - resolve_delta(child, base, result); - if (base->ref_first == base->ref_last && base->ofs_last == -1) - free_base_data(base); - - base->ref_first++; - return result; - } - - if (base->ofs_first <= base->ofs_last) { - struct object_entry *child = objects + ofs_deltas[base->ofs_first].obj_no; - struct base_data *result = alloc_base_data(); - - assert(child->real_type == OBJ_OFS_DELTA); - child->real_type = base->obj->real_type; - resolve_delta(child, base, result); - if (base->ofs_first == base->ofs_last) - free_base_data(base); - - base->ofs_first++; - return result; - } - - unlink_base_data(base); - return NULL; -} - -static void find_unresolved_deltas(struct base_data *base) -{ - struct base_data *new_base, *prev_base = NULL; - for (;;) { - new_base = find_unresolved_deltas_1(base, prev_base); - - if (new_base) { - prev_base = base; - base = new_base; - } else { - free(base); - base = prev_base; - if (!base) - return; - prev_base = base->base; - } - } + return result; } static int compare_ofs_delta_entry(const void *a, const void *b) @@ -1071,34 +1019,131 @@ static int compare_ref_delta_entry(const void *a, const void *b) return oidcmp(&delta_a->oid, &delta_b->oid); } -static void resolve_base(struct object_entry *obj) -{ - struct base_data *base_obj = alloc_base_data(); - base_obj->obj = obj; - base_obj->data = NULL; - find_unresolved_deltas(base_obj); -} - static void *threaded_second_pass(void *data) { - set_thread_data(data); + if (data) + set_thread_data(data); for (;;) { - int i; - counter_lock(); - display_progress(progress, nr_resolved_deltas); - counter_unlock(); + struct base_data *parent = NULL; + struct object_entry *child_obj; + struct base_data *child; + work_lock(); - while (nr_dispatched < nr_objects && - is_delta_type(objects[nr_dispatched].type)) - nr_dispatched++; - if (nr_dispatched >= nr_objects) { - work_unlock(); - break; + if (list_empty(&work_head)) { + /* + * Take an object from the object array. + */ + while (nr_dispatched < nr_objects && + is_delta_type(objects[nr_dispatched].type)) + nr_dispatched++; + if (nr_dispatched >= nr_objects) { + work_unlock(); + break; + } + child_obj = &objects[nr_dispatched++]; + } else { + /* + * Peek at the top of the stack, and take a child from + * it. + */ + parent = list_first_entry(&work_head, struct base_data, + list); + + if (parent->ref_first <= parent->ref_last) { + int offset = ref_deltas[parent->ref_first++].obj_no; + child_obj = objects + offset; + if (child_obj->real_type != OBJ_REF_DELTA) + die("REF_DELTA at offset %"PRIuMAX" already resolved (duplicate base %s?)", + (uintmax_t) child_obj->idx.offset, + oid_to_hex(&parent->obj->idx.oid)); + child_obj->real_type = parent->obj->real_type; + } else { + child_obj = objects + + ofs_deltas[parent->ofs_first++].obj_no; + assert(child_obj->real_type == OBJ_OFS_DELTA); + child_obj->real_type = parent->obj->real_type; + } + + if (parent->ref_first > parent->ref_last && + parent->ofs_first > parent->ofs_last) { + /* + * This parent has run out of children, so move + * it to done_head. + */ + list_del(&parent->list); + list_add(&parent->list, &done_head); + } + + /* + * Ensure that the parent has data, since we will need + * it later. + * + * NEEDSWORK: If parent data needs to be reloaded, this + * prolongs the time that the current thread spends in + * the mutex. A mitigating factor is that parent data + * needs to be reloaded only if the delta base cache + * limit is exceeded, so in the typical case, this does + * not happen. + */ + get_base_data(parent); + parent->retain_data++; } - i = nr_dispatched++; work_unlock(); - resolve_base(&objects[i]); + if (parent) { + child = resolve_delta(child_obj, parent); + if (!child->children_remaining) + FREE_AND_NULL(child->data); + } else { + child = make_base(child_obj, NULL); + if (child->children_remaining) { + /* + * Since this child has its own delta children, + * we will need this data in the future. + * Inflate now so that future iterations will + * have access to this object's data while + * outside the work mutex. + */ + child->data = get_data_from_pack(child_obj); + child->size = child_obj->size; + } + } + + work_lock(); + if (parent) + parent->retain_data--; + if (child->data) { + /* + * This child has its own children, so add it to + * work_head. + */ + list_add(&child->list, &work_head); + base_cache_used += child->size; + prune_base_data(NULL); + } else { + /* + * This child does not have its own children. It may be + * the last descendant of its ancestors; free those + * that we can. + */ + struct base_data *p = parent; + + while (p) { + struct base_data *next_p; + + p->children_remaining--; + if (p->children_remaining) + break; + + next_p = p->base; + free_base_data(p); + list_del(&p->list); + free(p); + + p = next_p; + } + } + work_unlock(); } return NULL; } @@ -1199,6 +1244,7 @@ static void resolve_deltas(void) nr_ref_deltas + nr_ofs_deltas); nr_dispatched = 0; + base_cache_limit = delta_base_cache_limit * nr_threads; if (nr_threads > 1 || getenv("GIT_FORCE_THREADS")) { init_thread(); for (i = 0; i < nr_threads; i++) { @@ -1213,15 +1259,7 @@ static void resolve_deltas(void) cleanup_thread(); return; } - - for (i = 0; i < nr_objects; i++) { - struct object_entry *obj = &objects[i]; - - if (is_delta_type(obj->type)) - continue; - resolve_base(obj); - display_progress(progress, nr_resolved_deltas); - } + threaded_second_pass(¬hread_data); } /* @@ -1376,22 +1414,28 @@ static void fix_unresolved_deltas(struct hashfile *f) for (i = 0; i < nr_ref_deltas; i++) { struct ref_delta_entry *d = sorted_by_pos[i]; enum object_type type; - struct base_data *base_obj = alloc_base_data(); + void *data; + unsigned long size; if (objects[d->obj_no].real_type != OBJ_REF_DELTA) continue; - base_obj->data = read_object_file(&d->oid, &type, - &base_obj->size); - if (!base_obj->data) + data = read_object_file(&d->oid, &type, &size); + if (!data) continue; if (check_object_signature(the_repository, &d->oid, - base_obj->data, base_obj->size, + data, size, type_name(type))) die(_("local object %s is corrupt"), oid_to_hex(&d->oid)); - base_obj->obj = append_obj_to_pack(f, d->oid.hash, - base_obj->data, base_obj->size, type); - find_unresolved_deltas(base_obj); + + /* + * Add this as an object to the objects array and call + * threaded_second_pass() (which will pick up the added + * object). + */ + append_obj_to_pack(f, d->oid.hash, data, size, type); + threaded_second_pass(NULL); + display_progress(progress, nr_resolved_deltas); } free(sorted_by_pos); diff --git a/builtin/log.c b/builtin/log.c index 4ec7f57cf4..b8824d898f 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -33,7 +33,6 @@ #include "commit-slab.h" #include "repository.h" #include "commit-reach.h" -#include "interdiff.h" #include "range-diff.h" #define MAIL_DEFAULT_WRAP 72 @@ -1207,7 +1206,8 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, if (rev->idiff_oid1) { fprintf_ln(rev->diffopt.file, "%s", rev->idiff_title); - show_interdiff(rev, 0); + show_interdiff(rev->idiff_oid1, rev->idiff_oid2, 0, + &rev->diffopt); } if (rev->rdiff1) { @@ -1595,16 +1595,20 @@ static void infer_range_diff_ranges(struct strbuf *r1, struct commit *head) { const char *head_oid = oid_to_hex(&head->object.oid); + int prev_is_range = !!strstr(prev, ".."); - if (!strstr(prev, "..")) { + if (prev_is_range) + strbuf_addstr(r1, prev); + else strbuf_addf(r1, "%s..%s", head_oid, prev); + + if (origin) + strbuf_addf(r2, "%s..%s", oid_to_hex(&origin->object.oid), head_oid); + else if (prev_is_range) + die(_("failed to infer range-diff origin of current series")); + else { + warning(_("using '%s' as range-diff origin of current series"), prev); strbuf_addf(r2, "%s..%s", prev, head_oid); - } else if (!origin) { - die(_("failed to infer range-diff ranges")); - } else { - strbuf_addstr(r1, prev); - strbuf_addf(r2, "%s..%s", - oid_to_hex(&origin->object.oid), head_oid); } } diff --git a/builtin/merge.c b/builtin/merge.c index 032a8f5434..9d5359edc2 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -456,7 +456,7 @@ static void finish(struct commit *head_commit, * user should see them. */ close_object_store(the_repository->objects); - run_auto_gc(verbosity < 0); + run_auto_maintenance(verbosity < 0); } } if (new_head && show_diffstat) { diff --git a/builtin/push.c b/builtin/push.c index bc94078e72..0eeb2c8dd5 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -61,26 +61,27 @@ static struct refspec rs = REFSPEC_INIT_PUSH; static struct string_list push_options_config = STRING_LIST_INIT_DUP; -static const char *map_refspec(const char *ref, - struct remote *remote, struct ref *local_refs) +static void refspec_append_mapped(struct refspec *refspec, const char *ref, + struct remote *remote, struct ref *local_refs) { const char *branch_name; struct ref *matched = NULL; /* Does "ref" uniquely name our ref? */ - if (count_refspec_match(ref, local_refs, &matched) != 1) - return ref; + if (count_refspec_match(ref, local_refs, &matched) != 1) { + refspec_append(refspec, ref); + return; + } if (remote->push.nr) { struct refspec_item query; memset(&query, 0, sizeof(struct refspec_item)); query.src = matched->name; if (!query_refspecs(&remote->push, &query) && query.dst) { - struct strbuf buf = STRBUF_INIT; - strbuf_addf(&buf, "%s%s:%s", - query.force ? "+" : "", - query.src, query.dst); - return strbuf_detach(&buf, NULL); + refspec_appendf(refspec, "%s%s:%s", + query.force ? "+" : "", + query.src, query.dst); + return; } } @@ -88,14 +89,13 @@ static const char *map_refspec(const char *ref, skip_prefix(matched->name, "refs/heads/", &branch_name)) { struct branch *branch = branch_get(branch_name); if (branch->merge_nr == 1 && branch->merge[0]->src) { - struct strbuf buf = STRBUF_INIT; - strbuf_addf(&buf, "%s:%s", - ref, branch->merge[0]->src); - return strbuf_detach(&buf, NULL); + refspec_appendf(refspec, "%s:%s", + ref, branch->merge[0]->src); + return; } } - return ref; + refspec_append(refspec, ref); } static void set_refspecs(const char **refs, int nr, const char *repo) @@ -107,30 +107,26 @@ static void set_refspecs(const char **refs, int nr, const char *repo) for (i = 0; i < nr; i++) { const char *ref = refs[i]; if (!strcmp("tag", ref)) { - struct strbuf tagref = STRBUF_INIT; if (nr <= ++i) die(_("tag shorthand without <tag>")); ref = refs[i]; if (deleterefs) - strbuf_addf(&tagref, ":refs/tags/%s", ref); + refspec_appendf(&rs, ":refs/tags/%s", ref); else - strbuf_addf(&tagref, "refs/tags/%s", ref); - ref = strbuf_detach(&tagref, NULL); + refspec_appendf(&rs, "refs/tags/%s", ref); } else if (deleterefs) { - struct strbuf delref = STRBUF_INIT; if (strchr(ref, ':')) die(_("--delete only accepts plain target ref names")); - strbuf_addf(&delref, ":%s", ref); - ref = strbuf_detach(&delref, NULL); + refspec_appendf(&rs, ":%s", ref); } else if (!strchr(ref, ':')) { if (!remote) { /* lazily grab remote and local_refs */ remote = remote_get(repo); local_refs = get_local_heads(); } - ref = map_refspec(ref, remote, local_refs); - } - refspec_append(&rs, ref); + refspec_append_mapped(&rs, ref, remote, local_refs); + } else + refspec_append(&rs, ref); } } @@ -192,8 +188,6 @@ static const char message_detached_head_die[] = static void setup_push_upstream(struct remote *remote, struct branch *branch, int triangular, int simple) { - struct strbuf refspec = STRBUF_INIT; - if (!branch) die(_(message_detached_head_die), remote->name); if (!branch->merge_nr || !branch->merge || !branch->remote_name) @@ -219,18 +213,14 @@ static void setup_push_upstream(struct remote *remote, struct branch *branch, die_push_simple(branch, remote); } - strbuf_addf(&refspec, "%s:%s", branch->refname, branch->merge[0]->src); - refspec_append(&rs, refspec.buf); + refspec_appendf(&rs, "%s:%s", branch->refname, branch->merge[0]->src); } static void setup_push_current(struct remote *remote, struct branch *branch) { - struct strbuf refspec = STRBUF_INIT; - if (!branch) die(_(message_detached_head_die), remote->name); - strbuf_addf(&refspec, "%s:%s", branch->refname, branch->refname); - refspec_append(&rs, refspec.buf); + refspec_appendf(&rs, "%s:%s", branch->refname, branch->refname); } static int is_workflow_triangular(struct remote *remote) diff --git a/builtin/rebase.c b/builtin/rebase.c index c4ff2039ef..eeca53382f 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -734,10 +734,10 @@ static int finish_rebase(struct rebase_options *opts) apply_autostash(state_dir_path("autostash", opts)); close_object_store(the_repository->objects); /* - * We ignore errors in 'gc --auto', since the + * We ignore errors in 'git maintenance run --auto', since the * user should see them. */ - run_auto_gc(!(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE))); + run_auto_maintenance(!(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE))); if (opts->type == REBASE_MERGE) { struct replay_opts replay = REPLAY_OPTS_INIT; diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 439f29d6c7..bb9909c52e 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -57,6 +57,7 @@ static int advertise_push_options; static int unpack_limit = 100; static off_t max_input_size; static int report_status; +static int report_status_v2; static int use_sideband; static int use_atomic; static int use_push_options; @@ -97,6 +98,17 @@ static int keepalive_in_sec = 5; static struct tmp_objdir *tmp_objdir; +static struct proc_receive_ref { + unsigned int want_add:1, + want_delete:1, + want_modify:1, + negative_ref:1; + char *ref_prefix; + struct proc_receive_ref *next; +} *proc_receive_ref; + +static void proc_receive_ref_append(const char *prefix); + static enum deny_action parse_deny_action(const char *var, const char *value) { if (value) { @@ -229,6 +241,13 @@ static int receive_pack_config(const char *var, const char *value, void *cb) return 0; } + if (strcmp(var, "receive.procreceiverefs") == 0) { + if (!value) + return config_error_nonbool(var); + proc_receive_ref_append(value); + return 0; + } + return git_default_config(var, value, cb); } @@ -240,7 +259,7 @@ static void show_ref(const char *path, const struct object_id *oid) struct strbuf cap = STRBUF_INIT; strbuf_addstr(&cap, - "report-status delete-refs side-band-64k quiet"); + "report-status report-status-v2 delete-refs side-band-64k quiet"); if (advertise_atomic_push) strbuf_addstr(&cap, " atomic"); if (prefer_ofs_delta) @@ -310,17 +329,94 @@ static void write_head_info(void) packet_flush(1); } +#define RUN_PROC_RECEIVE_SCHEDULED 1 +#define RUN_PROC_RECEIVE_RETURNED 2 struct command { struct command *next; const char *error_string; + struct ref_push_report *report; unsigned int skip_update:1, - did_not_exist:1; + did_not_exist:1, + run_proc_receive:2; int index; struct object_id old_oid; struct object_id new_oid; char ref_name[FLEX_ARRAY]; /* more */ }; +static void proc_receive_ref_append(const char *prefix) +{ + struct proc_receive_ref *ref_pattern; + char *p; + int len; + + ref_pattern = xcalloc(1, sizeof(struct proc_receive_ref)); + p = strchr(prefix, ':'); + if (p) { + while (prefix < p) { + if (*prefix == 'a') + ref_pattern->want_add = 1; + else if (*prefix == 'd') + ref_pattern->want_delete = 1; + else if (*prefix == 'm') + ref_pattern->want_modify = 1; + else if (*prefix == '!') + ref_pattern->negative_ref = 1; + prefix++; + } + prefix++; + } else { + ref_pattern->want_add = 1; + ref_pattern->want_delete = 1; + ref_pattern->want_modify = 1; + } + len = strlen(prefix); + while (len && prefix[len - 1] == '/') + len--; + ref_pattern->ref_prefix = xmemdupz(prefix, len); + if (!proc_receive_ref) { + proc_receive_ref = ref_pattern; + } else { + struct proc_receive_ref *end; + + end = proc_receive_ref; + while (end->next) + end = end->next; + end->next = ref_pattern; + } +} + +static int proc_receive_ref_matches(struct command *cmd) +{ + struct proc_receive_ref *p; + + if (!proc_receive_ref) + return 0; + + for (p = proc_receive_ref; p; p = p->next) { + const char *match = p->ref_prefix; + const char *remains; + + if (!p->want_add && is_null_oid(&cmd->old_oid)) + continue; + else if (!p->want_delete && is_null_oid(&cmd->new_oid)) + continue; + else if (!p->want_modify && + !is_null_oid(&cmd->old_oid) && + !is_null_oid(&cmd->new_oid)) + continue; + + if (skip_prefix(cmd->ref_name, match, &remains) && + (!*remains || *remains == '/')) { + if (!p->negative_ref) + return 1; + } else if (p->negative_ref) { + return 1; + } + } + return 0; +} + static void rp_error(const char *err, ...) __attribute__((format (printf, 1, 2))); static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2))); @@ -692,6 +788,7 @@ static void prepare_push_cert_sha1(struct child_process *proc) struct receive_hook_feed_state { struct command *cmd; + struct ref_push_report *report; int skip_broken; struct strbuf buf; const struct string_list *push_options; @@ -779,11 +876,31 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep) cmd = cmd->next; if (!cmd) return -1; /* EOF */ + if (!bufp) + return 0; /* OK, can feed something. */ strbuf_reset(&state->buf); - strbuf_addf(&state->buf, "%s %s %s\n", - oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid), - cmd->ref_name); - state->cmd = cmd->next; + if (!state->report) + state->report = cmd->report; + if (state->report) { + struct object_id *old_oid; + struct object_id *new_oid; + const char *ref_name; + + old_oid = state->report->old_oid ? state->report->old_oid : &cmd->old_oid; + new_oid = state->report->new_oid ? state->report->new_oid : &cmd->new_oid; + ref_name = state->report->ref_name ? state->report->ref_name : cmd->ref_name; + strbuf_addf(&state->buf, "%s %s %s\n", + oid_to_hex(old_oid), oid_to_hex(new_oid), + ref_name); + state->report = state->report->next; + if (!state->report) + state->cmd = cmd->next; + } else { + strbuf_addf(&state->buf, "%s %s %s\n", + oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid), + cmd->ref_name); + state->cmd = cmd->next; + } if (bufp) { *bufp = state->buf.buf; *sizep = state->buf.len; @@ -802,6 +919,7 @@ static int run_receive_hook(struct command *commands, strbuf_init(&state.buf, 0); state.cmd = commands; state.skip_broken = skip_broken; + state.report = NULL; if (feed_receive_hook(&state, NULL, NULL)) return 0; state.cmd = commands; @@ -840,6 +958,268 @@ static int run_update_hook(struct command *cmd) return finish_command(&proc); } +static struct command *find_command_by_refname(struct command *list, + const char *refname) +{ + for (; list; list = list->next) + if (!strcmp(list->ref_name, refname)) + return list; + return NULL; +} + +static int read_proc_receive_report(struct packet_reader *reader, + struct command *commands, + struct strbuf *errmsg) +{ + struct command *cmd; + struct command *hint = NULL; + struct ref_push_report *report = NULL; + int new_report = 0; + int code = 0; + int once = 0; + + for (;;) { + struct object_id old_oid, new_oid; + const char *head; + const char *refname; + char *p; + + if (packet_reader_read(reader) != PACKET_READ_NORMAL) + break; + + head = reader->line; + p = strchr(head, ' '); + if (!p) { + strbuf_addf(errmsg, "proc-receive reported incomplete status line: '%s'\n", head); + code = -1; + continue; + } + *p++ = '\0'; + if (!strcmp(head, "option")) { + const char *key, *val; + + if (!hint || !(report || new_report)) { + if (!once++) + strbuf_addstr(errmsg, "proc-receive reported 'option' without a matching 'ok/ng' directive\n"); + code = -1; + continue; + } + if (new_report) { + if (!hint->report) { + hint->report = xcalloc(1, sizeof(struct ref_push_report)); + report = hint->report; + } else { + report = hint->report; + while (report->next) + report = report->next; + report->next = xcalloc(1, sizeof(struct ref_push_report)); + report = report->next; + } + new_report = 0; + } + key = p; + p = strchr(key, ' '); + if (p) + *p++ = '\0'; + val = p; + if (!strcmp(key, "refname")) + report->ref_name = xstrdup_or_null(val); + else if (!strcmp(key, "old-oid") && val && + !parse_oid_hex(val, &old_oid, &val)) + report->old_oid = oiddup(&old_oid); + else if (!strcmp(key, "new-oid") && val && + !parse_oid_hex(val, &new_oid, &val)) + report->new_oid = oiddup(&new_oid); + else if (!strcmp(key, "forced-update")) + report->forced_update = 1; + else if (!strcmp(key, "fall-through")) + /* Fall through, let 'receive-pack' to execute it. */ + hint->run_proc_receive = 0; + continue; + } + + report = NULL; + new_report = 0; + refname = p; + p = strchr(refname, ' '); + if (p) + *p++ = '\0'; + if (strcmp(head, "ok") && strcmp(head, "ng")) { + strbuf_addf(errmsg, "proc-receive reported bad status '%s' on ref '%s'\n", + head, refname); + code = -1; + continue; + } + + /* first try searching at our hint, falling back to all refs */ + if (hint) + hint = find_command_by_refname(hint, refname); + if (!hint) + hint = find_command_by_refname(commands, refname); + if (!hint) { + strbuf_addf(errmsg, "proc-receive reported status on unknown ref: %s\n", + refname); + code = -1; + continue; + } + if (!hint->run_proc_receive) { + strbuf_addf(errmsg, "proc-receive reported status on unexpected ref: %s\n", + refname); + code = -1; + continue; + } + hint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED; + if (!strcmp(head, "ng")) { + if (p) + hint->error_string = xstrdup(p); + else + hint->error_string = "failed"; + code = -1; + continue; + } + new_report = 1; + } + + for (cmd = commands; cmd; cmd = cmd->next) + if (cmd->run_proc_receive && !cmd->error_string && + !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)) { + cmd->error_string = "proc-receive failed to report status"; + code = -1; + } + return code; +} + +static int run_proc_receive_hook(struct command *commands, + const struct string_list *push_options) +{ + struct child_process proc = CHILD_PROCESS_INIT; + struct async muxer; + struct command *cmd; + const char *argv[2]; + struct packet_reader reader; + struct strbuf cap = STRBUF_INIT; + struct strbuf errmsg = STRBUF_INIT; + int hook_use_push_options = 0; + int version = 0; + int code; + + argv[0] = find_hook("proc-receive"); + if (!argv[0]) { + rp_error("cannot find hook 'proc-receive'"); + return -1; + } + argv[1] = NULL; + + proc.argv = argv; + proc.in = -1; + proc.out = -1; + proc.trace2_hook_name = "proc-receive"; + + if (use_sideband) { + memset(&muxer, 0, sizeof(muxer)); + muxer.proc = copy_to_sideband; + muxer.in = -1; + code = start_async(&muxer); + if (code) + return code; + proc.err = muxer.in; + } else { + proc.err = 0; + } + + code = start_command(&proc); + if (code) { + if (use_sideband) + finish_async(&muxer); + return code; + } + + sigchain_push(SIGPIPE, SIG_IGN); + + /* Version negotiaton */ + packet_reader_init(&reader, proc.out, NULL, 0, + PACKET_READ_CHOMP_NEWLINE | + PACKET_READ_GENTLE_ON_EOF); + if (use_atomic) + strbuf_addstr(&cap, " atomic"); + if (use_push_options) + strbuf_addstr(&cap, " push-options"); + if (cap.len) { + packet_write_fmt(proc.in, "version=1%c%s\n", '\0', cap.buf + 1); + strbuf_release(&cap); + } else { + packet_write_fmt(proc.in, "version=1\n"); + } + packet_flush(proc.in); + + for (;;) { + int linelen; + + if (packet_reader_read(&reader) != PACKET_READ_NORMAL) + break; + + if (reader.pktlen > 8 && starts_with(reader.line, "version=")) { + version = atoi(reader.line + 8); + linelen = strlen(reader.line); + if (linelen < reader.pktlen) { + const char *feature_list = reader.line + linelen + 1; + if (parse_feature_request(feature_list, "push-options")) + hook_use_push_options = 1; + } + } + } + + if (version != 1) { + strbuf_addf(&errmsg, "proc-receive version '%d' is not supported", + version); + code = -1; + goto cleanup; + } + + /* Send commands */ + for (cmd = commands; cmd; cmd = cmd->next) { + if (!cmd->run_proc_receive || cmd->skip_update || cmd->error_string) + continue; + packet_write_fmt(proc.in, "%s %s %s", + oid_to_hex(&cmd->old_oid), + oid_to_hex(&cmd->new_oid), + cmd->ref_name); + } + packet_flush(proc.in); + + /* Send push options */ + if (hook_use_push_options) { + struct string_list_item *item; + + for_each_string_list_item(item, push_options) + packet_write_fmt(proc.in, "%s", item->string); + packet_flush(proc.in); + } + + /* Read result from proc-receive */ + code = read_proc_receive_report(&reader, commands, &errmsg); + +cleanup: + close(proc.in); + close(proc.out); + if (use_sideband) + finish_async(&muxer); + if (finish_command(&proc)) + code = -1; + if (errmsg.len >0) { + char *p = errmsg.buf; + + p += errmsg.len - 1; + if (*p == '\n') + *p = '\0'; + rp_error("%s", errmsg.buf); + strbuf_release(&errmsg); + } + sigchain_pop(SIGPIPE); + + return code; +} + static char *refuse_unconfigured_deny_msg = N_("By default, updating the current branch in a non-bare repository\n" "is denied, because it will make the index and work tree inconsistent\n" @@ -1415,7 +1795,7 @@ static void execute_commands_non_atomic(struct command *commands, struct strbuf err = STRBUF_INIT; for (cmd = commands; cmd; cmd = cmd->next) { - if (!should_process_cmd(cmd)) + if (!should_process_cmd(cmd) || cmd->run_proc_receive) continue; transaction = ref_transaction_begin(&err); @@ -1455,7 +1835,7 @@ static void execute_commands_atomic(struct command *commands, } for (cmd = commands; cmd; cmd = cmd->next) { - if (!should_process_cmd(cmd)) + if (!should_process_cmd(cmd) || cmd->run_proc_receive) continue; cmd->error_string = update(cmd, si); @@ -1491,6 +1871,7 @@ static void execute_commands(struct command *commands, struct iterate_data data; struct async muxer; int err_fd = 0; + int run_proc_receive = 0; if (unpacker_error) { for (cmd = commands; cmd; cmd = cmd->next) @@ -1520,6 +1901,22 @@ static void execute_commands(struct command *commands, reject_updates_to_hidden(commands); + /* + * Try to find commands that have special prefix in their reference names, + * and mark them to run an external "proc-receive" hook later. + */ + if (proc_receive_ref) { + for (cmd = commands; cmd; cmd = cmd->next) { + if (!should_process_cmd(cmd)) + continue; + + if (proc_receive_ref_matches(cmd)) { + cmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED; + run_proc_receive = 1; + } + } + } + if (run_receive_hook(commands, "pre-receive", 0, push_options)) { for (cmd = commands; cmd; cmd = cmd->next) { if (!cmd->error_string) @@ -1546,6 +1943,14 @@ static void execute_commands(struct command *commands, free(head_name_to_free); head_name = head_name_to_free = resolve_refdup("HEAD", 0, NULL, NULL); + if (run_proc_receive && + run_proc_receive_hook(commands, push_options)) + for (cmd = commands; cmd; cmd = cmd->next) + if (!cmd->error_string && + !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED) && + (cmd->run_proc_receive || use_atomic)) + cmd->error_string = "fail to run proc-receive hook"; + if (use_atomic) execute_commands_atomic(commands, si); else @@ -1629,6 +2034,8 @@ static struct command *read_head_info(struct packet_reader *reader, int len = 0; if (parse_feature_request(feature_list, "report-status")) report_status = 1; + if (parse_feature_request(feature_list, "report-status-v2")) + report_status_v2 = 1; if (parse_feature_request(feature_list, "side-band-64k")) use_sideband = LARGE_PACKET_MAX; if (parse_feature_request(feature_list, "quiet")) @@ -1947,6 +2354,51 @@ static void report(struct command *commands, const char *unpack_status) strbuf_release(&buf); } +static void report_v2(struct command *commands, const char *unpack_status) +{ + struct command *cmd; + struct strbuf buf = STRBUF_INIT; + struct ref_push_report *report; + + packet_buf_write(&buf, "unpack %s\n", + unpack_status ? unpack_status : "ok"); + for (cmd = commands; cmd; cmd = cmd->next) { + int count = 0; + + if (cmd->error_string) { + packet_buf_write(&buf, "ng %s %s\n", + cmd->ref_name, + cmd->error_string); + continue; + } + packet_buf_write(&buf, "ok %s\n", + cmd->ref_name); + for (report = cmd->report; report; report = report->next) { + if (count++ > 0) + packet_buf_write(&buf, "ok %s\n", + cmd->ref_name); + if (report->ref_name) + packet_buf_write(&buf, "option refname %s\n", + report->ref_name); + if (report->old_oid) + packet_buf_write(&buf, "option old-oid %s\n", + oid_to_hex(report->old_oid)); + if (report->new_oid) + packet_buf_write(&buf, "option new-oid %s\n", + oid_to_hex(report->new_oid)); + if (report->forced_update) + packet_buf_write(&buf, "option forced-update\n"); + } + } + packet_buf_flush(&buf); + + if (use_sideband) + send_sideband(1, 1, buf.buf, buf.len, use_sideband); + else + write_or_die(1, buf.buf, buf.len); + strbuf_release(&buf); +} + static int delete_only(struct command *commands) { struct command *cmd; @@ -2055,7 +2507,9 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) &push_options); if (pack_lockfile) unlink_or_warn(pack_lockfile); - if (report_status) + if (report_status_v2) + report_v2(commands, unpack_status); + else if (report_status) report(commands, unpack_status); run_receive_hook(commands, "post-receive", 1, &push_options); diff --git a/builtin/remote.c b/builtin/remote.c index 542f56e387..64b4b551eb 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -1356,7 +1356,7 @@ static int set_head(int argc, const char **argv) result |= error(_("Not a valid ref: %s"), buf2.buf); else if (create_symref(buf.buf, buf2.buf, "remote set-head")) result |= error(_("Could not setup %s"), buf.buf); - if (opt_a) + else if (opt_a) printf("%s/HEAD set to %s\n", argv[0], head_name); free(head_name); } diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 2b9610f121..7af148d733 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -29,10 +29,12 @@ static struct send_pack_args args; static void print_helper_status(struct ref *ref) { struct strbuf buf = STRBUF_INIT; + struct ref_push_report *report; for (; ref; ref = ref->next) { const char *msg = NULL; const char *res; + int count = 0; switch(ref->status) { case REF_STATUS_NONE: @@ -94,6 +96,23 @@ static void print_helper_status(struct ref *ref) } strbuf_addch(&buf, '\n'); + if (ref->status == REF_STATUS_OK) { + for (report = ref->report; report; report = report->next) { + if (count++ > 0) + strbuf_addf(&buf, "ok %s\n", ref->name); + if (report->ref_name) + strbuf_addf(&buf, "option refname %s\n", + report->ref_name); + if (report->old_oid) + strbuf_addf(&buf, "option old-oid %s\n", + oid_to_hex(report->old_oid)); + if (report->new_oid) + strbuf_addf(&buf, "option new-oid %s\n", + oid_to_hex(report->new_oid)); + if (report->forced_update) + strbuf_addstr(&buf, "option forced-update\n"); + } + } write_or_die(1, buf.buf, buf.len); } strbuf_release(&buf); diff --git a/builtin/tag.c b/builtin/tag.c index 5cbd80dc3e..ecf011776d 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -26,7 +26,7 @@ static const char * const git_tag_usage[] = { "\t\t<tagname> [<head>]"), N_("git tag -d <tagname>..."), N_("git tag -l [-n[<num>]] [--contains <commit>] [--no-contains <commit>] [--points-at <object>]\n" - "\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"), + "\t\t[--format=<format>] [--merged <commit>] [--no-merged <commit>] [<pattern>...]"), N_("git tag -v [--format=<format>] <tagname>..."), NULL }; @@ -457,8 +457,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (argc == 0) cmdmode = 'l'; else if (filter.with_commit || filter.no_commit || - filter.points_at.nr || filter.merge_commit || - filter.lines != -1) + filter.reachable_from || filter.unreachable_from || + filter.points_at.nr || filter.lines != -1) cmdmode = 'l'; } @@ -509,7 +509,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) die(_("--no-contains option is only allowed in list mode")); if (filter.points_at.nr) die(_("--points-at option is only allowed in list mode")); - if (filter.merge_commit) + if (filter.reachable_from || filter.unreachable_from) die(_("--merged and --no-merged options are only allowed in list mode")); if (cmdmode == 'd') return for_each_tag_name(argv, delete_tag, NULL); diff --git a/builtin/worktree.c b/builtin/worktree.c index bb70fde97e..99abaeec6c 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -555,7 +555,7 @@ static int add(int ac, const char **av, const char *prefix) N_("create a new branch")), OPT_STRING('B', NULL, &new_branch_force, N_("branch"), N_("create or reset a branch")), - OPT_BOOL(0, "detach", &opts.detach, N_("detach HEAD at named commit")), + OPT_BOOL('d', "detach", &opts.detach, N_("detach HEAD at named commit")), OPT_BOOL(0, "checkout", &opts.checkout, N_("populate the new working tree")), OPT_BOOL(0, "lock", &opts.keep_locked, N_("keep the new working tree locked")), OPT__QUIET(&opts.quiet, N_("suppress progress reporting")), diff --git a/command-list.txt b/command-list.txt index e5901f2213..0e3204e7d1 100644 --- a/command-list.txt +++ b/command-list.txt @@ -117,6 +117,7 @@ git-ls-remote plumbinginterrogators git-ls-tree plumbinginterrogators git-mailinfo purehelpers git-mailsplit purehelpers +git-maintenance mainporcelain git-merge mainporcelain history git-merge-base plumbinginterrogators git-merge-file plumbingmanipulators diff --git a/commit-graph.c b/commit-graph.c index 0ed003e218..695c1cf7a3 100644 --- a/commit-graph.c +++ b/commit-graph.c @@ -172,7 +172,7 @@ static char *get_split_graph_filename(struct object_directory *odb, oid_hex); } -static char *get_chain_filename(struct object_directory *odb) +char *get_commit_graph_chain_filename(struct object_directory *odb) { return xstrfmt("%s/info/commit-graphs/commit-graph-chain", odb->path); } @@ -523,7 +523,7 @@ static struct commit_graph *load_commit_graph_chain(struct repository *r, struct stat st; struct object_id *oids; int i = 0, valid = 1, count; - char *chain_name = get_chain_filename(odb); + char *chain_name = get_commit_graph_chain_filename(odb); FILE *fp; int stat_res; @@ -1675,7 +1675,7 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx) } if (ctx->split) { - char *lock_name = get_chain_filename(ctx->odb); + char *lock_name = get_commit_graph_chain_filename(ctx->odb); hold_lock_file_for_update_mode(&lk, lock_name, LOCK_DIE_ON_ERROR, 0444); @@ -2045,7 +2045,7 @@ static void expire_commit_graphs(struct write_commit_graph_context *ctx) if (ctx->split_opts && ctx->split_opts->expire_time) expire_time = ctx->split_opts->expire_time; if (!ctx->split) { - char *chain_file_name = get_chain_filename(ctx->odb); + char *chain_file_name = get_commit_graph_chain_filename(ctx->odb); unlink(chain_file_name); free(chain_file_name); ctx->num_commit_graphs_after = 0; diff --git a/commit-graph.h b/commit-graph.h index 09a97030dc..765221cdcc 100644 --- a/commit-graph.h +++ b/commit-graph.h @@ -25,6 +25,7 @@ struct raw_object_store; struct string_list; char *get_commit_graph_filename(struct object_directory *odb); +char *get_commit_graph_chain_filename(struct object_directory *odb); int open_commit_graph(const char *graph_file, int *fd, struct stat *st); /* @@ -95,7 +95,6 @@ static inline int parse_commit_no_graph(struct commit *commit) #ifndef NO_THE_REPOSITORY_COMPATIBILITY_MACROS #define parse_commit_internal(item, quiet, use) repo_parse_commit_internal(the_repository, item, quiet, use) -#define parse_commit_gently(item, quiet) repo_parse_commit_gently(the_repository, item, quiet) #define parse_commit(item) repo_parse_commit(the_repository, item) #endif diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 9147fba3d5..8be4a0316e 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1784,6 +1784,10 @@ _git_format_patch () " "" "${cur##--thread=}" return ;; + --base=*|--interdiff=*|--range-diff=*) + __git_complete_refs --cur="${cur#--*=}" + return + ;; --*) __gitcomp_builtin format-patch "$__git_format_patch_extra_options" return diff --git a/contrib/diff-highlight/DiffHighlight.pm b/contrib/diff-highlight/DiffHighlight.pm index e2589922a6..376f577737 100644 --- a/contrib/diff-highlight/DiffHighlight.pm +++ b/contrib/diff-highlight/DiffHighlight.pm @@ -112,7 +112,7 @@ sub handle_line { # Since we can receive arbitrary input, there's no optimal # place to flush. Flushing on a blank line is a heuristic that # happens to match git-log output. - if (!length) { + if (/^$/) { $flush_cb->(); } } diff --git a/diff-lib.c b/diff-lib.c index 5d5d3dafab..346fdcf0b0 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -565,3 +565,28 @@ int index_differs_from(struct repository *r, object_array_clear(&rev.pending); return (rev.diffopt.flags.has_changes != 0); } + +static struct strbuf *idiff_prefix_cb(struct diff_options *opt, void *data) +{ + return data; +} + +void show_interdiff(const struct object_id *oid1, const struct object_id *oid2, + int indent, struct diff_options *diffopt) +{ + struct diff_options opts; + struct strbuf prefix = STRBUF_INIT; + + memcpy(&opts, diffopt, sizeof(opts)); + opts.output_format = DIFF_FORMAT_PATCH; + opts.output_prefix = idiff_prefix_cb; + strbuf_addchars(&prefix, ' ', indent); + opts.output_prefix_data = &prefix; + diff_setup_done(&opts); + + diff_tree_oid(oid1, oid2, "", &opts); + diffcore_std(&opts); + diff_flush(&opts); + + strbuf_release(&prefix); +} @@ -482,14 +482,14 @@ int git_diff_basic_config(const char *var, const char *value, void *cb) static char *quote_two(const char *one, const char *two) { - int need_one = quote_c_style(one, NULL, NULL, 1); - int need_two = quote_c_style(two, NULL, NULL, 1); + int need_one = quote_c_style(one, NULL, NULL, CQUOTE_NODQ); + int need_two = quote_c_style(two, NULL, NULL, CQUOTE_NODQ); struct strbuf res = STRBUF_INIT; if (need_one + need_two) { strbuf_addch(&res, '"'); - quote_c_style(one, &res, NULL, 1); - quote_c_style(two, &res, NULL, 1); + quote_c_style(one, &res, NULL, CQUOTE_NODQ); + quote_c_style(two, &res, NULL, CQUOTE_NODQ); strbuf_addch(&res, '"'); } else { strbuf_addstr(&res, one); @@ -601,6 +601,13 @@ int index_differs_from(struct repository *r, const char *def, int ita_invisible_in_index); /* + * Emit an interdiff of two object ID's to 'diff_options.file' optionally + * indented by 'indent' spaces. + */ +void show_interdiff(const struct object_id *, const struct object_id *, + int indent, struct diff_options *); + +/* * Fill the contents of the filespec "df", respecting any textconv defined by * its userdiff driver. The "driver" parameter must come from a * previous call to get_textconv(), and therefore should either be NULL or have diff --git a/generate-cmdlist.sh b/generate-cmdlist.sh index 45fecf8bdf..9dbbb08e70 100755 --- a/generate-cmdlist.sh +++ b/generate-cmdlist.sh @@ -10,7 +10,7 @@ command_list () { } get_categories () { - tr ' ' '\n'| + tr ' ' '\012'| grep -v '^$' | sort | uniq diff --git a/git-add--interactive.perl b/git-add--interactive.perl index f36c0078ac..8a72018712 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -714,7 +714,7 @@ sub parse_diff { if (defined $patch_mode_revision) { push @diff_cmd, get_diff_reference($patch_mode_revision); } - my @diff = run_cmd_pipe("git", @diff_cmd, "--", $path); + my @diff = run_cmd_pipe("git", @diff_cmd, qw(--no-color --), $path); my @colored = (); if ($diff_use_color) { my @display_cmd = ("git", @diff_cmd, qw(--color --), $path); @@ -754,13 +754,16 @@ sub parse_diff_header { my $head = { TEXT => [], DISPLAY => [], TYPE => 'header' }; my $mode = { TEXT => [], DISPLAY => [], TYPE => 'mode' }; my $deletion = { TEXT => [], DISPLAY => [], TYPE => 'deletion' }; - my $addition = { TEXT => [], DISPLAY => [], TYPE => 'addition' }; + my $addition; for (my $i = 0; $i < @{$src->{TEXT}}; $i++) { + if ($src->{TEXT}->[$i] =~ /^new file/) { + $addition = 1; + $head->{TYPE} = 'addition'; + } my $dest = $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? $mode : $src->{TEXT}->[$i] =~ /^deleted file/ ? $deletion : - $src->{TEXT}->[$i] =~ /^new file/ ? $addition : $head; push @{$dest->{TEXT}}, $src->{TEXT}->[$i]; push @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i]; @@ -1501,12 +1504,6 @@ sub patch_update_file { push @{$deletion->{DISPLAY}}, @{$hunk->{DISPLAY}}; } @hunk = ($deletion); - } elsif (@{$addition->{TEXT}}) { - foreach my $hunk (@hunk) { - push @{$addition->{TEXT}}, @{$hunk->{TEXT}}; - push @{$addition->{DISPLAY}}, @{$hunk->{DISPLAY}}; - } - @hunk = ($addition); } $num = scalar @hunk; @@ -1516,6 +1513,7 @@ sub patch_update_file { my ($prev, $next, $other, $undecided, $i); $other = ''; + last if ($ix and !$num); if ($num <= $ix) { $ix = 0; } @@ -1548,35 +1546,51 @@ sub patch_update_file { last; } } - last if (!$undecided); + last if (!$undecided && ($num || !$addition)); - if ($hunk[$ix]{TYPE} eq 'hunk' && - hunk_splittable($hunk[$ix]{TEXT})) { - $other .= ',s'; - } - if ($hunk[$ix]{TYPE} eq 'hunk') { - $other .= ',e'; - } - for (@{$hunk[$ix]{DISPLAY}}) { - print; + if ($num) { + if ($hunk[$ix]{TYPE} eq 'hunk' && + hunk_splittable($hunk[$ix]{TEXT})) { + $other .= ',s'; + } + if ($hunk[$ix]{TYPE} eq 'hunk') { + $other .= ',e'; + } + for (@{$hunk[$ix]{DISPLAY}}) { + print; + } } - print colored $prompt_color, "(", ($ix+1), "/$num) ", - sprintf(__($patch_update_prompt_modes{$patch_mode}{$hunk[$ix]{TYPE}}), $other); + my $type = $num ? $hunk[$ix]{TYPE} : $head->{TYPE}; + print colored $prompt_color, "(", ($ix+1), "/", ($num ? $num : 1), ") ", + sprintf(__($patch_update_prompt_modes{$patch_mode}{$type}), $other); my $line = prompt_single_character; last unless defined $line; if ($line) { if ($line =~ /^y/i) { - $hunk[$ix]{USE} = 1; + if ($num) { + $hunk[$ix]{USE} = 1; + } else { + $head->{USE} = 1; + } } elsif ($line =~ /^n/i) { - $hunk[$ix]{USE} = 0; + if ($num) { + $hunk[$ix]{USE} = 0; + } else { + $head->{USE} = 0; + } } elsif ($line =~ /^a/i) { - while ($ix < $num) { - if (!defined $hunk[$ix]{USE}) { - $hunk[$ix]{USE} = 1; + if ($num) { + while ($ix < $num) { + if (!defined $hunk[$ix]{USE}) { + $hunk[$ix]{USE} = 1; + } + $ix++; } + } else { + $head->{USE} = 1; $ix++; } next; @@ -1613,19 +1627,28 @@ sub patch_update_file { next; } elsif ($line =~ /^d/i) { - while ($ix < $num) { - if (!defined $hunk[$ix]{USE}) { - $hunk[$ix]{USE} = 0; + if ($num) { + while ($ix < $num) { + if (!defined $hunk[$ix]{USE}) { + $hunk[$ix]{USE} = 0; + } + $ix++; } + } else { + $head->{USE} = 0; $ix++; } next; } elsif ($line =~ /^q/i) { - for ($i = 0; $i < $num; $i++) { - if (!defined $hunk[$i]{USE}) { - $hunk[$i]{USE} = 0; + if ($num) { + for ($i = 0; $i < $num; $i++) { + if (!defined $hunk[$i]{USE}) { + $hunk[$i]{USE} = 0; + } } + } elsif (!defined $head->{USE}) { + $head->{USE} = 0; } $quit = 1; last; @@ -1743,7 +1766,7 @@ sub patch_update_file { } } - @hunk = coalesce_overlapping_hunks(@hunk); + @hunk = coalesce_overlapping_hunks(@hunk) if ($num); my $n_lofs = 0; my @result = (); @@ -1753,7 +1776,7 @@ sub patch_update_file { } } - if (@result) { + if (@result or $head->{USE}) { my @patch = reassemble_patch($head->{TEXT}, @result); my $apply_routine = $patch_mode_flavour{APPLY}; &$apply_routine(@patch); @@ -4237,7 +4237,7 @@ class P4Unshelve(Command): """ for parent in (range(65535)): - log = extractLogMessageFromGitCommit("{0}^{1}".format(starting_point, parent)) + log = extractLogMessageFromGitCommit("{0}~{1}".format(starting_point, parent)) settings = extractSettingsGitLog(log) if 'change' in settings: return settings @@ -534,6 +534,7 @@ static struct cmd_struct commands[] = { { "ls-tree", cmd_ls_tree, RUN_SETUP }, { "mailinfo", cmd_mailinfo, RUN_SETUP_GENTLY | NO_PARSEOPT }, { "mailsplit", cmd_mailsplit, NO_PARSEOPT }, + { "maintenance", cmd_maintenance, RUN_SETUP_GENTLY | NO_PARSEOPT }, { "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE }, { "merge-base", cmd_merge_base, RUN_SETUP }, { "merge-file", cmd_merge_file, RUN_SETUP_GENTLY }, diff --git a/interdiff.c b/interdiff.c deleted file mode 100644 index c81d680a6c..0000000000 --- a/interdiff.c +++ /dev/null @@ -1,28 +0,0 @@ -#include "cache.h" -#include "commit.h" -#include "revision.h" -#include "interdiff.h" - -static struct strbuf *idiff_prefix_cb(struct diff_options *opt, void *data) -{ - return data; -} - -void show_interdiff(struct rev_info *rev, int indent) -{ - struct diff_options opts; - struct strbuf prefix = STRBUF_INIT; - - memcpy(&opts, &rev->diffopt, sizeof(opts)); - opts.output_format = DIFF_FORMAT_PATCH; - opts.output_prefix = idiff_prefix_cb; - strbuf_addchars(&prefix, ' ', indent); - opts.output_prefix_data = &prefix; - diff_setup_done(&opts); - - diff_tree_oid(rev->idiff_oid1, rev->idiff_oid2, "", &opts); - diffcore_std(&opts); - diff_flush(&opts); - - strbuf_release(&prefix); -} diff --git a/interdiff.h b/interdiff.h deleted file mode 100644 index 01c730a5c9..0000000000 --- a/interdiff.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef INTERDIFF_H -#define INTERDIFF_H - -struct rev_info; - -void show_interdiff(struct rev_info *, int indent); - -#endif diff --git a/log-tree.c b/log-tree.c index 55a68d0c61..cb8942fec1 100644 --- a/log-tree.c +++ b/log-tree.c @@ -15,7 +15,6 @@ #include "sequencer.h" #include "line-log.h" #include "help.h" -#include "interdiff.h" #include "range-diff.h" static struct decoration name_decoration = { "object names" }; @@ -800,7 +799,8 @@ void show_log(struct rev_info *opt) next_commentary_block(opt, NULL); fprintf_ln(opt->diffopt.file, "%s", opt->idiff_title); - show_interdiff(opt, 2); + show_interdiff(opt->idiff_oid1, opt->idiff_oid2, 2, + &opt->diffopt); memcpy(&diff_queued_diff, &dq, sizeof(diff_queued_diff)); } @@ -917,26 +917,15 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log } /* More than one parent? */ - if (parents && parents->next) { + if (parents->next) { if (opt->ignore_merges) return 0; else if (opt->combine_merges) return do_diff_combined(opt, commit); - else if (opt->first_parent_only) { - /* - * Generate merge log entry only for the first - * parent, showing summary diff of the others - * we merged _in_. - */ - parse_commit_or_die(parents->item); - diff_tree_oid(get_commit_tree_oid(parents->item), - oid, "", &opt->diffopt); - log_tree_diff_flush(opt); - return !opt->loginfo; + else if (!opt->first_parent_only) { + /* If we show multiple diffs, show the parent info */ + log->parent = parents->item; } - - /* If we show individual diffs, show the parent info */ - log->parent = parents->item; } showed_log = 0; @@ -952,7 +941,7 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log /* Set up the log info for the next parent, if any.. */ parents = parents->next; - if (!parents) + if (!parents || opt->first_parent_only) break; log->parent = parents->item; opt->loginfo = log; diff --git a/mergetools/meld b/mergetools/meld index 7a08470f88..aab4ebb935 100644 --- a/mergetools/meld +++ b/mergetools/meld @@ -3,34 +3,87 @@ diff_cmd () { } merge_cmd () { - if test -z "${meld_has_output_option:+set}" + check_meld_for_features + + option_auto_merge= + if test "$meld_use_auto_merge_option" = true then - check_meld_for_output_version + option_auto_merge="--auto-merge" fi if test "$meld_has_output_option" = true then - "$merge_tool_path" --output="$MERGED" \ + "$merge_tool_path" $option_auto_merge --output="$MERGED" \ "$LOCAL" "$BASE" "$REMOTE" else - "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE" + "$merge_tool_path" $option_auto_merge "$LOCAL" "$MERGED" "$REMOTE" fi } -# Check whether we should use 'meld --output <file>' -check_meld_for_output_version () { - meld_path="$(git config mergetool.meld.path)" - meld_path="${meld_path:-meld}" +# Get meld help message +init_meld_help_msg () { + if test -z "$meld_help_msg" + then + meld_path="$(git config mergetool.meld.path || echo meld)" + meld_help_msg=$("$meld_path" --help 2>&1) + fi +} - if meld_has_output_option=$(git config --bool mergetool.meld.hasOutput) +# Check the features and set flags +check_meld_for_features () { + # Check whether we should use 'meld --output <file>' + if test -z "$meld_has_output_option" then - : use configured value - elif "$meld_path" --help 2>&1 | - grep -e '--output=' -e '\[OPTION\.\.\.\]' >/dev/null + meld_has_output_option=$(git config --bool mergetool.meld.hasOutput) + case "$meld_has_output_option" in + true | false) + : use configured value + ;; + *) + : empty or invalid configured value, detecting "--output" automatically + init_meld_help_msg + + case "$meld_help_msg" in + *"--output="* | *'[OPTION...]'*) + # All version that has [OPTION...] supports --output + meld_has_output_option=true + ;; + *) + meld_has_output_option=false + ;; + esac + ;; + esac + fi + # Check whether we should use 'meld --auto-merge ...' + if test -z "$meld_use_auto_merge_option" then - : old ones mention --output and new ones just say OPTION... - meld_has_output_option=true - else - meld_has_output_option=false + meld_use_auto_merge_option=$( + git config --bool-or-str mergetool.meld.useAutoMerge + ) + case "$meld_use_auto_merge_option" in + true | false) + : use well formatted boolean value + ;; + auto) + # testing the "--auto-merge" option only if config is "auto" + init_meld_help_msg + + case "$meld_help_msg" in + *"--auto-merge"* | *'[OPTION...]'*) + meld_use_auto_merge_option=true + ;; + *) + meld_use_auto_merge_option=false + ;; + esac + ;; + "") + meld_use_auto_merge_option=false + ;; + *) + die "unknown mergetool.meld.useAutoMerge: $meld_use_auto_merge_option" + ;; + esac fi } @@ -432,14 +432,11 @@ static size_t write_midx_header(struct hashfile *f, unsigned char num_chunks, uint32_t num_packs) { - unsigned char byte_values[4]; - hashwrite_be32(f, MIDX_SIGNATURE); - byte_values[0] = MIDX_VERSION; - byte_values[1] = oid_version(); - byte_values[2] = num_chunks; - byte_values[3] = 0; /* unused */ - hashwrite(f, byte_values, sizeof(byte_values)); + hashwrite_u8(f, MIDX_VERSION); + hashwrite_u8(f, oid_version()); + hashwrite_u8(f, num_chunks); + hashwrite_u8(f, 0); /* unused */ hashwrite_be32(f, num_packs); return MIDX_HEADER_SIZE; @@ -73,6 +73,7 @@ struct object_array { * sha1-name.c: 20 * list-objects-filter.c: 21 * builtin/fsck.c: 0--3 + * builtin/gc.c: 0 * builtin/index-pack.c: 2021 * builtin/pack-objects.c: 20 * builtin/reflog.c: 10--12 diff --git a/pack-bitmap-write.c b/pack-bitmap-write.c index a7a4964b50..5e998bdaa7 100644 --- a/pack-bitmap-write.c +++ b/pack-bitmap-write.c @@ -503,8 +503,7 @@ static void write_hash_cache(struct hashfile *f, for (i = 0; i < index_nr; ++i) { struct object_entry *entry = (struct object_entry *)index[i]; - uint32_t hash_value = htonl(entry->hash); - hashwrite(f, &hash_value, sizeof(hash_value)); + hashwrite_be32(f, entry->hash); } } diff --git a/pack-write.c b/pack-write.c index 685d327d80..a6cdb3c67c 100644 --- a/pack-write.c +++ b/pack-write.c @@ -117,10 +117,8 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec list = sorted_by_sha; for (i = 0; i < nr_objects; i++) { struct pack_idx_entry *obj = *list++; - if (index_version < 2) { - uint32_t offset = htonl(obj->offset); - hashwrite(f, &offset, 4); - } + if (index_version < 2) + hashwrite_be32(f, obj->offset); hashwrite(f, obj->oid.hash, the_hash_algo->rawsz); if ((opts->flags & WRITE_IDX_STRICT) && (i && oideq(&list[-2]->oid, &obj->oid))) @@ -135,8 +133,7 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec list = sorted_by_sha; for (i = 0; i < nr_objects; i++) { struct pack_idx_entry *obj = *list++; - uint32_t crc32_val = htonl(obj->crc32); - hashwrite(f, &crc32_val, 4); + hashwrite_be32(f, obj->crc32); } /* write the 32-bit offset table */ @@ -148,8 +145,7 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec offset = (need_large_offset(obj->offset, opts) ? (0x80000000 | nr_large_offset++) : obj->offset); - offset = htonl(offset); - hashwrite(f, &offset, 4); + hashwrite_be32(f, offset); } /* write the large offset table */ diff --git a/packfile.c b/packfile.c index 9ef27508f2..e69012e7f2 100644 --- a/packfile.c +++ b/packfile.c @@ -923,6 +923,7 @@ unsigned long repo_approximate_object_count(struct repository *r) count += p->num_objects; } r->objects->approximate_object_count = count; + r->objects->approximate_object_count_valid = 1; } return r->objects->approximate_object_count; } @@ -210,7 +210,7 @@ int sq_dequote_to_strvec(char *arg, struct strvec *array) */ #define X8(x) x, x, x, x, x, x, x, x #define X16(x) X8(x), X8(x) -static signed char const sq_lookup[256] = { +static signed char const cq_lookup[256] = { /* 0 1 2 3 4 5 6 7 */ /* 0x00 */ 1, 1, 1, 1, 1, 1, 1, 'a', /* 0x08 */ 'b', 't', 'n', 'v', 'f', 'r', 1, 1, @@ -223,9 +223,9 @@ static signed char const sq_lookup[256] = { /* 0x80 */ /* set to 0 */ }; -static inline int sq_must_quote(char c) +static inline int cq_must_quote(char c) { - return sq_lookup[(unsigned char)c] + quote_path_fully > 0; + return cq_lookup[(unsigned char)c] + quote_path_fully > 0; } /* returns the longest prefix not needing a quote up to maxlen if positive. @@ -235,9 +235,9 @@ static size_t next_quote_pos(const char *s, ssize_t maxlen) { size_t len; if (maxlen < 0) { - for (len = 0; !sq_must_quote(s[len]); len++); + for (len = 0; !cq_must_quote(s[len]); len++); } else { - for (len = 0; len < maxlen && !sq_must_quote(s[len]); len++); + for (len = 0; len < maxlen && !cq_must_quote(s[len]); len++); } return len; } @@ -256,7 +256,7 @@ static size_t next_quote_pos(const char *s, ssize_t maxlen) * Return value is the same as in (1). */ static size_t quote_c_style_counted(const char *name, ssize_t maxlen, - struct strbuf *sb, FILE *fp, int no_dq) + struct strbuf *sb, FILE *fp, unsigned flags) { #undef EMIT #define EMIT(c) \ @@ -272,6 +272,7 @@ static size_t quote_c_style_counted(const char *name, ssize_t maxlen, count += (l); \ } while (0) + int no_dq = !!(flags & CQUOTE_NODQ); size_t len, count = 0; const char *p = name; @@ -291,8 +292,8 @@ static size_t quote_c_style_counted(const char *name, ssize_t maxlen, ch = (unsigned char)*p++; if (maxlen >= 0) maxlen -= len + 1; - if (sq_lookup[ch] >= ' ') { - EMIT(sq_lookup[ch]); + if (cq_lookup[ch] >= ' ') { + EMIT(cq_lookup[ch]); } else { EMIT(((ch >> 6) & 03) + '0'); EMIT(((ch >> 3) & 07) + '0'); @@ -309,19 +310,21 @@ static size_t quote_c_style_counted(const char *name, ssize_t maxlen, return count; } -size_t quote_c_style(const char *name, struct strbuf *sb, FILE *fp, int nodq) +size_t quote_c_style(const char *name, struct strbuf *sb, FILE *fp, unsigned flags) { - return quote_c_style_counted(name, -1, sb, fp, nodq); + return quote_c_style_counted(name, -1, sb, fp, flags); } -void quote_two_c_style(struct strbuf *sb, const char *prefix, const char *path, int nodq) +void quote_two_c_style(struct strbuf *sb, const char *prefix, const char *path, + unsigned flags) { + int nodq = !!(flags & CQUOTE_NODQ); if (quote_c_style(prefix, NULL, NULL, 0) || quote_c_style(path, NULL, NULL, 0)) { if (!nodq) strbuf_addch(sb, '"'); - quote_c_style(prefix, sb, NULL, 1); - quote_c_style(path, sb, NULL, 1); + quote_c_style(prefix, sb, NULL, CQUOTE_NODQ); + quote_c_style(path, sb, NULL, CQUOTE_NODQ); if (!nodq) strbuf_addch(sb, '"'); } else { @@ -352,13 +355,25 @@ void write_name_quoted_relative(const char *name, const char *prefix, } /* quote path as relative to the given prefix */ -char *quote_path_relative(const char *in, const char *prefix, - struct strbuf *out) +char *quote_path(const char *in, const char *prefix, struct strbuf *out, unsigned flags) { struct strbuf sb = STRBUF_INIT; const char *rel = relative_path(in, prefix, &sb); + int force_dq = ((flags & QUOTE_PATH_QUOTE_SP) && strchr(rel, ' ')); + strbuf_reset(out); - quote_c_style_counted(rel, strlen(rel), out, NULL, 0); + + /* + * If the caller wants us to enclose the output in a dq-pair + * whether quote_c_style_counted() needs to, we do it ourselves + * and tell quote_c_style_counted() not to. + */ + if (force_dq) + strbuf_addch(out, '"'); + quote_c_style_counted(rel, strlen(rel), out, NULL, + force_dq ? CQUOTE_NODQ : 0); + if (force_dq) + strbuf_addch(out, '"'); strbuf_release(&sb); return out->buf; @@ -64,16 +64,19 @@ struct strvec; int sq_dequote_to_strvec(char *arg, struct strvec *); int unquote_c_style(struct strbuf *, const char *quoted, const char **endp); -size_t quote_c_style(const char *name, struct strbuf *, FILE *, int no_dq); -void quote_two_c_style(struct strbuf *, const char *, const char *, int); + +/* Bits in the flags parameter to quote_c_style() */ +#define CQUOTE_NODQ 01 +size_t quote_c_style(const char *name, struct strbuf *, FILE *, unsigned); +void quote_two_c_style(struct strbuf *, const char *, const char *, unsigned); void write_name_quoted(const char *name, FILE *, int terminator); void write_name_quoted_relative(const char *name, const char *prefix, FILE *fp, int terminator); /* quote path as relative to the given prefix */ -char *quote_path_relative(const char *in, const char *prefix, - struct strbuf *out); +char *quote_path(const char *in, const char *prefix, struct strbuf *out, unsigned flags); +#define QUOTE_PATH_QUOTE_SP 01 /* quoting as a string literal for other languages */ void perl_quote_buf(struct strbuf *sb, const char *src); diff --git a/read-cache.c b/read-cache.c index fa291cdbee..ecf6f68994 100644 --- a/read-cache.c +++ b/read-cache.c @@ -2101,7 +2101,7 @@ static unsigned long load_cache_entries_threaded(struct index_state *istate, con nr = 0; for (j = p->ieot_start; j < p->ieot_start + p->ieot_blocks; j++) nr += p->ieot->entries[j].nr; - istate->ce_mem_pool = xmalloc(sizeof(*istate->ce_mem_pool)); + p->ce_mem_pool = xmalloc(sizeof(*istate->ce_mem_pool)); if (istate->version == 4) { mem_pool_init(p->ce_mem_pool, estimate_cache_size_from_compressed(nr)); diff --git a/ref-filter.c b/ref-filter.c index 110bcd741a..5550a0d34c 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -2167,9 +2167,9 @@ static int ref_filter_handler(const char *refname, const struct object_id *oid, * obtain the commit using the 'oid' available and discard all * non-commits early. The actual filtering is done later. */ - if (filter->merge_commit || filter->with_commit || filter->no_commit || filter->verbose) { - commit = lookup_commit_reference_gently(the_repository, oid, - 1); + if (filter->reachable_from || filter->unreachable_from || + filter->with_commit || filter->no_commit || filter->verbose) { + commit = lookup_commit_reference_gently(the_repository, oid, 1); if (!commit) return 0; /* We perform the filtering for the '--contains' option... */ @@ -2231,13 +2231,19 @@ void ref_array_clear(struct ref_array *array) } } -static void do_merge_filter(struct ref_filter_cbdata *ref_cbdata) +#define EXCLUDE_REACHED 0 +#define INCLUDE_REACHED 1 +static void reach_filter(struct ref_array *array, + struct commit_list *check_reachable, + int include_reached) { struct rev_info revs; int i, old_nr; - struct ref_filter *filter = ref_cbdata->filter; - struct ref_array *array = ref_cbdata->array; struct commit **to_clear = xcalloc(sizeof(struct commit *), array->nr); + struct commit_list *cr; + + if (!check_reachable) + return; repo_init_revisions(the_repository, &revs, NULL); @@ -2247,8 +2253,11 @@ static void do_merge_filter(struct ref_filter_cbdata *ref_cbdata) to_clear[i] = item->commit; } - filter->merge_commit->object.flags |= UNINTERESTING; - add_pending_object(&revs, &filter->merge_commit->object, ""); + for (cr = check_reachable; cr; cr = cr->next) { + struct commit *merge_commit = cr->item; + merge_commit->object.flags |= UNINTERESTING; + add_pending_object(&revs, &merge_commit->object, ""); + } revs.limited = 1; if (prepare_revision_walk(&revs)) @@ -2263,14 +2272,19 @@ static void do_merge_filter(struct ref_filter_cbdata *ref_cbdata) int is_merged = !!(commit->object.flags & UNINTERESTING); - if (is_merged == (filter->merge == REF_FILTER_MERGED_INCLUDE)) + if (is_merged == include_reached) array->items[array->nr++] = array->items[i]; else free_array_item(item); } clear_commit_marks_many(old_nr, to_clear, ALL_REV_FLAGS); - clear_commit_marks(filter->merge_commit, ALL_REV_FLAGS); + + while (check_reachable) { + struct commit *merge_commit = pop_commit(&check_reachable); + clear_commit_marks(merge_commit, ALL_REV_FLAGS); + } + free(to_clear); } @@ -2322,8 +2336,8 @@ int filter_refs(struct ref_array *array, struct ref_filter *filter, unsigned int clear_contains_cache(&ref_cbdata.no_contains_cache); /* Filters that need revision walking */ - if (filter->merge_commit) - do_merge_filter(&ref_cbdata); + reach_filter(array, filter->reachable_from, INCLUDE_REACHED); + reach_filter(array, filter->unreachable_from, EXCLUDE_REACHED); return ret; } @@ -2541,31 +2555,22 @@ int parse_opt_merge_filter(const struct option *opt, const char *arg, int unset) { struct ref_filter *rf = opt->value; struct object_id oid; - int no_merged = starts_with(opt->long_name, "no"); + struct commit *merge_commit; BUG_ON_OPT_NEG(unset); - if (rf->merge) { - if (no_merged) { - return error(_("option `%s' is incompatible with --merged"), - opt->long_name); - } else { - return error(_("option `%s' is incompatible with --no-merged"), - opt->long_name); - } - } - - rf->merge = no_merged - ? REF_FILTER_MERGED_OMIT - : REF_FILTER_MERGED_INCLUDE; - if (get_oid(arg, &oid)) die(_("malformed object name %s"), arg); - rf->merge_commit = lookup_commit_reference_gently(the_repository, - &oid, 0); - if (!rf->merge_commit) + merge_commit = lookup_commit_reference_gently(the_repository, &oid, 0); + + if (!merge_commit) return error(_("option `%s' must point to a commit"), opt->long_name); + if (starts_with(opt->long_name, "no")) + commit_list_insert(merge_commit, &rf->unreachable_from); + else + commit_list_insert(merge_commit, &rf->reachable_from); + return 0; } diff --git a/ref-filter.h b/ref-filter.h index 8ecc33cdfa..feaef4a8fd 100644 --- a/ref-filter.h +++ b/ref-filter.h @@ -54,13 +54,8 @@ struct ref_filter { struct oid_array points_at; struct commit_list *with_commit; struct commit_list *no_commit; - - enum { - REF_FILTER_MERGED_NONE = 0, - REF_FILTER_MERGED_INCLUDE, - REF_FILTER_MERGED_OMIT - } merge; - struct commit *merge_commit; + struct commit_list *reachable_from; + struct commit_list *unreachable_from; unsigned int with_commit_tag_algo : 1, match_as_path : 1, @@ -1774,6 +1774,7 @@ struct ref_store *get_main_ref_store(struct repository *r) BUG("attempting to get main_ref_store outside of repository"); r->refs_private = ref_store_init(r->gitdir, REF_STORE_ALL_CAPS); + r->refs_private = maybe_debug_wrap_ref_store(r->gitdir, r->refs_private); return r->refs_private; } diff --git a/refs/debug.c b/refs/debug.c new file mode 100644 index 0000000000..922e64fa6a --- /dev/null +++ b/refs/debug.c @@ -0,0 +1,398 @@ + +#include "refs-internal.h" +#include "trace.h" + +static struct trace_key trace_refs = TRACE_KEY_INIT(REFS); + +struct debug_ref_store { + struct ref_store base; + struct ref_store *refs; +}; + +extern struct ref_storage_be refs_be_debug; + +struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_store *store) +{ + struct debug_ref_store *res; + struct ref_storage_be *be_copy; + + if (!trace_want(&trace_refs)) { + return store; + } + res = xmalloc(sizeof(struct debug_ref_store)); + be_copy = xmalloc(sizeof(*be_copy)); + *be_copy = refs_be_debug; + /* we never deallocate backends, so safe to copy the pointer. */ + be_copy->name = store->be->name; + trace_printf_key(&trace_refs, "ref_store for %s\n", gitdir); + res->refs = store; + base_ref_store_init((struct ref_store *)res, be_copy); + return (struct ref_store *)res; +} + +static int debug_init_db(struct ref_store *refs, struct strbuf *err) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)refs; + int res = drefs->refs->be->init_db(drefs->refs, err); + trace_printf_key(&trace_refs, "init_db: %d\n", res); + return res; +} + +static int debug_transaction_prepare(struct ref_store *refs, + struct ref_transaction *transaction, + struct strbuf *err) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)refs; + int res; + transaction->ref_store = drefs->refs; + res = drefs->refs->be->transaction_prepare(drefs->refs, transaction, + err); + trace_printf_key(&trace_refs, "transaction_prepare: %d\n", res); + return res; +} + +static void print_update(int i, const char *refname, + const struct object_id *old_oid, + const struct object_id *new_oid, unsigned int flags, + unsigned int type, const char *msg) +{ + char o[GIT_MAX_HEXSZ + 1] = "null"; + char n[GIT_MAX_HEXSZ + 1] = "null"; + if (old_oid) + oid_to_hex_r(o, old_oid); + if (new_oid) + oid_to_hex_r(n, new_oid); + + type &= 0xf; /* see refs.h REF_* */ + flags &= REF_HAVE_NEW | REF_HAVE_OLD | REF_NO_DEREF | + REF_FORCE_CREATE_REFLOG; + trace_printf_key(&trace_refs, "%d: %s %s -> %s (F=0x%x, T=0x%x) \"%s\"\n", i, refname, + o, n, flags, type, msg); +} + +static void print_transaction(struct ref_transaction *transaction) +{ + int i; + trace_printf_key(&trace_refs, "transaction {\n"); + for (i = 0; i < transaction->nr; i++) { + struct ref_update *u = transaction->updates[i]; + print_update(i, u->refname, &u->old_oid, &u->new_oid, u->flags, + u->type, u->msg); + } + trace_printf_key(&trace_refs, "}\n"); +} + +static int debug_transaction_finish(struct ref_store *refs, + struct ref_transaction *transaction, + struct strbuf *err) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)refs; + int res; + transaction->ref_store = drefs->refs; + res = drefs->refs->be->transaction_finish(drefs->refs, transaction, + err); + print_transaction(transaction); + trace_printf_key(&trace_refs, "finish: %d\n", res); + return res; +} + +static int debug_transaction_abort(struct ref_store *refs, + struct ref_transaction *transaction, + struct strbuf *err) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)refs; + int res; + transaction->ref_store = drefs->refs; + res = drefs->refs->be->transaction_abort(drefs->refs, transaction, err); + return res; +} + +static int debug_initial_transaction_commit(struct ref_store *refs, + struct ref_transaction *transaction, + struct strbuf *err) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)refs; + int res; + transaction->ref_store = drefs->refs; + res = drefs->refs->be->initial_transaction_commit(drefs->refs, + transaction, err); + return res; +} + +static int debug_pack_refs(struct ref_store *ref_store, unsigned int flags) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + int res = drefs->refs->be->pack_refs(drefs->refs, flags); + trace_printf_key(&trace_refs, "pack_refs: %d\n", res); + return res; +} + +static int debug_create_symref(struct ref_store *ref_store, + const char *ref_name, const char *target, + const char *logmsg) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + int res = drefs->refs->be->create_symref(drefs->refs, ref_name, target, + logmsg); + trace_printf_key(&trace_refs, "create_symref: %s -> %s \"%s\": %d\n", ref_name, + target, logmsg, res); + return res; +} + +static int debug_delete_refs(struct ref_store *ref_store, const char *msg, + struct string_list *refnames, unsigned int flags) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + int res = + drefs->refs->be->delete_refs(drefs->refs, msg, refnames, flags); + int i; + trace_printf_key(&trace_refs, "delete_refs {\n"); + for (i = 0; i < refnames->nr; i++) + trace_printf_key(&trace_refs, "%s\n", refnames->items[i].string); + trace_printf_key(&trace_refs, "}: %d\n", res); + return res; +} + +static int debug_rename_ref(struct ref_store *ref_store, const char *oldref, + const char *newref, const char *logmsg) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + int res = drefs->refs->be->rename_ref(drefs->refs, oldref, newref, + logmsg); + trace_printf_key(&trace_refs, "rename_ref: %s -> %s \"%s\": %d\n", oldref, newref, + logmsg, res); + return res; +} + +static int debug_copy_ref(struct ref_store *ref_store, const char *oldref, + const char *newref, const char *logmsg) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + int res = + drefs->refs->be->copy_ref(drefs->refs, oldref, newref, logmsg); + trace_printf_key(&trace_refs, "copy_ref: %s -> %s \"%s\": %d\n", oldref, newref, + logmsg, res); + return res; +} + +struct debug_ref_iterator { + struct ref_iterator base; + struct ref_iterator *iter; +}; + +static int debug_ref_iterator_advance(struct ref_iterator *ref_iterator) +{ + struct debug_ref_iterator *diter = + (struct debug_ref_iterator *)ref_iterator; + int res = diter->iter->vtable->advance(diter->iter); + if (res) + trace_printf_key(&trace_refs, "iterator_advance: (%d)\n", res); + else + trace_printf_key(&trace_refs, "iterator_advance: %s (0)\n", + diter->iter->refname); + + diter->base.ordered = diter->iter->ordered; + diter->base.refname = diter->iter->refname; + diter->base.oid = diter->iter->oid; + diter->base.flags = diter->iter->flags; + return res; +} + +static int debug_ref_iterator_peel(struct ref_iterator *ref_iterator, + struct object_id *peeled) +{ + struct debug_ref_iterator *diter = + (struct debug_ref_iterator *)ref_iterator; + int res = diter->iter->vtable->peel(diter->iter, peeled); + trace_printf_key(&trace_refs, "iterator_peel: %s: %d\n", diter->iter->refname, res); + return res; +} + +static int debug_ref_iterator_abort(struct ref_iterator *ref_iterator) +{ + struct debug_ref_iterator *diter = + (struct debug_ref_iterator *)ref_iterator; + int res = diter->iter->vtable->abort(diter->iter); + trace_printf_key(&trace_refs, "iterator_abort: %d\n", res); + return res; +} + +static struct ref_iterator_vtable debug_ref_iterator_vtable = { + debug_ref_iterator_advance, debug_ref_iterator_peel, + debug_ref_iterator_abort +}; + +static struct ref_iterator * +debug_ref_iterator_begin(struct ref_store *ref_store, const char *prefix, + unsigned int flags) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + struct ref_iterator *res = + drefs->refs->be->iterator_begin(drefs->refs, prefix, flags); + struct debug_ref_iterator *diter = xcalloc(1, sizeof(*diter)); + base_ref_iterator_init(&diter->base, &debug_ref_iterator_vtable, 1); + diter->iter = res; + trace_printf_key(&trace_refs, "ref_iterator_begin: %s (0x%x)\n", prefix, flags); + return &diter->base; +} + +static int debug_read_raw_ref(struct ref_store *ref_store, const char *refname, + struct object_id *oid, struct strbuf *referent, + unsigned int *type) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + int res = 0; + + oidcpy(oid, &null_oid); + res = drefs->refs->be->read_raw_ref(drefs->refs, refname, oid, referent, + type); + + if (res == 0) { + trace_printf_key(&trace_refs, "read_raw_ref: %s: %s (=> %s) type %x: %d\n", + refname, oid_to_hex(oid), referent->buf, *type, res); + } else { + trace_printf_key(&trace_refs, "read_raw_ref: %s: %d\n", refname, res); + } + return res; +} + +static struct ref_iterator * +debug_reflog_iterator_begin(struct ref_store *ref_store) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + struct ref_iterator *res = + drefs->refs->be->reflog_iterator_begin(drefs->refs); + trace_printf_key(&trace_refs, "for_each_reflog_iterator_begin\n"); + return res; +} + +struct debug_reflog { + const char *refname; + each_reflog_ent_fn *fn; + void *cb_data; +}; + +static int debug_print_reflog_ent(struct object_id *old_oid, + struct object_id *new_oid, + const char *committer, timestamp_t timestamp, + int tz, const char *msg, void *cb_data) +{ + struct debug_reflog *dbg = (struct debug_reflog *)cb_data; + int ret; + char o[GIT_MAX_HEXSZ + 1] = "null"; + char n[GIT_MAX_HEXSZ + 1] = "null"; + if (old_oid) + oid_to_hex_r(o, old_oid); + if (new_oid) + oid_to_hex_r(n, new_oid); + + ret = dbg->fn(old_oid, new_oid, committer, timestamp, tz, msg, + dbg->cb_data); + trace_printf_key(&trace_refs, "reflog_ent %s (ret %d): %s -> %s, %s %ld \"%s\"\n", + dbg->refname, ret, o, n, committer, (long int)timestamp, msg); + return ret; +} + +static int debug_for_each_reflog_ent(struct ref_store *ref_store, + const char *refname, each_reflog_ent_fn fn, + void *cb_data) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + struct debug_reflog dbg = { + .refname = refname, + .fn = fn, + .cb_data = cb_data, + }; + + int res = drefs->refs->be->for_each_reflog_ent( + drefs->refs, refname, &debug_print_reflog_ent, &dbg); + trace_printf_key(&trace_refs, "for_each_reflog: %s: %d\n", refname, res); + return res; +} + +static int debug_for_each_reflog_ent_reverse(struct ref_store *ref_store, + const char *refname, + each_reflog_ent_fn fn, + void *cb_data) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + struct debug_reflog dbg = { + .refname = refname, + .fn = fn, + .cb_data = cb_data, + }; + int res = drefs->refs->be->for_each_reflog_ent_reverse( + drefs->refs, refname, &debug_print_reflog_ent, &dbg); + trace_printf_key(&trace_refs, "for_each_reflog_reverse: %s: %d\n", refname, res); + return res; +} + +static int debug_reflog_exists(struct ref_store *ref_store, const char *refname) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + int res = drefs->refs->be->reflog_exists(drefs->refs, refname); + trace_printf_key(&trace_refs, "reflog_exists: %s: %d\n", refname, res); + return res; +} + +static int debug_create_reflog(struct ref_store *ref_store, const char *refname, + int force_create, struct strbuf *err) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + int res = drefs->refs->be->create_reflog(drefs->refs, refname, + force_create, err); + trace_printf_key(&trace_refs, "create_reflog: %s: %d\n", refname, res); + return res; +} + +static int debug_delete_reflog(struct ref_store *ref_store, const char *refname) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + int res = drefs->refs->be->delete_reflog(drefs->refs, refname); + trace_printf_key(&trace_refs, "delete_reflog: %s: %d\n", refname, res); + return res; +} + +static int debug_reflog_expire(struct ref_store *ref_store, const char *refname, + const struct object_id *oid, unsigned int flags, + reflog_expiry_prepare_fn prepare_fn, + reflog_expiry_should_prune_fn should_prune_fn, + reflog_expiry_cleanup_fn cleanup_fn, + void *policy_cb_data) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + int res = drefs->refs->be->reflog_expire(drefs->refs, refname, oid, + flags, prepare_fn, + should_prune_fn, cleanup_fn, + policy_cb_data); + trace_printf_key(&trace_refs, "reflog_expire: %s: %d\n", refname, res); + return res; +} + +struct ref_storage_be refs_be_debug = { + NULL, + "debug", + NULL, + debug_init_db, + debug_transaction_prepare, + debug_transaction_finish, + debug_transaction_abort, + debug_initial_transaction_commit, + + debug_pack_refs, + debug_create_symref, + debug_delete_refs, + debug_rename_ref, + debug_copy_ref, + + debug_ref_iterator_begin, + debug_read_raw_ref, + + debug_reflog_iterator_begin, + debug_for_each_reflog_ent, + debug_for_each_reflog_ent_reverse, + debug_reflog_exists, + debug_create_reflog, + debug_delete_reflog, + debug_reflog_expire, +}; diff --git a/refs/files-backend.c b/refs/files-backend.c index dd712e47f4..04e85e7002 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -39,13 +39,6 @@ #define REF_NEEDS_COMMIT (1 << 6) /* - * Used as a flag in ref_update::flags when we want to log a ref - * update but not actually perform it. This is used when a symbolic - * ref update is split up. - */ -#define REF_LOG_ONLY (1 << 7) - -/* * Used as a flag in ref_update::flags when the ref_update was via an * update to HEAD. */ diff --git a/refs/refs-internal.h b/refs/refs-internal.h index 527b0a6e2e..467f4b3c93 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -32,6 +32,13 @@ struct ref_transaction; #define REF_HAVE_OLD (1 << 3) /* + * Used as a flag in ref_update::flags when we want to log a ref + * update but not actually perform it. This is used when a symbolic + * ref update is split up. + */ +#define REF_LOG_ONLY (1 << 7) + +/* * Return the length of time to retry acquiring a loose reference lock * before giving up, in milliseconds: */ @@ -690,4 +697,9 @@ int parse_loose_ref_contents(const char *buf, struct object_id *oid, void base_ref_store_init(struct ref_store *refs, const struct ref_storage_be *be); +/* + * Support GIT_TRACE_REFS by optionally wrapping the given ref_store instance. + */ +struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_store *store); + #endif /* REFS_REFS_INTERNAL_H */ @@ -153,7 +153,7 @@ void refspec_init(struct refspec *rs, int fetch) rs->fetch = fetch; } -void refspec_append(struct refspec *rs, const char *refspec) +static void refspec_append_nodup(struct refspec *rs, char *refspec) { struct refspec_item item; @@ -163,7 +163,21 @@ void refspec_append(struct refspec *rs, const char *refspec) rs->items[rs->nr++] = item; ALLOC_GROW(rs->raw, rs->raw_nr + 1, rs->raw_alloc); - rs->raw[rs->raw_nr++] = xstrdup(refspec); + rs->raw[rs->raw_nr++] = refspec; +} + +void refspec_append(struct refspec *rs, const char *refspec) +{ + refspec_append_nodup(rs, xstrdup(refspec)); +} + +void refspec_appendf(struct refspec *rs, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + refspec_append_nodup(rs, xstrvfmt(fmt, ap)); + va_end(ap); } void refspec_appendn(struct refspec *rs, const char **refspecs, int nr) @@ -56,6 +56,8 @@ void refspec_item_init_or_die(struct refspec_item *item, const char *refspec, void refspec_item_clear(struct refspec_item *item); void refspec_init(struct refspec *rs, int fetch); void refspec_append(struct refspec *rs, const char *refspec); +__attribute__((format (printf,2,3))) +void refspec_appendf(struct refspec *rs, const char *fmt, ...); void refspec_appendn(struct refspec *rs, const char **refspecs, int nr); void refspec_clear(struct refspec *rs); @@ -287,19 +287,15 @@ static void read_branches_file(struct remote *remote) frag = (char *)git_default_branch_name(); add_url_alias(remote, strbuf_detach(&buf, NULL)); - strbuf_addf(&buf, "refs/heads/%s:refs/heads/%s", - frag, remote->name); - refspec_append(&remote->fetch, buf.buf); + refspec_appendf(&remote->fetch, "refs/heads/%s:refs/heads/%s", + frag, remote->name); /* * Cogito compatible push: push current HEAD to remote #branch * (master if missing) */ - strbuf_reset(&buf); - strbuf_addf(&buf, "HEAD:refs/heads/%s", frag); - refspec_append(&remote->push, buf.buf); + refspec_appendf(&remote->push, "HEAD:refs/heads/%s", frag); remote->fetch_tags = 1; /* always auto-follow */ - strbuf_release(&buf); } static int handle_config(const char *key, const char *value, void *cb) @@ -93,6 +93,14 @@ int for_each_remote(each_remote_fn fn, void *priv); int remote_has_url(struct remote *remote, const char *url); +struct ref_push_report { + const char *ref_name; + struct object_id *old_oid; + struct object_id *new_oid; + unsigned int forced_update:1; + struct ref_push_report *next; +}; + struct ref { struct ref *next; struct object_id old_oid; @@ -140,6 +148,7 @@ struct ref { REF_STATUS_ATOMIC_PUSH_FAILED } status; char *remote_status; + struct ref_push_report *report; struct ref *peer_ref; /* when renaming */ char name[FLEX_ARRAY]; /* more */ }; diff --git a/revision.c b/revision.c index 1239023f93..067030e64c 100644 --- a/revision.c +++ b/revision.c @@ -440,7 +440,7 @@ static struct commit *handle_commit(struct rev_info *revs, if (object->type == OBJ_COMMIT) { struct commit *commit = (struct commit *)object; - if (parse_commit(commit) < 0) + if (repo_parse_commit(revs->repo, commit) < 0) die("unable to parse commit %s", name); if (flags & UNINTERESTING) { mark_parents_uninteresting(commit); @@ -1013,7 +1013,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit) ts->treesame[0] = 1; } } - if (parse_commit(p) < 0) + if (repo_parse_commit(revs->repo, p) < 0) die("cannot simplify commit %s (because of %s)", oid_to_hex(&commit->object.oid), oid_to_hex(&p->object.oid)); @@ -1058,7 +1058,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit) * IOW, we pretend this parent is a * "root" commit. */ - if (parse_commit(p) < 0) + if (repo_parse_commit(revs->repo, p) < 0) die("cannot simplify commit %s (invalid %s)", oid_to_hex(&commit->object.oid), oid_to_hex(&p->object.oid)); @@ -1126,7 +1126,7 @@ static int process_parents(struct rev_info *revs, struct commit *commit, parent = parent->next; if (p) p->object.flags |= UNINTERESTING; - if (parse_commit_gently(p, 1) < 0) + if (repo_parse_commit_gently(revs->repo, p, 1) < 0) continue; if (p->parents) mark_parents_uninteresting(p); @@ -1157,7 +1157,7 @@ static int process_parents(struct rev_info *revs, struct commit *commit, struct commit *p = parent->item; int gently = revs->ignore_missing_links || revs->exclude_promisor_objects; - if (parse_commit_gently(p, gently) < 0) { + if (repo_parse_commit_gently(revs->repo, p, gently) < 0) { if (revs->exclude_promisor_objects && is_promisor_object(&p->object.oid)) { if (revs->first_parent_only) @@ -3337,7 +3337,7 @@ static void explore_walk_step(struct rev_info *revs) if (!c) return; - if (parse_commit_gently(c, 1) < 0) + if (repo_parse_commit_gently(revs->repo, c, 1) < 0) return; if (revs->sort_order == REV_SORT_BY_AUTHOR_DATE) @@ -3375,7 +3375,7 @@ static void indegree_walk_step(struct rev_info *revs) if (!c) return; - if (parse_commit_gently(c, 1) < 0) + if (repo_parse_commit_gently(revs->repo, c, 1) < 0) return; explore_to_depth(revs, commit_graph_generation(c)); @@ -3457,7 +3457,7 @@ static void init_topo_walk(struct rev_info *revs) struct commit *c = list->item; uint32_t generation; - if (parse_commit_gently(c, 1)) + if (repo_parse_commit_gently(revs->repo, c, 1)) continue; test_flag_and_insert(&info->explore_queue, c, TOPO_WALK_EXPLORED); @@ -3521,7 +3521,7 @@ static void expand_topo_walk(struct rev_info *revs, struct commit *commit) if (parent->object.flags & UNINTERESTING) continue; - if (parse_commit_gently(parent, 1) < 0) + if (repo_parse_commit_gently(revs->repo, parent, 1) < 0) continue; generation = commit_graph_generation(parent); diff --git a/run-command.c b/run-command.c index cc9c3296ba..2ee59acdc8 100644 --- a/run-command.c +++ b/run-command.c @@ -1866,15 +1866,13 @@ int run_processes_parallel_tr2(int n, get_next_task_fn get_next_task, return result; } -int run_auto_gc(int quiet) +int run_auto_maintenance(int quiet) { - struct strvec argv_gc_auto = STRVEC_INIT; - int status; + struct child_process maint = CHILD_PROCESS_INIT; - strvec_pushl(&argv_gc_auto, "gc", "--auto", NULL); - if (quiet) - strvec_push(&argv_gc_auto, "--quiet"); - status = run_command_v_opt(argv_gc_auto.v, RUN_GIT_CMD); - strvec_clear(&argv_gc_auto); - return status; + maint.git_cmd = 1; + strvec_pushl(&maint.args, "maintenance", "run", "--auto", NULL); + strvec_push(&maint.args, quiet ? "--quiet" : "--no-quiet"); + + return run_command(&maint); } diff --git a/run-command.h b/run-command.h index 8b9bfaef16..6472b38bde 100644 --- a/run-command.h +++ b/run-command.h @@ -221,7 +221,7 @@ int run_hook_ve(const char *const *env, const char *name, va_list args); /* * Trigger an auto-gc */ -int run_auto_gc(int quiet); +int run_auto_maintenance(int quiet); #define RUN_COMMAND_NO_STDIN 1 #define RUN_GIT_CMD 2 /*If this is to be git sub-command */ diff --git a/send-pack.c b/send-pack.c index 632f1580ca..c9698070fc 100644 --- a/send-pack.c +++ b/send-pack.c @@ -154,25 +154,79 @@ static int receive_status(struct packet_reader *reader, struct ref *refs) { struct ref *hint; int ret; + struct ref_push_report *report = NULL; + int new_report = 0; + int once = 0; hint = NULL; ret = receive_unpack_status(reader); while (1) { + struct object_id old_oid, new_oid; + const char *head; const char *refname; - char *msg; + char *p; if (packet_reader_read(reader) != PACKET_READ_NORMAL) break; - if (!starts_with(reader->line, "ok ") && !starts_with(reader->line, "ng ")) { - error("invalid ref status from remote: %s", reader->line); + head = reader->line; + p = strchr(head, ' '); + if (!p) { + error("invalid status line from remote: %s", reader->line); ret = -1; break; } + *p++ = '\0'; - refname = reader->line + 3; - msg = strchr(refname, ' '); - if (msg) - *msg++ = '\0'; + if (!strcmp(head, "option")) { + const char *key, *val; + if (!hint || !(report || new_report)) { + if (!once++) + error("'option' without a matching 'ok/ng' directive"); + ret = -1; + continue; + } + if (new_report) { + if (!hint->report) { + hint->report = xcalloc(1, sizeof(struct ref_push_report)); + report = hint->report; + } else { + report = hint->report; + while (report->next) + report = report->next; + report->next = xcalloc(1, sizeof(struct ref_push_report)); + report = report->next; + } + new_report = 0; + } + key = p; + p = strchr(key, ' '); + if (p) + *p++ = '\0'; + val = p; + if (!strcmp(key, "refname")) + report->ref_name = xstrdup_or_null(val); + else if (!strcmp(key, "old-oid") && val && + !parse_oid_hex(val, &old_oid, &val)) + report->old_oid = oiddup(&old_oid); + else if (!strcmp(key, "new-oid") && val && + !parse_oid_hex(val, &new_oid, &val)) + report->new_oid = oiddup(&new_oid); + else if (!strcmp(key, "forced-update")) + report->forced_update = 1; + continue; + } + + report = NULL; + new_report = 0; + if (strcmp(head, "ok") && strcmp(head, "ng")) { + error("invalid ref status from remote: %s", head); + ret = -1; + break; + } + refname = p; + p = strchr(refname, ' '); + if (p) + *p++ = '\0'; /* first try searching at our hint, falling back to all refs */ if (hint) hint = find_ref_by_name(hint, refname); @@ -180,22 +234,27 @@ static int receive_status(struct packet_reader *reader, struct ref *refs) hint = find_ref_by_name(refs, refname); if (!hint) { warning("remote reported status on unknown ref: %s", - refname); + refname); continue; } - if (hint->status != REF_STATUS_EXPECTING_REPORT) { + if (hint->status != REF_STATUS_EXPECTING_REPORT && + hint->status != REF_STATUS_OK && + hint->status != REF_STATUS_REMOTE_REJECT) { warning("remote reported status on unexpected ref: %s", - refname); + refname); continue; } - - if (reader->line[0] == 'o' && reader->line[1] == 'k') - hint->status = REF_STATUS_OK; - else + if (!strcmp(head, "ng")) { hint->status = REF_STATUS_REMOTE_REJECT; - hint->remote_status = xstrdup_or_null(msg); - /* start our next search from the next ref */ - hint = hint->next; + if (p) + hint->remote_status = xstrdup(p); + else + hint->remote_status = "failed"; + } else { + hint->status = REF_STATUS_OK; + hint->remote_status = xstrdup_or_null(p); + new_report = 1; + } } return ret; } @@ -244,7 +303,12 @@ static int check_to_send_update(const struct ref *ref, const struct send_pack_ar return CHECK_REF_STATUS_REJECTED; case REF_STATUS_UPTODATE: return CHECK_REF_UPTODATE; + default: + case REF_STATUS_EXPECTING_REPORT: + /* already passed checks on the local side */ + case REF_STATUS_OK: + /* of course this is OK */ return 0; } } @@ -371,7 +435,9 @@ int send_pack(struct send_pack_args *args, struct packet_reader reader; /* Does the other end support the reporting? */ - if (server_supports("report-status")) + if (server_supports("report-status-v2")) + status_report = 2; + else if (server_supports("report-status")) status_report = 1; if (server_supports("delete-refs")) allow_deleting_refs = 1; @@ -423,8 +489,10 @@ int send_pack(struct send_pack_args *args, use_push_options = push_options_supported && args->push_options; - if (status_report) + if (status_report == 1) strbuf_addstr(&cap_buf, " report-status"); + else if (status_report == 2) + strbuf_addstr(&cap_buf, " report-status-v2"); if (use_sideband) strbuf_addstr(&cap_buf, " side-band-64k"); if (quiet_supported && (args->quiet || !args->progress)) @@ -447,13 +515,6 @@ int send_pack(struct send_pack_args *args, if (ref->deletion && !allow_deleting_refs) ref->status = REF_STATUS_REJECT_NODELETE; - if (!args->dry_run) - advertise_shallow_grafts_buf(&req_buf); - - if (!args->dry_run && push_cert_nonce) - cmds_sent = generate_push_cert(&req_buf, remote_refs, args, - cap_buf.buf, push_cert_nonce); - /* * Clear the status for each ref and see if we need to send * the pack data. @@ -489,31 +550,35 @@ int send_pack(struct send_pack_args *args, ref->status = REF_STATUS_EXPECTING_REPORT; } + if (!args->dry_run) + advertise_shallow_grafts_buf(&req_buf); + /* * Finally, tell the other end! */ - for (ref = remote_refs; ref; ref = ref->next) { - char *old_hex, *new_hex; - - if (args->dry_run || push_cert_nonce) - continue; - - if (check_to_send_update(ref, args) < 0) - continue; - - old_hex = oid_to_hex(&ref->old_oid); - new_hex = oid_to_hex(&ref->new_oid); - if (!cmds_sent) { - packet_buf_write(&req_buf, - "%s %s %s%c%s", - old_hex, new_hex, ref->name, 0, - cap_buf.buf); - cmds_sent = 1; - } else { - packet_buf_write(&req_buf, "%s %s %s", - old_hex, new_hex, ref->name); + if (!args->dry_run && push_cert_nonce) + cmds_sent = generate_push_cert(&req_buf, remote_refs, args, + cap_buf.buf, push_cert_nonce); + else if (!args->dry_run) + for (ref = remote_refs; ref; ref = ref->next) { + char *old_hex, *new_hex; + + if (check_to_send_update(ref, args) < 0) + continue; + + old_hex = oid_to_hex(&ref->old_oid); + new_hex = oid_to_hex(&ref->new_oid); + if (!cmds_sent) { + packet_buf_write(&req_buf, + "%s %s %s%c%s", + old_hex, new_hex, ref->name, 0, + cap_buf.buf); + cmds_sent = 1; + } else { + packet_buf_write(&req_buf, "%s %s %s", + old_hex, new_hex, ref->name); + } } - } if (use_push_options) { struct string_list_item *item; diff --git a/submodule.c b/submodule.c index 6f8002fc9e..543b1123ae 100644 --- a/submodule.c +++ b/submodule.c @@ -438,13 +438,14 @@ void handle_ignore_submodules_arg(struct diff_options *diffopt, */ } -static int prepare_submodule_diff_summary(struct rev_info *rev, const char *path, - struct commit *left, struct commit *right, - struct commit_list *merge_bases) +static int prepare_submodule_diff_summary(struct repository *r, struct rev_info *rev, + const char *path, + struct commit *left, struct commit *right, + struct commit_list *merge_bases) { struct commit_list *list; - repo_init_revisions(the_repository, rev, NULL); + repo_init_revisions(r, rev, NULL); setup_revisions(0, NULL, rev, NULL); rev->left_right = 1; rev->first_parent_only = 1; @@ -632,7 +633,7 @@ void show_submodule_diff_summary(struct diff_options *o, const char *path, goto out; /* Treat revision walker failure the same as missing commits */ - if (prepare_submodule_diff_summary(&rev, path, left, right, merge_bases)) { + if (prepare_submodule_diff_summary(sub, &rev, path, left, right, merge_bases)) { diff_emit_submodule_error(o, "(revision walker failed)\n"); goto out; } @@ -840,9 +841,16 @@ static void collect_changed_submodules(struct repository *r, { struct rev_info rev; const struct commit *commit; + int save_warning; + struct setup_revision_opt s_r_opt = { + .assume_dashdash = 1, + }; + save_warning = warn_on_object_refname_ambiguity; + warn_on_object_refname_ambiguity = 0; repo_init_revisions(r, &rev, NULL); - setup_revisions(argv->nr, argv->v, &rev, NULL); + setup_revisions(argv->nr, argv->v, &rev, &s_r_opt); + warn_on_object_refname_ambiguity = save_warning; if (prepare_revision_walk(&rev)) die(_("revision walk setup failed")); diff --git a/t/chainlint.sed b/t/chainlint.sed index 70df40e34b..8a25c5b855 100644 --- a/t/chainlint.sed +++ b/t/chainlint.sed @@ -117,7 +117,7 @@ /^[ ]*!*[ ]*(..*)[ ]*[0-9]*[<>|&]/boneline # multi-line "(...\n...)" -/^[ ]*(/bsubshell +/^[ ]*(/bsubsh # innocuous line -- print it and advance to next line b @@ -130,11 +130,11 @@ b } b -:subshell +:subsh # bare "(" line? -- stash for later printing /^[ ]*([ ]*$/ { h - bnextline + bnextln } # "(..." line -- split off and stash "(", then process "..." as its own line x @@ -143,7 +143,7 @@ x s/(// bslurp -:nextline +:nextln N s/.*\n// @@ -151,10 +151,10 @@ s/.*\n// # incomplete line "...\" /\\$/bicmplte # multi-line quoted string "...\n..."? -/"/bdqstring +/"/bdqstr # multi-line quoted string '...\n...'? (but not contraction in string "it's") /'/{ - /"[^'"]*'[^'"]*"/!bsqstring + /"[^'"]*'[^'"]*"/!bsqstr } :folded # here-doc -- swallow it @@ -163,8 +163,8 @@ s/.*\n// # before closing ")", "done", "elsif", "else", or "fi" will need to be # re-visited to drop "suspect" marking since final line of those constructs # legitimately lacks "&&", so "suspect" mark must be removed -/^[ ]*#/bnextline -/^[ ]*$/bnextline +/^[ ]*#/bnextln +/^[ ]*$/bnextln # in-line comment -- strip it (but not "#" in a string, Bash ${#...} array # length, or Perforce "//depot/path#42" revision in filespec) /[ ]#/{ @@ -175,22 +175,22 @@ s/.*\n// # multi-line "case ... esac" /^[ ]*case[ ]..*[ ]in/bcase # multi-line "for ... done" or "while ... done" -/^[ ]*for[ ]..*[ ]in/bcontinue -/^[ ]*while[ ]/bcontinue -/^[ ]*do[ ]/bcontinue -/^[ ]*do[ ]*$/bcontinue -/;[ ]*do/bcontinue +/^[ ]*for[ ]..*[ ]in/bcont +/^[ ]*while[ ]/bcont +/^[ ]*do[ ]/bcont +/^[ ]*do[ ]*$/bcont +/;[ ]*do/bcont /^[ ]*done[ ]*&&[ ]*$/bdone /^[ ]*done[ ]*$/bdone /^[ ]*done[ ]*[<>|]/bdone /^[ ]*done[ ]*)/bdone -/||[ ]*exit[ ]/bcontinue -/||[ ]*exit[ ]*$/bcontinue +/||[ ]*exit[ ]/bcont +/||[ ]*exit[ ]*$/bcont # multi-line "if...elsif...else...fi" -/^[ ]*if[ ]/bcontinue -/^[ ]*then[ ]/bcontinue -/^[ ]*then[ ]*$/bcontinue -/;[ ]*then/bcontinue +/^[ ]*if[ ]/bcont +/^[ ]*then[ ]/bcont +/^[ ]*then[ ]*$/bcont +/;[ ]*then/bcont /^[ ]*elif[ ]/belse /^[ ]*elif[ ]*$/belse /^[ ]*else[ ]/belse @@ -234,10 +234,10 @@ s/.*\n// } } # line ends with pipe "...|" -- valid; not missing "&&" -/|[ ]*$/bcontinue +/|[ ]*$/bcont # missing end-of-line "&&" -- mark suspect /&&[ ]*$/!s/^/?!AMP?!/ -:continue +:cont # retrieve and print previous line x n @@ -250,7 +250,7 @@ s/\\\n// bslurp # check for multi-line double-quoted string "...\n..." -- fold to one line -:dqstring +:dqstr # remove all quote pairs s/"\([^"]*\)"/@!\1@!/g # done if no dangling quote @@ -258,13 +258,13 @@ s/"\([^"]*\)"/@!\1@!/g # otherwise, slurp next line and try again N s/\n// -bdqstring +bdqstr :dqdone s/@!/"/g bfolded # check for multi-line single-quoted string '...\n...' -- fold to one line -:sqstring +:sqstr # remove all quote pairs s/'\([^']*\)'/@!\1@!/g # done if no dangling quote @@ -272,7 +272,7 @@ s/'\([^']*\)'/@!\1@!/g # otherwise, slurp next line and try again N s/\n// -bsqstring +bsqstr :sqdone s/@!/'/g bfolded @@ -282,11 +282,11 @@ bfolded :heredoc s/^\(.*\)<<[ ]*[-\\'"]*\([A-Za-z0-9_][A-Za-z0-9_]*\)['"]*/<\2>\1<</ s/[ ]*<<// -:heredsub +:hdocsub N /^<\([^>]*\)>.*\n[ ]*\1[ ]*$/!{ s/\n.*$// - bheredsub + bhdocsub } s/^<[^>]*>// s/\n.*$// @@ -305,7 +305,7 @@ bcase x s/?!AMP?!// x -bcontinue +bcont # found "done" closing for-loop or while-loop, or "fi" closing if-then -- drop # "suspect" from final contained line since that line legitimately lacks "&&" @@ -321,10 +321,10 @@ bchkchn # found nested multi-line "(...\n...)" -- pass through untouched :nest x -:nstslurp +:nstslrp n # closing ")" on own line -- stop nested slurp -/^[ ]*)/bnstclose +/^[ ]*)/bnstcl # comment -- not closing ")" if in comment /^[ ]*#/bnstcnt # "$((...))" -- arithmetic expansion; not closing ")" @@ -332,11 +332,11 @@ n # "$(...)" -- command substitution; not closing ")" /\$([^)][^)]*)[^)]*$/bnstcnt # closing "...)" -- stop nested slurp -/)/bnstclose +/)/bnstcl :nstcnt x -bnstslurp -:nstclose +bnstslrp +:nstcl s/^/>>/ # is it "))" which closes nested and parent subshells? /)[ ]*)/bslurp diff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c new file mode 100644 index 0000000000..42164d9898 --- /dev/null +++ b/t/helper/test-proc-receive.c @@ -0,0 +1,176 @@ +#include "cache.h" +#include "connect.h" +#include "parse-options.h" +#include "pkt-line.h" +#include "sigchain.h" +#include "test-tool.h" + +static const char *proc_receive_usage[] = { + "test-tool proc-receive [<options>...]", + NULL +}; + +static int die_version; +static int die_readline; +static int no_push_options; +static int use_atomic; +static int use_push_options; +static int verbose; +static int version = 1; +static struct string_list returns = STRING_LIST_INIT_NODUP; + +struct command { + struct command *next; + const char *error_string; + unsigned int skip_update:1, + did_not_exist:1; + int index; + struct object_id old_oid; + struct object_id new_oid; + char ref_name[FLEX_ARRAY]; /* more */ +}; + +static void proc_receive_verison(struct packet_reader *reader) { + int server_version = 0; + + for (;;) { + int linelen; + + if (packet_reader_read(reader) != PACKET_READ_NORMAL) + break; + + if (reader->pktlen > 8 && starts_with(reader->line, "version=")) { + server_version = atoi(reader->line+8); + linelen = strlen(reader->line); + if (linelen < reader->pktlen) { + const char *feature_list = reader->line + linelen + 1; + if (parse_feature_request(feature_list, "atomic")) + use_atomic= 1; + if (parse_feature_request(feature_list, "push-options")) + use_push_options = 1; + } + } + } + + if (server_version != 1 || die_version) + die("bad protocol version: %d", server_version); + + packet_write_fmt(1, "version=%d%c%s\n", + version, '\0', + use_push_options && !no_push_options ? "push-options": ""); + packet_flush(1); +} + +static void proc_receive_read_commands(struct packet_reader *reader, + struct command **commands) +{ + struct command **tail = commands; + + for (;;) { + struct object_id old_oid, new_oid; + struct command *cmd; + const char *refname; + const char *p; + + if (packet_reader_read(reader) != PACKET_READ_NORMAL) + break; + + if (parse_oid_hex(reader->line, &old_oid, &p) || + *p++ != ' ' || + parse_oid_hex(p, &new_oid, &p) || + *p++ != ' ' || + die_readline) + die("protocol error: expected 'old new ref', got '%s'", + reader->line); + refname = p; + FLEX_ALLOC_STR(cmd, ref_name, refname); + oidcpy(&cmd->old_oid, &old_oid); + oidcpy(&cmd->new_oid, &new_oid); + + *tail = cmd; + tail = &cmd->next; + } +} + +static void proc_receive_read_push_options(struct packet_reader *reader, + struct string_list *options) +{ + + if (no_push_options || !use_push_options) + return; + + while (1) { + if (packet_reader_read(reader) != PACKET_READ_NORMAL) + break; + + string_list_append(options, reader->line); + } +} + +int cmd__proc_receive(int argc, const char **argv) +{ + int nongit_ok = 0; + struct packet_reader reader; + struct command *commands = NULL; + struct string_list push_options = STRING_LIST_INIT_DUP; + struct string_list_item *item; + struct option options[] = { + OPT_BOOL(0, "no-push-options", &no_push_options, + "disable push options"), + OPT_BOOL(0, "die-version", &die_version, + "die during version negotiation"), + OPT_BOOL(0, "die-readline", &die_readline, + "die when readline"), + OPT_STRING_LIST('r', "return", &returns, "old/new/ref/status/msg", + "return of results"), + OPT__VERBOSE(&verbose, "be verbose"), + OPT_INTEGER('V', "version", &version, + "use this protocol version number"), + OPT_END() + }; + + setup_git_directory_gently(&nongit_ok); + + argc = parse_options(argc, argv, "test-tools", options, proc_receive_usage, 0); + if (argc > 0) + usage_msg_opt("Too many arguments.", proc_receive_usage, options); + packet_reader_init(&reader, 0, NULL, 0, + PACKET_READ_CHOMP_NEWLINE | + PACKET_READ_DIE_ON_ERR_PACKET); + + sigchain_push(SIGPIPE, SIG_IGN); + proc_receive_verison(&reader); + proc_receive_read_commands(&reader, &commands); + proc_receive_read_push_options(&reader, &push_options); + + if (verbose) { + struct command *cmd; + + if (use_push_options || use_atomic) + fprintf(stderr, "proc-receive:%s%s\n", + use_atomic? " atomic": "", + use_push_options ? " push_options": ""); + + for (cmd = commands; cmd; cmd = cmd->next) + fprintf(stderr, "proc-receive< %s %s %s\n", + oid_to_hex(&cmd->old_oid), + oid_to_hex(&cmd->new_oid), + cmd->ref_name); + + if (push_options.nr > 0) + for_each_string_list_item(item, &push_options) + fprintf(stderr, "proc-receive< %s\n", item->string); + + if (returns.nr) + for_each_string_list_item(item, &returns) + fprintf(stderr, "proc-receive> %s\n", item->string); + } + + if (returns.nr) + for_each_string_list_item(item, &returns) + packet_write_fmt(1, "%s\n", item->string); + packet_flush(1); + sigchain_pop(SIGPIPE); + + return 0; +} diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c index 590b2efca7..a0d3966b29 100644 --- a/t/helper/test-tool.c +++ b/t/helper/test-tool.c @@ -46,6 +46,7 @@ static struct test_cmd cmds[] = { { "path-utils", cmd__path_utils }, { "pkt-line", cmd__pkt_line }, { "prio-queue", cmd__prio_queue }, + { "proc-receive", cmd__proc_receive}, { "progress", cmd__progress }, { "reach", cmd__reach }, { "read-cache", cmd__read_cache }, diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h index ddc8e990e9..07034d3f38 100644 --- a/t/helper/test-tool.h +++ b/t/helper/test-tool.h @@ -35,6 +35,7 @@ int cmd__parse_pathspec_file(int argc, const char** argv); int cmd__path_utils(int argc, const char **argv); int cmd__pkt_line(int argc, const char **argv); int cmd__prio_queue(int argc, const char **argv); +int cmd__proc_receive(int argc, const char **argv); int cmd__progress(int argc, const char **argv); int cmd__reach(int argc, const char **argv); int cmd__read_cache(int argc, const char **argv); diff --git a/t/perf/p5303-many-packs.sh b/t/perf/p5303-many-packs.sh index 7ee791669a..f4c2ab0584 100755 --- a/t/perf/p5303-many-packs.sh +++ b/t/perf/p5303-many-packs.sh @@ -73,6 +73,10 @@ do git rev-list --objects --all >/dev/null ' + test_perf "abbrev-commit ($nr_packs)" ' + git rev-list --abbrev-commit HEAD >/dev/null + ' + # This simulates the interesting part of the repack, which is the # actual pack generation, without smudging the on-disk setup # between trials. diff --git a/t/t1300-config.sh b/t/t1300-config.sh index 97ebfe1f9d..825d9a184f 100755 --- a/t/t1300-config.sh +++ b/t/t1300-config.sh @@ -1836,11 +1836,14 @@ test_expect_success '--show-scope with --show-origin' ' test_cmp expect output ' -test_expect_success '--local requires a repo' ' - # we expect 128 to ensure that we do not simply - # fail to find anything and return code "1" - test_expect_code 128 nongit git config --local foo.bar -' +for opt in --local --worktree +do + test_expect_success "$opt requires a repo" ' + # we expect 128 to ensure that we do not simply + # fail to find anything and return code "1" + test_expect_code 128 nongit git config $opt foo.bar + ' +done cat >.git/config <<-\EOF && [core] diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 4c0734157b..c24c6632ee 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -1287,6 +1287,7 @@ test_expect_success 'detect typo in branch name when using --edit-description' ' ' test_expect_success 'refuse --edit-description on unborn branch for now' ' + test_when_finished "git checkout master" && write_script editor <<-\EOF && echo "New contents" >"$1" EOF @@ -1298,10 +1299,6 @@ test_expect_success '--merged catches invalid object names' ' test_must_fail git branch --merged 0000000000000000000000000000000000000000 ' -test_expect_success '--merged is incompatible with --no-merged' ' - test_must_fail git branch --merged HEAD --no-merged HEAD -' - test_expect_success '--list during rebase' ' test_when_finished "reset_rebase" && git checkout master && diff --git a/t/t3201-branch-contains.sh b/t/t3201-branch-contains.sh index 40251c9f8f..efea5c4971 100755 --- a/t/t3201-branch-contains.sh +++ b/t/t3201-branch-contains.sh @@ -171,6 +171,69 @@ test_expect_success 'Assert that --contains only works on commits, not trees & b test_must_fail git branch --no-contains $blob ' +test_expect_success 'multiple branch --contains' ' + git checkout -b side2 master && + >feature && + git add feature && + git commit -m "add feature" && + git checkout -b next master && + git merge side && + git branch --contains side --contains side2 >actual && + cat >expect <<-\EOF && + * next + side + side2 + EOF + test_cmp expect actual +' + +test_expect_success 'multiple branch --merged' ' + git branch --merged next --merged master >actual && + cat >expect <<-\EOF && + master + * next + side + EOF + test_cmp expect actual +' + +test_expect_success 'multiple branch --no-contains' ' + git branch --no-contains side --no-contains side2 >actual && + cat >expect <<-\EOF && + master + EOF + test_cmp expect actual +' + +test_expect_success 'multiple branch --no-merged' ' + git branch --no-merged next --no-merged master >actual && + cat >expect <<-\EOF && + side2 + EOF + test_cmp expect actual +' + +test_expect_success 'branch --contains combined with --no-contains' ' + git checkout -b seen master && + git merge side && + git merge side2 && + git branch --contains side --no-contains side2 >actual && + cat >expect <<-\EOF && + next + side + EOF + test_cmp expect actual +' + +test_expect_success 'branch --merged combined with --no-merged' ' + git branch --merged seen --no-merged next >actual && + cat >expect <<-\EOF && + * seen + side2 + EOF + test_cmp expect actual +' + # We want to set up a case where the walk for the tracking info # of one branch crosses the tip of another branch (and make sure # that the latter walk does not mess up our flag to see if it was @@ -200,15 +263,4 @@ test_expect_success 'branch --merged with --verbose' ' test_i18ncmp expect actual ' -test_expect_success 'branch --contains combined with --no-contains' ' - git branch --contains zzz --no-contains topic >actual && - cat >expect <<-\EOF && - master - side - zzz - EOF - test_cmp expect actual - -' - test_done diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index fb73a847cb..ca04fac417 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -569,12 +569,20 @@ test_expect_success 'patch mode ignores unmerged entries' ' diff_cmp expected diff ' +test_expect_success 'index is refreshed after applying patch' ' + git reset --hard && + echo content >test && + printf y | git add -p && + git diff-files --exit-code +' + test_expect_success 'diffs can be colorized' ' git reset --hard && echo content >test && printf y >y && force_color git add -p >output 2>&1 <y && + git diff-files --exit-code && # We do not want to depend on the exact coloring scheme # git uses for diffs, so just check that we saw some kind of color. @@ -814,6 +822,44 @@ test_expect_success 'checkout -p works with pathological context lines' ' test_cmp expect a ' +# This should be called from a subshell as it sets a temporary editor +setup_new_file() { + write_script new-file-editor.sh <<-\EOF && + sed /^#/d "$1" >patch && + sed /^+c/d patch >"$1" + EOF + test_set_editor "$(pwd)/new-file-editor.sh" && + test_write_lines a b c d e f >new-file && + test_write_lines a b d e f >new-file-expect && + test_write_lines "@@ -0,0 +1,6 @@" +a +b +c +d +e +f >patch-expect +} + +test_expect_success 'add -N followed by add -p patch editing' ' + git reset --hard && + ( + setup_new_file && + git add -N new-file && + test_write_lines e n q | git add -p && + git cat-file blob :new-file >actual && + test_cmp new-file-expect actual && + test_cmp patch-expect patch + ) +' + +test_expect_success 'checkout -p patch editing of added file' ' + git reset --hard && + ( + setup_new_file && + git add new-file && + git commit -m "add new file" && + git rm new-file && + git commit -m "remove new file" && + test_write_lines e n q | git checkout -p HEAD^ && + test_cmp new-file-expect new-file && + test_cmp patch-expect patch + ) +' + test_expect_success 'show help from add--helper' ' git reset --hard && cat >expect <<-EOF && diff --git a/t/t5411-proc-receive-hook.sh b/t/t5411-proc-receive-hook.sh new file mode 100755 index 0000000000..746487286f --- /dev/null +++ b/t/t5411-proc-receive-hook.sh @@ -0,0 +1,117 @@ +#!/bin/sh +# +# Copyright (c) 2020 Jiang Xin +# + +test_description='Test proc-receive hook' + +. ./test-lib.sh + +. "$TEST_DIRECTORY"/t5411/common-functions.sh + +setup_upstream_and_workbench () { + # Refs of upstream : master(A) + # Refs of workbench: master(A) tags/v123 + test_expect_success "setup upstream and workbench" ' + rm -rf upstream.git && + rm -rf workbench && + git init --bare upstream.git && + git init workbench && + create_commits_in workbench A B && + ( + cd workbench && + # Try to make a stable fixed width for abbreviated commit ID, + # this fixed-width oid will be replaced with "<OID>". + git config core.abbrev 7 && + git tag -m "v123" v123 $A && + git remote add origin ../upstream.git && + git push origin master && + git update-ref refs/heads/master $A $B && + git -C ../upstream.git update-ref \ + refs/heads/master $A $B + ) && + TAG=$(git -C workbench rev-parse v123) && + + # setup pre-receive hook + write_script upstream.git/hooks/pre-receive <<-\EOF && + exec >&2 + echo "# pre-receive hook" + while read old new ref + do + echo "pre-receive< $old $new $ref" + done + EOF + + # setup post-receive hook + write_script upstream.git/hooks/post-receive <<-\EOF && + exec >&2 + echo "# post-receive hook" + while read old new ref + do + echo "post-receive< $old $new $ref" + done + EOF + + upstream=upstream.git + ' +} + +run_proc_receive_hook_test() { + case $1 in + http) + PROTOCOL="HTTP protocol" + URL_PREFIX="http://.*" + ;; + local) + PROTOCOL="builtin protocol" + URL_PREFIX="\.\." + ;; + esac + + # Include test cases for both file and HTTP protocol + for t in "$TEST_DIRECTORY"/t5411/test-*.sh + do + . "$t" + done +} + +# Initialize the upstream repository and local workbench. +setup_upstream_and_workbench + +# Load test cases that only need to be executed once. +for t in "$TEST_DIRECTORY"/t5411/once-*.sh +do + . "$t" +done + +# Initialize the upstream repository and local workbench. +setup_upstream_and_workbench + +# Run test cases for 'proc-receive' hook on local file protocol. +run_proc_receive_hook_test local + +ROOT_PATH="$PWD" +. "$TEST_DIRECTORY"/lib-gpg.sh +. "$TEST_DIRECTORY"/lib-httpd.sh +. "$TEST_DIRECTORY"/lib-terminal.sh +start_httpd + +# Re-initialize the upstream repository and local workbench. +setup_upstream_and_workbench + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +test_expect_success "setup for HTTP protocol" ' + git -C upstream.git config http.receivepack true && + upstream="$HTTPD_DOCUMENT_ROOT_PATH/upstream.git" && + mv upstream.git "$upstream" && + git -C workbench remote set-url origin "$HTTPD_URL/auth-push/smart/upstream.git" && + set_askpass user@host pass@host +' + +setup_askpass_helper + +# Run test cases for 'proc-receive' hook on HTTP protocol. +run_proc_receive_hook_test http + +test_done diff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh new file mode 100644 index 0000000000..6580bebd8e --- /dev/null +++ b/t/t5411/common-functions.sh @@ -0,0 +1,56 @@ +# Create commits in <repo> and assign each commit's oid to shell variables +# given in the arguments (A, B, and C). E.g.: +# +# create_commits_in <repo> A B C +# +# NOTE: Never calling this function from a subshell since variable +# assignments will disappear when subshell exits. +create_commits_in () { + repo="$1" && + if ! parent=$(git -C "$repo" rev-parse HEAD^{} --) + then + parent= + fi && + T=$(git -C "$repo" write-tree) && + shift && + while test $# -gt 0 + do + name=$1 && + test_tick && + if test -z "$parent" + then + oid=$(echo $name | git -C "$repo" commit-tree $T) + else + oid=$(echo $name | git -C "$repo" commit-tree -p $parent $T) + fi && + eval $name=$oid && + parent=$oid && + shift || + return 1 + done && + git -C "$repo" update-ref refs/heads/master $oid +} + +# Format the output of git-push, git-show-ref and other commands to make a +# user-friendly and stable text. We can easily prepare the expect text +# without having to worry about future changes of the commit ID and spaces +# of the output. Single quotes are replaced with double quotes, because +# it is boring to prepare unquoted single quotes in expect text. We also +# remove some locale error messages, which break test if we turn on +# `GIT_TEST_GETTEXT_POISON=true` in order to test unintentional translations +# on plumbing commands. +make_user_friendly_and_stable_output () { + sed \ + -e "s/ *\$//" \ + -e "s/ */ /g" \ + -e "s/'/\"/g" \ + -e "s/ / /g" \ + -e "s/$A/<COMMIT-A>/g" \ + -e "s/$B/<COMMIT-B>/g" \ + -e "s/$TAG/<TAG-v123>/g" \ + -e "s/$ZERO_OID/<ZERO-OID>/g" \ + -e "s/$(echo $A | cut -c1-7)[0-9a-f]*/<OID-A>/g" \ + -e "s/$(echo $B | cut -c1-7)[0-9a-f]*/<OID-B>/g" \ + -e "s#To $URL_PREFIX/upstream.git#To <URL/of/upstream.git>#" \ + -e "/^error: / d" +} diff --git a/t/t5411/once-0010-report-status-v1.sh b/t/t5411/once-0010-report-status-v1.sh new file mode 100644 index 0000000000..dc2cf4a522 --- /dev/null +++ b/t/t5411/once-0010-report-status-v1.sh @@ -0,0 +1,94 @@ +test_expect_success "setup receive.procReceiveRefs" ' + git -C "$upstream" config --add receive.procReceiveRefs refs/for +' + +test_expect_success "setup proc-receive hook" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/for/master/topic1" \ + -r "option fall-through" \ + -r "ok refs/for/master/topic2" \ + -r "option refname refs/for/changes/23/123/1" \ + -r "option new-oid $A" \ + -r "ok refs/for/master/topic2" \ + -r "option refname refs/for/changes/24/124/2" \ + -r "option old-oid $B" \ + -r "option new-oid $A" \ + -r "option forced-update" \ + -r "ng refs/for/next/topic target branch not exist" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : (B) refs/for/master/topic1(A) foo(A) refs/for/next/topic(A) refs/for/master/topic2(A) +test_expect_success "proc-receive: report status v1" ' + { + if test -z "$GIT_DEFAULT_HASH" || test "$GIT_DEFAULT_HASH" = "sha1" + then + printf "%s %s refs/heads/master\0report-status\n" \ + $A $B | packetize + else + printf "%s %s refs/heads/master\0report-status object-format=$GIT_DEFAULT_HASH\n" \ + $A $B | packetize + fi && + printf "%s %s refs/for/master/topic1\n" \ + $ZERO_OID $A | packetize && + printf "%s %s refs/heads/foo\n" \ + $ZERO_OID $A | packetize && + printf "%s %s refs/for/next/topic\n" \ + $ZERO_OID $A | packetize && + printf "%s %s refs/for/master/topic2\n" \ + $ZERO_OID $A | packetize && + printf 0000 && + printf "" | git -C "$upstream" pack-objects --stdout + } | git receive-pack "$upstream" --stateless-rpc \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + # pre-receive hook + pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master + pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1 + pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo + pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic + pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2 + # proc-receive hook + proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1 + proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic + proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic2 + proc-receive> ok refs/for/master/topic1 + proc-receive> option fall-through + proc-receive> ok refs/for/master/topic2 + proc-receive> option refname refs/for/changes/23/123/1 + proc-receive> option new-oid <COMMIT-A> + proc-receive> ok refs/for/master/topic2 + proc-receive> option refname refs/for/changes/24/124/2 + proc-receive> option old-oid <COMMIT-B> + proc-receive> option new-oid <COMMIT-A> + proc-receive> option forced-update + proc-receive> ng refs/for/next/topic target branch not exist + 000eunpack ok + 0019ok refs/heads/master + 001eok refs/for/master/topic1 + 0016ok refs/heads/foo + 0033ng refs/for/next/topic target branch not exist + 001eok refs/for/master/topic2 + 0000# post-receive hook + post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master + post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic1 + post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo + post-receive< <ZERO-OID> <COMMIT-A> refs/for/changes/23/123/1 + post-receive< <COMMIT-B> <COMMIT-A> refs/for/changes/24/124/2 + EOF + test_cmp expect actual && + + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/for/master/topic1 + <COMMIT-A> refs/heads/foo + <COMMIT-B> refs/heads/master + EOF + test_cmp expect actual +' diff --git a/t/t5411/test-0000-standard-git-push.sh b/t/t5411/test-0000-standard-git-push.sh new file mode 100644 index 0000000000..e206587348 --- /dev/null +++ b/t/t5411/test-0000-standard-git-push.sh @@ -0,0 +1,143 @@ +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git-push : master(B) next(A) +test_expect_success "git-push ($PROTOCOL)" ' + git -C workbench push origin \ + $B:refs/heads/master \ + HEAD:refs/heads/next \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next + remote: # post-receive hook + remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next + To <URL/of/upstream.git> + <OID-A>..<OID-B> <COMMIT-B> -> master + * [new branch] HEAD -> next + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-B> refs/heads/master + <COMMIT-A> refs/heads/next + EOF + test_cmp expect actual +' + +# Refs of upstream : master(B) next(A) +# Refs of workbench: master(A) tags/v123 +# git-push --atomic: master(A) next(B) +test_expect_success "git-push --atomic ($PROTOCOL)" ' + test_must_fail git -C workbench push --atomic origin \ + master \ + $B:refs/heads/next \ + >out 2>&1 && + make_user_friendly_and_stable_output <out | + sed -n \ + -e "/^To / { s/ */ /g; p; }" \ + -e "/^ ! / { s/ */ /g; p; }" \ + >actual && + cat >expect <<-EOF && + To <URL/of/upstream.git> + ! [rejected] master -> master (non-fast-forward) + ! [rejected] <COMMIT-B> -> next (atomic push failed) + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-B> refs/heads/master + <COMMIT-A> refs/heads/next + EOF + test_cmp expect actual +' + +# Refs of upstream : master(B) next(A) +# Refs of workbench: master(A) tags/v123 +# git-push : master(A) next(B) +test_expect_success "non-fast-forward git-push ($PROTOCOL)" ' + test_must_fail git \ + -C workbench \ + -c advice.pushUpdateRejected=false \ + push origin \ + master \ + $B:refs/heads/next \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/next + remote: # post-receive hook + remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/next + To <URL/of/upstream.git> + <OID-A>..<OID-B> <COMMIT-B> -> next + ! [rejected] master -> master (non-fast-forward) + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-B> refs/heads/master + <COMMIT-B> refs/heads/next + EOF + test_cmp expect actual +' + +# Refs of upstream : master(B) next(B) +# Refs of workbench: master(A) tags/v123 +# git-push -f : master(A) NULL tags/v123 refs/review/master/topic(A) a/b/c(A) +test_expect_success "git-push -f ($PROTOCOL)" ' + git -C workbench push -f origin \ + refs/tags/v123 \ + :refs/heads/next \ + master \ + master:refs/review/master/topic \ + HEAD:refs/heads/a/b/c \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master + remote: pre-receive< <COMMIT-B> <ZERO-OID> refs/heads/next + remote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123 + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c + remote: # post-receive hook + remote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master + remote: post-receive< <COMMIT-B> <ZERO-OID> refs/heads/next + remote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123 + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c + To <URL/of/upstream.git> + + <OID-B>...<OID-A> master -> master (forced update) + - [deleted] next + * [new tag] v123 -> v123 + * [new reference] master -> refs/review/master/topic + * [new branch] HEAD -> a/b/c + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/a/b/c + <COMMIT-A> refs/heads/master + <COMMIT-A> refs/review/master/topic + <TAG-v123> refs/tags/v123 + EOF + test_cmp expect actual +' + +# Refs of upstream : master(A) tags/v123 refs/review/master/topic(A) a/b/c(A) +# Refs of workbench: master(A) tags/v123 +test_expect_success "cleanup ($PROTOCOL)" ' + ( + cd "$upstream" && + git update-ref -d refs/review/master/topic && + git update-ref -d refs/tags/v123 && + git update-ref -d refs/heads/a/b/c + ) +' diff --git a/t/t5411/test-0001-standard-git-push--porcelain.sh b/t/t5411/test-0001-standard-git-push--porcelain.sh new file mode 100644 index 0000000000..48f6fcc846 --- /dev/null +++ b/t/t5411/test-0001-standard-git-push--porcelain.sh @@ -0,0 +1,147 @@ +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git-push : master(B) next(A) +test_expect_success "git-push ($PROTOCOL/porcelain)" ' + git -C workbench push --porcelain origin \ + $B:refs/heads/master \ + HEAD:refs/heads/next \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next + remote: # post-receive hook + remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next + To <URL/of/upstream.git> + <COMMIT-B>:refs/heads/master <OID-A>..<OID-B> + * HEAD:refs/heads/next [new branch] + Done + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-B> refs/heads/master + <COMMIT-A> refs/heads/next + EOF + test_cmp expect actual +' + +# Refs of upstream : master(B) next(A) +# Refs of workbench: master(A) tags/v123 +# git-push --atomic: master(A) next(B) +test_expect_success "git-push --atomic ($PROTOCOL/porcelain)" ' + test_must_fail git -C workbench push --atomic --porcelain origin \ + master \ + $B:refs/heads/next \ + >out 2>&1 && + make_user_friendly_and_stable_output <out | + sed -n \ + -e "s/^# GETTEXT POISON #//" \ + -e "/^To / { s/ */ /g; p; }" \ + -e "/^! / { s/ */ /g; p; }" \ + >actual && + cat >expect <<-EOF && + To <URL/of/upstream.git> + ! refs/heads/master:refs/heads/master [rejected] (non-fast-forward) + ! <COMMIT-B>:refs/heads/next [rejected] (atomic push failed) + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-B> refs/heads/master + <COMMIT-A> refs/heads/next + EOF + test_cmp expect actual +' + +# Refs of upstream : master(B) next(A) +# Refs of workbench: master(A) tags/v123 +# git-push : master(A) next(B) +test_expect_success "non-fast-forward git-push ($PROTOCOL/porcelain)" ' + test_must_fail git \ + -C workbench \ + -c advice.pushUpdateRejected=false \ + push --porcelain origin \ + master \ + $B:refs/heads/next \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/next + remote: # post-receive hook + remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/next + To <URL/of/upstream.git> + <COMMIT-B>:refs/heads/next <OID-A>..<OID-B> + ! refs/heads/master:refs/heads/master [rejected] (non-fast-forward) + Done + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-B> refs/heads/master + <COMMIT-B> refs/heads/next + EOF + test_cmp expect actual +' + +# Refs of upstream : master(B) next(B) +# Refs of workbench: master(A) tags/v123 +# git-push -f : master(A) NULL tags/v123 refs/review/master/topic(A) a/b/c(A) +test_expect_success "git-push -f ($PROTOCOL/porcelain)" ' + git -C workbench push --porcelain -f origin \ + refs/tags/v123 \ + :refs/heads/next \ + master \ + master:refs/review/master/topic \ + HEAD:refs/heads/a/b/c \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master + remote: pre-receive< <COMMIT-B> <ZERO-OID> refs/heads/next + remote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123 + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c + remote: # post-receive hook + remote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master + remote: post-receive< <COMMIT-B> <ZERO-OID> refs/heads/next + remote: post-receive< <ZERO-OID> <TAG-v123> refs/tags/v123 + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/review/master/topic + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/a/b/c + To <URL/of/upstream.git> + + refs/heads/master:refs/heads/master <OID-B>...<OID-A> (forced update) + - :refs/heads/next [deleted] + * refs/tags/v123:refs/tags/v123 [new tag] + * refs/heads/master:refs/review/master/topic [new reference] + * HEAD:refs/heads/a/b/c [new branch] + Done + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/a/b/c + <COMMIT-A> refs/heads/master + <COMMIT-A> refs/review/master/topic + <TAG-v123> refs/tags/v123 + EOF + test_cmp expect actual +' + +# Refs of upstream : master(A) tags/v123 refs/review/master/topic(A) a/b/c(A) +# Refs of workbench: master(A) tags/v123 +test_expect_success "cleanup ($PROTOCOL/porcelain)" ' + ( + cd "$upstream" && + git update-ref -d refs/review/master/topic && + git update-ref -d refs/tags/v123 && + git update-ref -d refs/heads/a/b/c + ) +' diff --git a/t/t5411/test-0002-pre-receive-declined.sh b/t/t5411/test-0002-pre-receive-declined.sh new file mode 100644 index 0000000000..c246f7e68e --- /dev/null +++ b/t/t5411/test-0002-pre-receive-declined.sh @@ -0,0 +1,33 @@ +test_expect_success "setup pre-receive hook ($PROTOCOL)" ' + mv "$upstream/hooks/pre-receive" "$upstream/hooks/pre-receive.ok" && + write_script "$upstream/hooks/pre-receive" <<-EOF + exit 1 + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git-push : master(B) next(A) +test_expect_success "git-push is declined ($PROTOCOL)" ' + test_must_fail git -C workbench push origin \ + $B:refs/heads/master \ + HEAD:refs/heads/next \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + To <URL/of/upstream.git> + ! [remote rejected] <COMMIT-B> -> master (pre-receive hook declined) + ! [remote rejected] HEAD -> next (pre-receive hook declined) + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' + +test_expect_success "cleanup ($PROTOCOL)" ' + mv "$upstream/hooks/pre-receive.ok" "$upstream/hooks/pre-receive" +' diff --git a/t/t5411/test-0003-pre-receive-declined--porcelain.sh b/t/t5411/test-0003-pre-receive-declined--porcelain.sh new file mode 100644 index 0000000000..b14894de81 --- /dev/null +++ b/t/t5411/test-0003-pre-receive-declined--porcelain.sh @@ -0,0 +1,34 @@ +test_expect_success "setup pre-receive hook ($PROTOCOL/porcelain)" ' + mv "$upstream/hooks/pre-receive" "$upstream/hooks/pre-receive.ok" && + write_script "$upstream/hooks/pre-receive" <<-EOF + exit 1 + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git-push : master(B) next(A) +test_expect_success "git-push is declined ($PROTOCOL/porcelain)" ' + test_must_fail git -C workbench push --porcelain origin \ + $B:refs/heads/master \ + HEAD:refs/heads/next \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + To <URL/of/upstream.git> + ! <COMMIT-B>:refs/heads/master [remote rejected] (pre-receive hook declined) + ! HEAD:refs/heads/next [remote rejected] (pre-receive hook declined) + Done + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' + +test_expect_success "cleanup ($PROTOCOL/porcelain)" ' + mv "$upstream/hooks/pre-receive.ok" "$upstream/hooks/pre-receive" +' diff --git a/t/t5411/test-0010-proc-receive-settings.sh b/t/t5411/test-0010-proc-receive-settings.sh new file mode 100644 index 0000000000..a36809927b --- /dev/null +++ b/t/t5411/test-0010-proc-receive-settings.sh @@ -0,0 +1,7 @@ +test_expect_success "add two receive.procReceiveRefs settings" ' + ( + cd "$upstream" && + git config --add receive.procReceiveRefs refs/for && + git config --add receive.procReceiveRefs refs/review/ + ) +' diff --git a/t/t5411/test-0011-no-hook-error.sh b/t/t5411/test-0011-no-hook-error.sh new file mode 100644 index 0000000000..bb6ec92a92 --- /dev/null +++ b/t/t5411/test-0011-no-hook-error.sh @@ -0,0 +1,64 @@ +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : next(A) refs/for/master/topic(A) +test_expect_success "proc-receive: no hook, fail to push special ref ($PROTOCOL)" ' + test_must_fail git -C workbench push origin \ + HEAD:next \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: error: cannot find hook "proc-receive" + remote: # post-receive hook + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next + To <URL/of/upstream.git> + * [new branch] HEAD -> next + ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook) + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + <COMMIT-A> refs/heads/next + EOF + test_cmp expect actual +' + +# Refs of upstream : master(A) next(A) +# Refs of workbench: master(A) tags/v123 +test_expect_success "cleanup ($PROTOCOL)" ' + git -C "$upstream" update-ref -d refs/heads/next +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push --atomic: (B) next(A) refs/for/master/topic(A) +test_expect_success "proc-receive: no hook, all failed for atomic push ($PROTOCOL)" ' + test_must_fail git -C workbench push --atomic origin \ + $B:master \ + HEAD:next \ + HEAD:refs/for/master/topic >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: error: cannot find hook "proc-receive" + To <URL/of/upstream.git> + ! [remote rejected] <COMMIT-B> -> master (fail to run proc-receive hook) + ! [remote rejected] HEAD -> next (fail to run proc-receive hook) + ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook) + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' diff --git a/t/t5411/test-0012-no-hook-error--porcelain.sh b/t/t5411/test-0012-no-hook-error--porcelain.sh new file mode 100644 index 0000000000..4814f74dc2 --- /dev/null +++ b/t/t5411/test-0012-no-hook-error--porcelain.sh @@ -0,0 +1,66 @@ +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : next(A) refs/for/master/topic(A) +test_expect_success "proc-receive: no hook, fail to push special ref ($PROTOCOL/porcelain)" ' + test_must_fail git -C workbench push --porcelain origin \ + HEAD:next \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: error: cannot find hook "proc-receive" + remote: # post-receive hook + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next + To <URL/of/upstream.git> + * HEAD:refs/heads/next [new branch] + ! HEAD:refs/for/master/topic [remote rejected] (fail to run proc-receive hook) + Done + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + <COMMIT-A> refs/heads/next + EOF + test_cmp expect actual +' + +# Refs of upstream : master(A) next(A) +# Refs of workbench: master(A) tags/v123 +test_expect_success "cleanup ($PROTOCOL/porcelain)" ' + git -C "$upstream" update-ref -d refs/heads/next +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push --atomic: (B) next(A) refs/for/master/topic(A) +test_expect_success "proc-receive: no hook, all failed for atomic push ($PROTOCOL/porcelain)" ' + test_must_fail git -C workbench push --porcelain --atomic origin \ + $B:master \ + HEAD:next \ + HEAD:refs/for/master/topic >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: error: cannot find hook "proc-receive" + To <URL/of/upstream.git> + ! <COMMIT-B>:refs/heads/master [remote rejected] (fail to run proc-receive hook) + ! HEAD:refs/heads/next [remote rejected] (fail to run proc-receive hook) + ! HEAD:refs/for/master/topic [remote rejected] (fail to run proc-receive hook) + Done + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' diff --git a/t/t5411/test-0013-bad-protocol.sh b/t/t5411/test-0013-bad-protocol.sh new file mode 100644 index 0000000000..c5fe4cb37b --- /dev/null +++ b/t/t5411/test-0013-bad-protocol.sh @@ -0,0 +1,217 @@ +test_expect_success "setup proc-receive hook (unknown version, $PROTOCOL)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v --version 2 + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/master/topic(A) +test_expect_success "proc-receive: bad protocol (unknown version, $PROTOCOL)" ' + test_must_fail git -C workbench push origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + + # Check status report for git-push + sed -n \ + -e "/^To / { p; n; p; }" \ + <actual >actual-report && + cat >expect <<-EOF && + To <URL/of/upstream.git> + ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook) + EOF + test_cmp expect actual-report && + + # Check error message from "receive-pack", but ignore unstable fatal error + # message ("remote: fatal: the remote end hung up unexpectedly") which + # is different from the remote HTTP server with different locale settings. + grep "^remote: error:" <actual >actual-error && + cat >expect <<-EOF && + remote: error: proc-receive version "2" is not supported + EOF + test_cmp expect actual-error && + + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' + +test_expect_success "setup proc-receive hook (hook --die-version, $PROTOCOL)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v --die-version + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/master/topic(A) +test_expect_success "proc-receive: bad protocol (hook --die-version, $PROTOCOL)" ' + test_must_fail git -C workbench push origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: fatal: bad protocol version: 1 + remote: error: proc-receive version "0" is not supported + To <URL/of/upstream.git> + ! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook) + EOF + test_cmp expect actual && + + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' + +test_expect_success "setup proc-receive hook (hook --die-readline, $PROTOCOL)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v --die-readline + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/master/topic(A) +test_expect_success "proc-receive: bad protocol (hook --die-readline, $PROTOCOL)" ' + test_must_fail git -C workbench push origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + + grep "remote: fatal: protocol error: expected \"old new ref\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"" actual && + + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' + +test_expect_success "setup proc-receive hook (no report, $PROTOCOL)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : next(A) refs/for/master/topic(A) +test_expect_success "proc-receive: bad protocol (no report, $PROTOCOL)" ' + test_must_fail git -C workbench push origin \ + HEAD:refs/heads/next \ + HEAD:refs/for/master/topic >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # post-receive hook + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next + To <URL/of/upstream.git> + * [new branch] HEAD -> next + ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status) + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + <COMMIT-A> refs/heads/next + EOF + test_cmp expect actual +' + +# Refs of upstream : master(A) next(A) +# Refs of workbench: master(A) tags/v123 +test_expect_success "cleanup ($PROTOCOL)" ' + git -C "$upstream" update-ref -d refs/heads/next + +' + +test_expect_success "setup proc-receive hook (no ref, $PROTOCOL)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/master/topic +test_expect_success "proc-receive: bad protocol (no ref, $PROTOCOL)" ' + test_must_fail git -C workbench push origin \ + HEAD:refs/for/master/topic\ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> ok + remote: error: proc-receive reported incomplete status line: "ok" + To <URL/of/upstream.git> + ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status) + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' + +test_expect_success "setup proc-receive hook (unknown status, $PROTOCOL)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "xx refs/for/master/topic" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/master/topic +test_expect_success "proc-receive: bad protocol (unknown status, $PROTOCOL)" ' + test_must_fail git -C workbench push origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> xx refs/for/master/topic + remote: error: proc-receive reported bad status "xx" on ref "refs/for/master/topic" + To <URL/of/upstream.git> + ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status) + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' diff --git a/t/t5411/test-0014-bad-protocol--porcelain.sh b/t/t5411/test-0014-bad-protocol--porcelain.sh new file mode 100644 index 0000000000..53b47b0185 --- /dev/null +++ b/t/t5411/test-0014-bad-protocol--porcelain.sh @@ -0,0 +1,160 @@ +test_expect_success "setup proc-receive hook (unknown version, $PROTOCOL/porcelain)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v --version 2 + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/master/topic(A) +test_expect_success "proc-receive: bad protocol (unknown version, $PROTOCOL/porcelain)" ' + test_must_fail git -C workbench push --porcelain origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + + # Check status report for git-push + sed -n \ + -e "/^To / { p; n; p; n; p; }" \ + <actual >actual-report && + cat >expect <<-EOF && + To <URL/of/upstream.git> + ! HEAD:refs/for/master/topic [remote rejected] (fail to run proc-receive hook) + Done + EOF + test_cmp expect actual-report && + + # Check error message from "receive-pack", but ignore unstable fatal error + # message ("remote: fatal: the remote end hung up unexpectedly") which + # is different from the remote HTTP server with different locale settings. + grep "^remote: error:" <actual >actual-error && + cat >expect <<-EOF && + remote: error: proc-receive version "2" is not supported + EOF + test_cmp expect actual-error && + + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' + +test_expect_success "setup proc-receive hook (no report, $PROTOCOL/porcelain)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : next(A) refs/for/master/topic(A) +test_expect_success "proc-receive: bad protocol (no report, $PROTOCOL/porcelain)" ' + test_must_fail git -C workbench push --porcelain origin \ + HEAD:refs/heads/next \ + HEAD:refs/for/master/topic >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # post-receive hook + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next + To <URL/of/upstream.git> + * HEAD:refs/heads/next [new branch] + ! HEAD:refs/for/master/topic [remote rejected] (proc-receive failed to report status) + Done + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + <COMMIT-A> refs/heads/next + EOF + test_cmp expect actual +' + +# Refs of upstream : master(A) next(A) +# Refs of workbench: master(A) tags/v123 +test_expect_success "cleanup ($PROTOCOL/porcelain)" ' + git -C "$upstream" update-ref -d refs/heads/next + +' + +test_expect_success "setup proc-receive hook (no ref, $PROTOCOL/porcelain)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/master/topic +test_expect_success "proc-receive: bad protocol (no ref, $PROTOCOL/porcelain)" ' + test_must_fail git -C workbench push --porcelain origin \ + HEAD:refs/for/master/topic\ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> ok + remote: error: proc-receive reported incomplete status line: "ok" + To <URL/of/upstream.git> + ! HEAD:refs/for/master/topic [remote rejected] (proc-receive failed to report status) + Done + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' + +test_expect_success "setup proc-receive hook (unknown status, $PROTOCOL/porcelain)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "xx refs/for/master/topic" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/master/topic +test_expect_success "proc-receive: bad protocol (unknown status, $PROTOCOL/porcelain)" ' + test_must_fail git -C workbench push --porcelain origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> xx refs/for/master/topic + remote: error: proc-receive reported bad status "xx" on ref "refs/for/master/topic" + To <URL/of/upstream.git> + ! HEAD:refs/for/master/topic [remote rejected] (proc-receive failed to report status) + Done + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' diff --git a/t/t5411/test-0020-report-ng.sh b/t/t5411/test-0020-report-ng.sh new file mode 100644 index 0000000000..f726b7ca9c --- /dev/null +++ b/t/t5411/test-0020-report-ng.sh @@ -0,0 +1,67 @@ +test_expect_success "setup proc-receive hook (ng, no message, $PROTOCOL)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ng refs/for/master/topic" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/master/topic +test_expect_success "proc-receive: fail to update (ng, no message, $PROTOCOL)" ' + test_must_fail git -C workbench push origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> ng refs/for/master/topic + To <URL/of/upstream.git> + ! [remote rejected] HEAD -> refs/for/master/topic (failed) + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' + +test_expect_success "setup proc-receive hook (ng message, $PROTOCOL)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ng refs/for/master/topic error msg" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/master/topic +test_expect_success "proc-receive: fail to update (ng, with message, $PROTOCOL)" ' + test_must_fail git -C workbench push origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> ng refs/for/master/topic error msg + To <URL/of/upstream.git> + ! [remote rejected] HEAD -> refs/for/master/topic (error msg) + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' diff --git a/t/t5411/test-0021-report-ng--porcelain.sh b/t/t5411/test-0021-report-ng--porcelain.sh new file mode 100644 index 0000000000..fbf5569103 --- /dev/null +++ b/t/t5411/test-0021-report-ng--porcelain.sh @@ -0,0 +1,69 @@ +test_expect_success "setup proc-receive hook (ng, no message, $PROTOCOL/porcelain)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ng refs/for/master/topic" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/master/topic +test_expect_success "proc-receive: fail to update (ng, no message, $PROTOCOL/porcelain)" ' + test_must_fail git -C workbench push --porcelain origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> ng refs/for/master/topic + To <URL/of/upstream.git> + ! HEAD:refs/for/master/topic [remote rejected] (failed) + Done + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' + +test_expect_success "setup proc-receive hook (ng message, $PROTOCOL/porcelain)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ng refs/for/master/topic error msg" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/master/topic +test_expect_success "proc-receive: fail to update (ng, with message, $PROTOCOL/porcelain)" ' + test_must_fail git -C workbench push --porcelain origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> ng refs/for/master/topic error msg + To <URL/of/upstream.git> + ! HEAD:refs/for/master/topic [remote rejected] (error msg) + Done + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' diff --git a/t/t5411/test-0022-report-unexpect-ref.sh b/t/t5411/test-0022-report-unexpect-ref.sh new file mode 100644 index 0000000000..92a415b929 --- /dev/null +++ b/t/t5411/test-0022-report-unexpect-ref.sh @@ -0,0 +1,45 @@ +test_expect_success "setup proc-receive hook (unexpected ref, $PROTOCOL)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/heads/master" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : (B) refs/for/master/topic +test_expect_success "proc-receive: report unexpected ref ($PROTOCOL)" ' + test_must_fail git -C workbench push origin \ + $B:refs/heads/master \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> ok refs/heads/master + remote: error: proc-receive reported status on unexpected ref: refs/heads/master + remote: # post-receive hook + remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master + To <URL/of/upstream.git> + <OID-A>..<OID-B> <COMMIT-B> -> master + ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status) + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-B> refs/heads/master + EOF + test_cmp expect actual +' + +# Refs of upstream : master(B) +# Refs of workbench: master(A) tags/v123 +test_expect_success "cleanup ($PROTOCOL)" ' + git -C "$upstream" update-ref refs/heads/master $A +' diff --git a/t/t5411/test-0023-report-unexpect-ref--porcelain.sh b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh new file mode 100644 index 0000000000..acbf93e40a --- /dev/null +++ b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh @@ -0,0 +1,46 @@ +test_expect_success "setup proc-receive hook (unexpected ref, $PROTOCOL/porcelain)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/heads/master" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : (B) refs/for/master/topic +test_expect_success "proc-receive: report unexpected ref ($PROTOCOL/porcelain)" ' + test_must_fail git -C workbench push --porcelain origin \ + $B:refs/heads/master \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> ok refs/heads/master + remote: error: proc-receive reported status on unexpected ref: refs/heads/master + remote: # post-receive hook + remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master + To <URL/of/upstream.git> + <COMMIT-B>:refs/heads/master <OID-A>..<OID-B> + ! HEAD:refs/for/master/topic [remote rejected] (proc-receive failed to report status) + Done + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-B> refs/heads/master + EOF + test_cmp expect actual +' + +# Refs of upstream : master(B) +# Refs of workbench: master(A) tags/v123 +test_expect_success "cleanup ($PROTOCOL/porcelain)" ' + git -C "$upstream" update-ref refs/heads/master $A +' diff --git a/t/t5411/test-0024-report-unknown-ref.sh b/t/t5411/test-0024-report-unknown-ref.sh new file mode 100644 index 0000000000..c3946f329a --- /dev/null +++ b/t/t5411/test-0024-report-unknown-ref.sh @@ -0,0 +1,34 @@ +test_expect_success "setup proc-receive hook (unexpected ref, $PROTOCOL)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/for/master/topic" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/a/b/c/my/topic +test_expect_success "proc-receive: report unknown reference ($PROTOCOL)" ' + test_must_fail git -C workbench push origin \ + HEAD:refs/for/a/b/c/my/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic + remote: proc-receive> ok refs/for/master/topic + remote: error: proc-receive reported status on unknown ref: refs/for/master/topic + To <URL/of/upstream.git> + ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (proc-receive failed to report status) + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' diff --git a/t/t5411/test-0025-report-unknown-ref--porcelain.sh b/t/t5411/test-0025-report-unknown-ref--porcelain.sh new file mode 100644 index 0000000000..d093b1a579 --- /dev/null +++ b/t/t5411/test-0025-report-unknown-ref--porcelain.sh @@ -0,0 +1,35 @@ +test_expect_success "setup proc-receive hook (unexpected ref, $PROTOCOL/porcelain)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/for/master/topic" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/a/b/c/my/topic +test_expect_success "proc-receive: report unknown reference ($PROTOCOL/porcelain)" ' + test_must_fail git -C workbench push --porcelain origin \ + HEAD:refs/for/a/b/c/my/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic + remote: proc-receive> ok refs/for/master/topic + remote: error: proc-receive reported status on unknown ref: refs/for/master/topic + To <URL/of/upstream.git> + ! HEAD:refs/for/a/b/c/my/topic [remote rejected] (proc-receive failed to report status) + Done + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' diff --git a/t/t5411/test-0026-push-options.sh b/t/t5411/test-0026-push-options.sh new file mode 100644 index 0000000000..d0c4da8b23 --- /dev/null +++ b/t/t5411/test-0026-push-options.sh @@ -0,0 +1,79 @@ +test_expect_success "setup proc-receive hook and disable push-options ($PROTOCOL)" ' + git -C "$upstream" config receive.advertisePushOptions false && + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/for/master/topic" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push -o ... : refs/for/master/topic +test_expect_success "proc-receive: not support push options ($PROTOCOL)" ' + test_must_fail git -C workbench push \ + -o issue=123 \ + -o reviewer=user1 \ + origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + test_i18ngrep "fatal: the receiving end does not support push options" \ + actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' + +test_expect_success "enable push options ($PROTOCOL)" ' + git -C "$upstream" config receive.advertisePushOptions true +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push -o ... : next(A) refs/for/master/topic +test_expect_success "proc-receive: push with options ($PROTOCOL)" ' + git -C workbench push \ + --atomic \ + -o issue=123 \ + -o reviewer=user1 \ + origin \ + HEAD:refs/heads/next \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive: atomic push_options + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive< issue=123 + remote: proc-receive< reviewer=user1 + remote: proc-receive> ok refs/for/master/topic + remote: # post-receive hook + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + To <URL/of/upstream.git> + * [new branch] HEAD -> next + * [new reference] HEAD -> refs/for/master/topic + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + <COMMIT-A> refs/heads/next + EOF + test_cmp expect actual +' + +# Refs of upstream : master(A) next(A) +# Refs of workbench: master(A) tags/v123 +test_expect_success "cleanup ($PROTOCOL)" ' + git -C "$upstream" update-ref -d refs/heads/next +' diff --git a/t/t5411/test-0027-push-options--porcelain.sh b/t/t5411/test-0027-push-options--porcelain.sh new file mode 100644 index 0000000000..c89a1e7c57 --- /dev/null +++ b/t/t5411/test-0027-push-options--porcelain.sh @@ -0,0 +1,82 @@ +test_expect_success "setup proc-receive hook and disable push-options ($PROTOCOL/porcelain)" ' + git -C "$upstream" config receive.advertisePushOptions false && + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/for/master/topic" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push -o ... : refs/for/master/topic +test_expect_success "proc-receive: not support push options ($PROTOCOL/porcelain)" ' + test_must_fail git -C workbench push \ + --porcelain \ + -o issue=123 \ + -o reviewer=user1 \ + origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + test_i18ngrep "fatal: the receiving end does not support push options" \ + actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' + +test_expect_success "enable push options ($PROTOCOL/porcelain)" ' + git -C "$upstream" config receive.advertisePushOptions true +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push -o ... : next(A) refs/for/master/topic +test_expect_success "proc-receive: push with options ($PROTOCOL/porcelain)" ' + git -C workbench push \ + --porcelain \ + --atomic \ + -o issue=123 \ + -o reviewer=user1 \ + origin \ + HEAD:refs/heads/next \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive: atomic push_options + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive< issue=123 + remote: proc-receive< reviewer=user1 + remote: proc-receive> ok refs/for/master/topic + remote: # post-receive hook + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + To <URL/of/upstream.git> + * HEAD:refs/heads/next [new branch] + * HEAD:refs/for/master/topic [new reference] + Done + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + <COMMIT-A> refs/heads/next + EOF + test_cmp expect actual +' + +# Refs of upstream : master(A) next(A) +# Refs of workbench: master(A) tags/v123 +test_expect_success "cleanup ($PROTOCOL/porcelain)" ' + git -C "$upstream" update-ref -d refs/heads/next +' diff --git a/t/t5411/test-0030-report-ok.sh b/t/t5411/test-0030-report-ok.sh new file mode 100644 index 0000000000..44c99d3831 --- /dev/null +++ b/t/t5411/test-0030-report-ok.sh @@ -0,0 +1,35 @@ +test_expect_success "setup proc-receive hook (ok, $PROTOCOL)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/for/master/topic" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/master/topic +test_expect_success "proc-receive: ok ($PROTOCOL)" ' + git -C workbench push origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> ok refs/for/master/topic + remote: # post-receive hook + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + To <URL/of/upstream.git> + * [new reference] HEAD -> refs/for/master/topic + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' diff --git a/t/t5411/test-0031-report-ok--porcelain.sh b/t/t5411/test-0031-report-ok--porcelain.sh new file mode 100644 index 0000000000..3223b26184 --- /dev/null +++ b/t/t5411/test-0031-report-ok--porcelain.sh @@ -0,0 +1,36 @@ +test_expect_success "setup proc-receive hook (ok, $PROTOCOL/porcelain)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/for/master/topic" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/master/topic +test_expect_success "proc-receive: ok ($PROTOCOL/porcelain)" ' + git -C workbench push --porcelain origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> ok refs/for/master/topic + remote: # post-receive hook + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + To <URL/of/upstream.git> + * HEAD:refs/for/master/topic [new reference] + Done + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' diff --git a/t/t5411/test-0032-report-with-options.sh b/t/t5411/test-0032-report-with-options.sh new file mode 100644 index 0000000000..b77b78c49f --- /dev/null +++ b/t/t5411/test-0032-report-with-options.sh @@ -0,0 +1,256 @@ +test_expect_success "setup proc-receive hook (option without matching ok, $PROTOCOL)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "option refname refs/pull/123/head" \ + -r "option old-oid $B" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A) +test_expect_success "proc-receive: report option without matching ok ($PROTOCOL)" ' + test_must_fail git -C workbench push origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> option refname refs/pull/123/head + remote: proc-receive> option old-oid <COMMIT-B> + remote: error: proc-receive reported "option" without a matching "ok/ng" directive + To <URL/of/upstream.git> + ! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status) + EOF + test_cmp expect actual +' + +test_expect_success "setup proc-receive hook (option refname, $PROTOCOL)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/for/master/topic" \ + -r "option refname refs/pull/123/head" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A) +test_expect_success "proc-receive: report option refname ($PROTOCOL)" ' + git -C workbench push origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option refname refs/pull/123/head + remote: # post-receive hook + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head + To <URL/of/upstream.git> + * [new reference] HEAD -> refs/pull/123/head + EOF + test_cmp expect actual +' + +test_expect_success "setup proc-receive hook (option refname and forced-update, $PROTOCOL)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/for/master/topic" \ + -r "option refname refs/pull/123/head" \ + -r "option forced-update" + EOF +' +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A) +test_expect_success "proc-receive: report option refname and forced-update ($PROTOCOL)" ' + git -C workbench push origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option refname refs/pull/123/head + remote: proc-receive> option forced-update + remote: # post-receive hook + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head + To <URL/of/upstream.git> + * [new reference] HEAD -> refs/pull/123/head + EOF + test_cmp expect actual +' + +test_expect_success "setup proc-receive hook (option refname and old-oid, $PROTOCOL)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/for/master/topic" \ + -r "option refname refs/pull/123/head" \ + -r "option old-oid $B" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A) +test_expect_success "proc-receive: report option refname and old-oid ($PROTOCOL)" ' + git -C workbench push origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option refname refs/pull/123/head + remote: proc-receive> option old-oid <COMMIT-B> + remote: # post-receive hook + remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head + To <URL/of/upstream.git> + <OID-B>..<OID-A> HEAD -> refs/pull/123/head + EOF + test_cmp expect actual +' + +test_expect_success "setup proc-receive hook (option old-oid, $PROTOCOL)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/for/master/topic" \ + -r "option old-oid $B" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A) +test_expect_success "proc-receive: report option old-oid ($PROTOCOL)" ' + git -C workbench push origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option old-oid <COMMIT-B> + remote: # post-receive hook + remote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic + To <URL/of/upstream.git> + <OID-B>..<OID-A> HEAD -> refs/for/master/topic + EOF + test_cmp expect actual +' + +test_expect_success "setup proc-receive hook (option old-oid and new-oid, $PROTOCOL)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/for/master/topic" \ + -r "option old-oid $A" \ + -r "option new-oid $B" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A) +test_expect_success "proc-receive: report option old-oid and new-oid ($PROTOCOL)" ' + git -C workbench push origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option old-oid <COMMIT-A> + remote: proc-receive> option new-oid <COMMIT-B> + remote: # post-receive hook + remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic + To <URL/of/upstream.git> + <OID-A>..<OID-B> HEAD -> refs/for/master/topic + EOF + test_cmp expect actual +' + +test_expect_success "setup proc-receive hook (report with multiple rewrites, $PROTOCOL)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/for/a/b/c/topic" \ + -r "ok refs/for/next/topic" \ + -r "option refname refs/pull/123/head" \ + -r "ok refs/for/master/topic" \ + -r "option refname refs/pull/124/head" \ + -r "option old-oid $B" \ + -r "option forced-update" \ + -r "option new-oid $A" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A) +test_expect_success "proc-receive: report with multiple rewrites ($PROTOCOL)" ' + git -C workbench push origin \ + HEAD:refs/for/next/topic \ + HEAD:refs/for/a/b/c/topic \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> ok refs/for/a/b/c/topic + remote: proc-receive> ok refs/for/next/topic + remote: proc-receive> option refname refs/pull/123/head + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option refname refs/pull/124/head + remote: proc-receive> option old-oid <COMMIT-B> + remote: proc-receive> option forced-update + remote: proc-receive> option new-oid <COMMIT-A> + remote: # post-receive hook + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic + remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head + To <URL/of/upstream.git> + * [new reference] HEAD -> refs/pull/123/head + * [new reference] HEAD -> refs/for/a/b/c/topic + + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update) + EOF + test_cmp expect actual && + + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' diff --git a/t/t5411/test-0033-report-with-options--porcelain.sh b/t/t5411/test-0033-report-with-options--porcelain.sh new file mode 100644 index 0000000000..1fe352b686 --- /dev/null +++ b/t/t5411/test-0033-report-with-options--porcelain.sh @@ -0,0 +1,265 @@ +test_expect_success "setup proc-receive hook (option without matching ok, $PROTOCOL/porcelain)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "option refname refs/pull/123/head" \ + -r "option old-oid $B" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A) +test_expect_success "proc-receive: report option without matching ok ($PROTOCOL/porcelain)" ' + test_must_fail git -C workbench push --porcelain origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> option refname refs/pull/123/head + remote: proc-receive> option old-oid <COMMIT-B> + remote: error: proc-receive reported "option" without a matching "ok/ng" directive + To <URL/of/upstream.git> + ! HEAD:refs/for/master/topic [remote rejected] (proc-receive failed to report status) + Done + EOF + test_cmp expect actual +' + +test_expect_success "setup proc-receive hook (option refname, $PROTOCOL/porcelain)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/for/master/topic" \ + -r "option refname refs/pull/123/head" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A) +test_expect_success "proc-receive: report option refname ($PROTOCOL/porcelain)" ' + git -C workbench push --porcelain origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option refname refs/pull/123/head + remote: # post-receive hook + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head + To <URL/of/upstream.git> + * HEAD:refs/pull/123/head [new reference] + Done + EOF + test_cmp expect actual +' + +test_expect_success "setup proc-receive hook (option refname and forced-update, $PROTOCOL/porcelain)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/for/master/topic" \ + -r "option refname refs/pull/123/head" \ + -r "option forced-update" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A) +test_expect_success "proc-receive: report option refname and forced-update ($PROTOCOL/porcelain)" ' + git -C workbench push --porcelain origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option refname refs/pull/123/head + remote: proc-receive> option forced-update + remote: # post-receive hook + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head + To <URL/of/upstream.git> + * HEAD:refs/pull/123/head [new reference] + Done + EOF + test_cmp expect actual +' + +test_expect_success "setup proc-receive hook (option refname and old-oid, $PROTOCOL/porcelain)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/for/master/topic" \ + -r "option refname refs/pull/123/head" \ + -r "option old-oid $B" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A) +test_expect_success "proc-receive: report option refname and old-oid ($PROTOCOL/porcelain)" ' + git -C workbench push --porcelain origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option refname refs/pull/123/head + remote: proc-receive> option old-oid <COMMIT-B> + remote: # post-receive hook + remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/123/head + To <URL/of/upstream.git> + HEAD:refs/pull/123/head <OID-B>..<OID-A> + Done + EOF + test_cmp expect actual +' + +test_expect_success "setup proc-receive hook (option old-oid, $PROTOCOL/porcelain)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/for/master/topic" \ + -r "option old-oid $B" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A) +test_expect_success "proc-receive: report option old-oid ($PROTOCOL/porcelain)" ' + git -C workbench push --porcelain origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option old-oid <COMMIT-B> + remote: # post-receive hook + remote: post-receive< <COMMIT-B> <COMMIT-A> refs/for/master/topic + To <URL/of/upstream.git> + HEAD:refs/for/master/topic <OID-B>..<OID-A> + Done + EOF + test_cmp expect actual +' + +test_expect_success "setup proc-receive hook (option old-oid and new-oid, $PROTOCOL/porcelain)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/for/master/topic" \ + -r "option old-oid $A" \ + -r "option new-oid $B" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A) +test_expect_success "proc-receive: report option old-oid and new-oid ($PROTOCOL/porcelain)" ' + git -C workbench push --porcelain origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option old-oid <COMMIT-A> + remote: proc-receive> option new-oid <COMMIT-B> + remote: # post-receive hook + remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic + To <URL/of/upstream.git> + HEAD:refs/for/master/topic <OID-A>..<OID-B> + Done + EOF + test_cmp expect actual +' + +test_expect_success "setup proc-receive hook (report with multiple rewrites, $PROTOCOL/porcelain)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/for/a/b/c/topic" \ + -r "ok refs/for/next/topic" \ + -r "option refname refs/pull/123/head" \ + -r "ok refs/for/master/topic" \ + -r "option refname refs/pull/124/head" \ + -r "option old-oid $B" \ + -r "option forced-update" \ + -r "option new-oid $A" + + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/next/topic(A) refs/for/a/b/c/topic(A) refs/for/master/topic(A) +test_expect_success "proc-receive: report with multiple rewrites ($PROTOCOL/porcelain)" ' + git -C workbench push --porcelain origin \ + HEAD:refs/for/next/topic \ + HEAD:refs/for/a/b/c/topic \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> ok refs/for/a/b/c/topic + remote: proc-receive> ok refs/for/next/topic + remote: proc-receive> option refname refs/pull/123/head + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option refname refs/pull/124/head + remote: proc-receive> option old-oid <COMMIT-B> + remote: proc-receive> option forced-update + remote: proc-receive> option new-oid <COMMIT-A> + remote: # post-receive hook + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/123/head + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/topic + remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head + To <URL/of/upstream.git> + * HEAD:refs/pull/123/head [new reference] + * HEAD:refs/for/a/b/c/topic [new reference] + + HEAD:refs/pull/124/head <OID-B>...<OID-A> (forced update) + Done + EOF + test_cmp expect actual && + + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' diff --git a/t/t5411/test-0034-report-ft.sh b/t/t5411/test-0034-report-ft.sh new file mode 100644 index 0000000000..aca2b0676c --- /dev/null +++ b/t/t5411/test-0034-report-ft.sh @@ -0,0 +1,44 @@ +test_expect_success "setup proc-receive hook (ft, $PROTOCOL)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/for/master/topic" \ + -r "option fall-through" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/master/topic(B) +test_expect_success "proc-receive: fall throught, let receive-pack to execute ($PROTOCOL)" ' + git -C workbench push origin \ + $B:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option fall-through + remote: # post-receive hook + remote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic + To <URL/of/upstream.git> + * [new reference] <COMMIT-B> -> refs/for/master/topic + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-B> refs/for/master/topic + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' + +# Refs of upstream : master(A) refs/for/master/topic(A) +# Refs of workbench: master(A) tags/v123 +test_expect_success "cleanup ($PROTOCOL)" ' + git -C "$upstream" update-ref -d refs/for/master/topic +' diff --git a/t/t5411/test-0035-report-ft--porcelain.sh b/t/t5411/test-0035-report-ft--porcelain.sh new file mode 100644 index 0000000000..30ffffb352 --- /dev/null +++ b/t/t5411/test-0035-report-ft--porcelain.sh @@ -0,0 +1,45 @@ +test_expect_success "setup proc-receive hook (fall-through, $PROTOCOL/porcelain)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/for/master/topic" \ + -r "option fall-through" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/master/topic(B) +test_expect_success "proc-receive: fall throught, let receive-pack to execute ($PROTOCOL/porcelain)" ' + git -C workbench push --porcelain origin \ + $B:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option fall-through + remote: # post-receive hook + remote: post-receive< <ZERO-OID> <COMMIT-B> refs/for/master/topic + To <URL/of/upstream.git> + * <COMMIT-B>:refs/for/master/topic [new reference] + Done + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-B> refs/for/master/topic + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' + +# Refs of upstream : master(A) refs/for/master/topic(A) +# Refs of workbench: master(A) tags/v123 +test_expect_success "cleanup ($PROTOCOL/porcelain)" ' + git -C "$upstream" update-ref -d refs/for/master/topic +' diff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh new file mode 100644 index 0000000000..73283d81e8 --- /dev/null +++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh @@ -0,0 +1,227 @@ +test_expect_success "setup git config for remote-tracking of special refs" ' + ( + cd workbench && + if ! git config --get-all remote.origin.fetch | grep refs/for/ + then + git config --add remote.origin.fetch \ + "+refs/for/*:refs/t/for/*" && + git config --add remote.origin.fetch \ + "+refs/pull/*:refs/t/pull/*" && + git config --add remote.origin.fetch \ + "+refs/changes/*:refs/t/changes/*" + fi + ) +' + +test_expect_success "setup proc-receive hook (multiple rewrites for one ref, no refname for the 1st rewrite, $PROTOCOL)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/for/master/topic" \ + -r "option old-oid $A" \ + -r "option new-oid $B" \ + -r "ok refs/for/master/topic" \ + -r "option refname refs/changes/24/124/1" \ + -r "option old-oid $ZERO_OID" \ + -r "option new-oid $A" \ + -r "ok refs/for/master/topic" \ + -r "option refname refs/changes/25/125/1" \ + -r "option old-oid $A" \ + -r "option new-oid $B" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/master/topic(A) +test_expect_success "proc-receive: multiple rewrite for one ref, no refname for the 1st rewrite ($PROTOCOL)" ' + git -C workbench push origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option old-oid <COMMIT-A> + remote: proc-receive> option new-oid <COMMIT-B> + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option refname refs/changes/24/124/1 + remote: proc-receive> option old-oid <ZERO-OID> + remote: proc-receive> option new-oid <COMMIT-A> + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option refname refs/changes/25/125/1 + remote: proc-receive> option old-oid <COMMIT-A> + remote: proc-receive> option new-oid <COMMIT-B> + remote: # post-receive hook + remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1 + remote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1 + To <URL/of/upstream.git> + <OID-A>..<OID-B> HEAD -> refs/for/master/topic + * [new reference] HEAD -> refs/changes/24/124/1 + <OID-A>..<OID-B> HEAD -> refs/changes/25/125/1 + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' + +test_expect_success "proc-receive: check remote-tracking #1 ($PROTOCOL)" ' + git -C workbench show-ref | + grep -v -e refs/remotes -e refs/heads -e refs/tags >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/t/changes/24/124/1 + <COMMIT-B> refs/t/changes/25/125/1 + <COMMIT-B> refs/t/for/master/topic + EOF + test_cmp expect actual && + git -C workbench update-ref -d refs/t/for/master/topic && + git -C workbench update-ref -d refs/t/changes/24/124/1 && + git -C workbench update-ref -d refs/t/changes/25/125/1 +' + +test_expect_success "setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/for/master/topic" \ + -r "option refname refs/changes/24/124/1" \ + -r "option old-oid $ZERO_OID" \ + -r "option new-oid $A" \ + -r "ok refs/for/master/topic" \ + -r "option old-oid $A" \ + -r "option new-oid $B" \ + -r "ok refs/for/master/topic" \ + -r "option refname refs/changes/25/125/1" \ + -r "option old-oid $B" \ + -r "option new-oid $A" \ + -r "option forced-update" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/master/topic(A) +test_expect_success "proc-receive: multiple rewrites for one ref, no refname for the 2nd rewrite ($PROTOCOL)" ' + git -C workbench push origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option refname refs/changes/24/124/1 + remote: proc-receive> option old-oid <ZERO-OID> + remote: proc-receive> option new-oid <COMMIT-A> + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option old-oid <COMMIT-A> + remote: proc-receive> option new-oid <COMMIT-B> + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option refname refs/changes/25/125/1 + remote: proc-receive> option old-oid <COMMIT-B> + remote: proc-receive> option new-oid <COMMIT-A> + remote: proc-receive> option forced-update + remote: # post-receive hook + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1 + remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic + remote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1 + To <URL/of/upstream.git> + * [new reference] HEAD -> refs/changes/24/124/1 + <OID-A>..<OID-B> HEAD -> refs/for/master/topic + + <OID-B>...<OID-A> HEAD -> refs/changes/25/125/1 (forced update) + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' + +test_expect_success "proc-receive: check remote-tracking #2 ($PROTOCOL)" ' + git -C workbench show-ref | + grep -v -e refs/remotes -e refs/heads -e refs/tags >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/t/changes/24/124/1 + <COMMIT-A> refs/t/changes/25/125/1 + <COMMIT-B> refs/t/for/master/topic + EOF + test_cmp expect actual && + git -C workbench update-ref -d refs/t/for/master/topic && + git -C workbench update-ref -d refs/t/changes/24/124/1 && + git -C workbench update-ref -d refs/t/changes/25/125/1 +' + +test_expect_success "setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/for/master/topic" \ + -r "option refname refs/changes/23/123/1" \ + -r "ok refs/for/master/topic" \ + -r "option refname refs/changes/24/124/2" \ + -r "option old-oid $A" \ + -r "option new-oid $B" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/master/topic(A) +test_expect_success "proc-receive: multiple rewrites for one ref ($PROTOCOL)" ' + git -C workbench push origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option refname refs/changes/23/123/1 + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option refname refs/changes/24/124/2 + remote: proc-receive> option old-oid <COMMIT-A> + remote: proc-receive> option new-oid <COMMIT-B> + remote: # post-receive hook + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1 + remote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2 + To <URL/of/upstream.git> + * [new reference] HEAD -> refs/changes/23/123/1 + <OID-A>..<OID-B> HEAD -> refs/changes/24/124/2 + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' + +test_expect_success "proc-receive: check remote-tracking #3 ($PROTOCOL)" ' + git -C workbench show-ref | + grep -v -e refs/remotes -e refs/heads -e refs/tags >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/t/changes/23/123/1 + <COMMIT-B> refs/t/changes/24/124/2 + EOF + test_cmp expect actual && + git -C workbench update-ref -d refs/t/changes/24/124/1 && + git -C workbench update-ref -d refs/t/changes/25/125/2 +' diff --git a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh new file mode 100644 index 0000000000..77b5b22ed4 --- /dev/null +++ b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh @@ -0,0 +1,172 @@ +test_expect_success "setup proc-receive hook (multiple rewrites for one ref, no refname for the 1st rewrite, $PROTOCOL/porcelain)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/for/master/topic" \ + -r "option old-oid $A" \ + -r "option new-oid $B" \ + -r "ok refs/for/master/topic" \ + -r "option refname refs/changes/24/124/1" \ + -r "option old-oid $ZERO_OID" \ + -r "option new-oid $A" \ + -r "ok refs/for/master/topic" \ + -r "option refname refs/changes/25/125/1" \ + -r "option old-oid $A" \ + -r "option new-oid $B" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/master/topic(A) +test_expect_success "proc-receive: multiple rewrite for one ref, no refname for the 1st rewrite ($PROTOCOL/porcelain)" ' + git -C workbench push --porcelain origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option old-oid <COMMIT-A> + remote: proc-receive> option new-oid <COMMIT-B> + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option refname refs/changes/24/124/1 + remote: proc-receive> option old-oid <ZERO-OID> + remote: proc-receive> option new-oid <COMMIT-A> + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option refname refs/changes/25/125/1 + remote: proc-receive> option old-oid <COMMIT-A> + remote: proc-receive> option new-oid <COMMIT-B> + remote: # post-receive hook + remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1 + remote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/25/125/1 + To <URL/of/upstream.git> + HEAD:refs/for/master/topic <OID-A>..<OID-B> + * HEAD:refs/changes/24/124/1 [new reference] + HEAD:refs/changes/25/125/1 <OID-A>..<OID-B> + Done + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' + +test_expect_success "setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL/porcelain)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/for/master/topic" \ + -r "option refname refs/changes/24/124/1" \ + -r "option old-oid $ZERO_OID" \ + -r "option new-oid $A" \ + -r "ok refs/for/master/topic" \ + -r "option old-oid $A" \ + -r "option new-oid $B" \ + -r "ok refs/for/master/topic" \ + -r "option refname refs/changes/25/125/1" \ + -r "option old-oid $B" \ + -r "option new-oid $A" \ + -r "option forced-update" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/master/topic(A) +test_expect_success "proc-receive: multiple rewrites for one ref, no refname for the 2nd rewrite ($PROTOCOL/porcelain)" ' + git -C workbench push --porcelain origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option refname refs/changes/24/124/1 + remote: proc-receive> option old-oid <ZERO-OID> + remote: proc-receive> option new-oid <COMMIT-A> + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option old-oid <COMMIT-A> + remote: proc-receive> option new-oid <COMMIT-B> + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option refname refs/changes/25/125/1 + remote: proc-receive> option old-oid <COMMIT-B> + remote: proc-receive> option new-oid <COMMIT-A> + remote: proc-receive> option forced-update + remote: # post-receive hook + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/24/124/1 + remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic + remote: post-receive< <COMMIT-B> <COMMIT-A> refs/changes/25/125/1 + To <URL/of/upstream.git> + * HEAD:refs/changes/24/124/1 [new reference] + HEAD:refs/for/master/topic <OID-A>..<OID-B> + + HEAD:refs/changes/25/125/1 <OID-B>...<OID-A> (forced update) + Done + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' + +test_expect_success "setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL/porcelain)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/for/master/topic" \ + -r "option refname refs/changes/23/123/1" \ + -r "ok refs/for/master/topic" \ + -r "option refname refs/changes/24/124/2" \ + -r "option old-oid $A" \ + -r "option new-oid $B" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : refs/for/master/topic(A) +test_expect_success "proc-receive: multiple rewrites for one ref ($PROTOCOL/porcelain)" ' + git -C workbench push --porcelain origin \ + HEAD:refs/for/master/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option refname refs/changes/23/123/1 + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option refname refs/changes/24/124/2 + remote: proc-receive> option old-oid <COMMIT-A> + remote: proc-receive> option new-oid <COMMIT-B> + remote: # post-receive hook + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/changes/23/123/1 + remote: post-receive< <COMMIT-A> <COMMIT-B> refs/changes/24/124/2 + To <URL/of/upstream.git> + * HEAD:refs/changes/23/123/1 [new reference] + HEAD:refs/changes/24/124/2 <OID-A>..<OID-B> + Done + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' diff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh new file mode 100644 index 0000000000..a74a2cb449 --- /dev/null +++ b/t/t5411/test-0038-report-mixed-refs.sh @@ -0,0 +1,89 @@ +test_expect_success "setup proc-receive hook ($PROTOCOL)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/for/next/topic2" \ + -r "ng refs/for/next/topic1 fail to call Web API" \ + -r "ok refs/for/master/topic" \ + -r "option refname refs/for/master/topic" \ + -r "option old-oid $A" \ + -r "option new-oid $B" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : (B) bar(A) baz(A) refs/for/next/topic(A) foo(A) refs/for/master/topic(A) +test_expect_success "proc-receive: report update of mixed refs ($PROTOCOL)" ' + test_must_fail git -C workbench push origin \ + $B:refs/heads/master \ + HEAD:refs/heads/bar \ + HEAD:refs/heads/baz \ + HEAD:refs/for/next/topic2 \ + HEAD:refs/for/next/topic1 \ + HEAD:refs/heads/foo \ + HEAD:refs/for/master/topic \ + HEAD:refs/for/next/topic3 \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2 + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1 + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3 + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2 + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1 + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3 + remote: proc-receive> ok refs/for/next/topic2 + remote: proc-receive> ng refs/for/next/topic1 fail to call Web API + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option refname refs/for/master/topic + remote: proc-receive> option old-oid <COMMIT-A> + remote: proc-receive> option new-oid <COMMIT-B> + remote: # post-receive hook + remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2 + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo + remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic + To <URL/of/upstream.git> + <OID-A>..<OID-B> <COMMIT-B> -> master + * [new branch] HEAD -> bar + * [new branch] HEAD -> baz + * [new reference] HEAD -> refs/for/next/topic2 + * [new branch] HEAD -> foo + <OID-A>..<OID-B> HEAD -> refs/for/master/topic + ! [remote rejected] HEAD -> refs/for/next/topic1 (fail to call Web API) + ! [remote rejected] HEAD -> refs/for/next/topic3 (proc-receive failed to report status) + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/bar + <COMMIT-A> refs/heads/baz + <COMMIT-A> refs/heads/foo + <COMMIT-B> refs/heads/master + EOF + test_cmp expect actual +' + +# Refs of upstream : master(B) foo(A) bar(A)) baz(A) +# Refs of workbench: master(A) tags/v123 +test_expect_success "cleanup ($PROTOCOL)" ' + ( + cd "$upstream" && + git update-ref refs/heads/master $A && + git update-ref -d refs/heads/foo && + git update-ref -d refs/heads/bar && + git update-ref -d refs/heads/baz + ) +' diff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh new file mode 100644 index 0000000000..e4baa13ea3 --- /dev/null +++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh @@ -0,0 +1,91 @@ +test_expect_success "setup proc-receive hook ($PROTOCOL/porcelain)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/for/next/topic2" \ + -r "ng refs/for/next/topic1 fail to call Web API" \ + -r "ok refs/for/master/topic" \ + -r "option refname refs/for/master/topic" \ + -r "option old-oid $A" \ + -r "option new-oid $B" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : (B) bar(A) baz(A) refs/for/next/topic(A) foo(A) refs/for/master/topic(A) +test_expect_success "proc-receive: report update of mixed refs ($PROTOCOL/porcelain)" ' + test_must_fail git -C workbench push --porcelain origin \ + $B:refs/heads/master \ + HEAD:refs/heads/bar \ + HEAD:refs/heads/baz \ + HEAD:refs/for/next/topic2 \ + HEAD:refs/for/next/topic1 \ + HEAD:refs/heads/foo \ + HEAD:refs/for/master/topic \ + HEAD:refs/for/next/topic3 \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2 + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1 + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3 + remote: # proc-receive hook + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2 + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic1 + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic3 + remote: proc-receive> ok refs/for/next/topic2 + remote: proc-receive> ng refs/for/next/topic1 fail to call Web API + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option refname refs/for/master/topic + remote: proc-receive> option old-oid <COMMIT-A> + remote: proc-receive> option new-oid <COMMIT-B> + remote: # post-receive hook + remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/master + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/bar + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/baz + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic2 + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/foo + remote: post-receive< <COMMIT-A> <COMMIT-B> refs/for/master/topic + To <URL/of/upstream.git> + <COMMIT-B>:refs/heads/master <OID-A>..<OID-B> + * HEAD:refs/heads/bar [new branch] + * HEAD:refs/heads/baz [new branch] + * HEAD:refs/for/next/topic2 [new reference] + * HEAD:refs/heads/foo [new branch] + HEAD:refs/for/master/topic <OID-A>..<OID-B> + ! HEAD:refs/for/next/topic1 [remote rejected] (fail to call Web API) + ! HEAD:refs/for/next/topic3 [remote rejected] (proc-receive failed to report status) + Done + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/bar + <COMMIT-A> refs/heads/baz + <COMMIT-A> refs/heads/foo + <COMMIT-B> refs/heads/master + EOF + test_cmp expect actual +' + +# Refs of upstream : master(B) foo(A) bar(A)) baz(A) +# Refs of workbench: master(A) tags/v123 +test_expect_success "cleanup ($PROTOCOL/porcelain)" ' + ( + cd "$upstream" && + git update-ref refs/heads/master $A && + git update-ref -d refs/heads/foo && + git update-ref -d refs/heads/bar && + git update-ref -d refs/heads/baz + ) + +' diff --git a/t/t5411/test-0040-process-all-refs.sh b/t/t5411/test-0040-process-all-refs.sh new file mode 100644 index 0000000000..b07c999f53 --- /dev/null +++ b/t/t5411/test-0040-process-all-refs.sh @@ -0,0 +1,113 @@ +test_expect_success "config receive.procReceiveRefs = refs ($PROTOCOL)" ' + git -C "$upstream" config --unset-all receive.procReceiveRefs && + git -C "$upstream" config --add receive.procReceiveRefs refs +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +test_expect_success "setup upstream branches ($PROTOCOL)" ' + ( + cd "$upstream" && + git update-ref refs/heads/master $B && + git update-ref refs/heads/foo $A && + git update-ref refs/heads/bar $A && + git update-ref refs/heads/baz $A + ) + +' + +test_expect_success "setup proc-receive hook ($PROTOCOL)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/heads/master" \ + -r "option fall-through" \ + -r "ok refs/heads/foo" \ + -r "option fall-through" \ + -r "ok refs/heads/bar" \ + -r "option fall-through" \ + -r "ok refs/for/master/topic" \ + -r "option refname refs/pull/123/head" \ + -r "option old-oid $A" \ + -r "option new-oid $B" \ + -r "ok refs/for/next/topic" \ + -r "option refname refs/pull/124/head" \ + -r "option old-oid $B" \ + -r "option new-oid $A" \ + -r "option forced-update" + EOF +' + +# Refs of upstream : master(B) foo(A) bar(A)) baz(A) +# Refs of workbench: master(A) tags/v123 +# git push -f : master(A) (NULL) (B) refs/for/master/topic(A) refs/for/next/topic(A) +test_expect_success "proc-receive: process all refs ($PROTOCOL)" ' + git -C workbench push -f origin \ + HEAD:refs/heads/master \ + :refs/heads/foo \ + $B:refs/heads/bar \ + HEAD:refs/for/master/topic \ + HEAD:refs/for/next/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar + remote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo + remote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic + remote: # proc-receive hook + remote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar + remote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo + remote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic + remote: proc-receive> ok refs/heads/master + remote: proc-receive> option fall-through + remote: proc-receive> ok refs/heads/foo + remote: proc-receive> option fall-through + remote: proc-receive> ok refs/heads/bar + remote: proc-receive> option fall-through + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option refname refs/pull/123/head + remote: proc-receive> option old-oid <COMMIT-A> + remote: proc-receive> option new-oid <COMMIT-B> + remote: proc-receive> ok refs/for/next/topic + remote: proc-receive> option refname refs/pull/124/head + remote: proc-receive> option old-oid <COMMIT-B> + remote: proc-receive> option new-oid <COMMIT-A> + remote: proc-receive> option forced-update + remote: # post-receive hook + remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar + remote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo + remote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master + remote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head + remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head + To <URL/of/upstream.git> + <OID-A>..<OID-B> <COMMIT-B> -> bar + - [deleted] foo + + <OID-B>...<OID-A> HEAD -> master (forced update) + <OID-A>..<OID-B> HEAD -> refs/pull/123/head + + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update) + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-B> refs/heads/bar + <COMMIT-A> refs/heads/baz + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' + +# Refs of upstream : master(A) bar(A) baz(B) +# Refs of workbench: master(A) tags/v123 +test_expect_success "cleanup ($PROTOCOL)" ' + ( + cd "$upstream" && + git update-ref -d refs/heads/bar && + git update-ref -d refs/heads/baz + ) +' diff --git a/t/t5411/test-0041-process-all-refs--porcelain.sh b/t/t5411/test-0041-process-all-refs--porcelain.sh new file mode 100644 index 0000000000..0dd9824616 --- /dev/null +++ b/t/t5411/test-0041-process-all-refs--porcelain.sh @@ -0,0 +1,114 @@ +test_expect_success "config receive.procReceiveRefs = refs ($PROTOCOL/porcelain)" ' + git -C "$upstream" config --unset-all receive.procReceiveRefs && + git -C "$upstream" config --add receive.procReceiveRefs refs +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +test_expect_success "setup upstream branches ($PROTOCOL/porcelain)" ' + ( + cd "$upstream" && + git update-ref refs/heads/master $B && + git update-ref refs/heads/foo $A && + git update-ref refs/heads/bar $A && + git update-ref refs/heads/baz $A + ) + +' + +test_expect_success "setup proc-receive hook ($PROTOCOL/porcelain)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/heads/master" \ + -r "option fall-through" \ + -r "ok refs/heads/foo" \ + -r "option fall-through" \ + -r "ok refs/heads/bar" \ + -r "option fall-through" \ + -r "ok refs/for/master/topic" \ + -r "option refname refs/pull/123/head" \ + -r "option old-oid $A" \ + -r "option new-oid $B" \ + -r "ok refs/for/next/topic" \ + -r "option refname refs/pull/124/head" \ + -r "option old-oid $B" \ + -r "option new-oid $A" \ + -r "option forced-update" + EOF +' + +# Refs of upstream : master(B) foo(A) bar(A)) baz(A) +# Refs of workbench: master(A) tags/v123 +# git push -f : master(A) (NULL) (B) refs/for/master/topic(A) refs/for/next/topic(A) +test_expect_success "proc-receive: process all refs ($PROTOCOL/porcelain)" ' + git -C workbench push --porcelain -f origin \ + HEAD:refs/heads/master \ + :refs/heads/foo \ + $B:refs/heads/bar \ + HEAD:refs/for/master/topic \ + HEAD:refs/for/next/topic \ + >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar + remote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo + remote: pre-receive< <COMMIT-B> <COMMIT-A> refs/heads/master + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic + remote: # proc-receive hook + remote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar + remote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo + remote: proc-receive< <COMMIT-B> <COMMIT-A> refs/heads/master + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/next/topic + remote: proc-receive> ok refs/heads/master + remote: proc-receive> option fall-through + remote: proc-receive> ok refs/heads/foo + remote: proc-receive> option fall-through + remote: proc-receive> ok refs/heads/bar + remote: proc-receive> option fall-through + remote: proc-receive> ok refs/for/master/topic + remote: proc-receive> option refname refs/pull/123/head + remote: proc-receive> option old-oid <COMMIT-A> + remote: proc-receive> option new-oid <COMMIT-B> + remote: proc-receive> ok refs/for/next/topic + remote: proc-receive> option refname refs/pull/124/head + remote: proc-receive> option old-oid <COMMIT-B> + remote: proc-receive> option new-oid <COMMIT-A> + remote: proc-receive> option forced-update + remote: # post-receive hook + remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/bar + remote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/foo + remote: post-receive< <COMMIT-B> <COMMIT-A> refs/heads/master + remote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head + remote: post-receive< <COMMIT-B> <COMMIT-A> refs/pull/124/head + To <URL/of/upstream.git> + <COMMIT-B>:refs/heads/bar <OID-A>..<OID-B> + - :refs/heads/foo [deleted] + + HEAD:refs/heads/master <OID-B>...<OID-A> (forced update) + HEAD:refs/pull/123/head <OID-A>..<OID-B> + + HEAD:refs/pull/124/head <OID-B>...<OID-A> (forced update) + Done + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-B> refs/heads/bar + <COMMIT-A> refs/heads/baz + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' + +# Refs of upstream : master(A) bar(A) baz(B) +# Refs of workbench: master(A) tags/v123 +test_expect_success "cleanup ($PROTOCOL/porcelain)" ' + ( + cd "$upstream" && + git update-ref -d refs/heads/bar && + git update-ref -d refs/heads/baz + ) +' diff --git a/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh b/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh new file mode 100644 index 0000000000..c22849cbe2 --- /dev/null +++ b/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh @@ -0,0 +1,135 @@ +test_expect_success "config receive.procReceiveRefs with modifiers ($PROTOCOL)" ' + ( + cd "$upstream" && + git config --unset-all receive.procReceiveRefs && + git config --add receive.procReceiveRefs m:refs/heads/master && + git config --add receive.procReceiveRefs ad:refs/heads && + git config --add receive.procReceiveRefs "a!:refs/heads" + ) +' + +test_expect_success "setup proc-receive hook ($PROTOCOL)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/heads/master" \ + -r "option refname refs/pull/123/head" \ + -r "option old-oid $A" \ + -r "option new-oid $B" \ + -r "ok refs/tags/v123 " \ + -r "option refname refs/pull/124/head" + EOF +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +# git push : master(B) tags/v123 +test_expect_success "proc-receive: update branch and new tag ($PROTOCOL)" ' + git -C workbench push origin \ + $B:refs/heads/master \ + v123 >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/master + remote: pre-receive< <ZERO-OID> <TAG-v123> refs/tags/v123 + remote: # proc-receive hook + remote: proc-receive< <COMMIT-A> <COMMIT-B> refs/heads/master + remote: proc-receive< <ZERO-OID> <TAG-v123> refs/tags/v123 + remote: proc-receive> ok refs/heads/master + remote: proc-receive> option refname refs/pull/123/head + remote: proc-receive> option old-oid <COMMIT-A> + remote: proc-receive> option new-oid <COMMIT-B> + remote: proc-receive> ok refs/tags/v123 + remote: proc-receive> option refname refs/pull/124/head + remote: # post-receive hook + remote: post-receive< <COMMIT-A> <COMMIT-B> refs/pull/123/head + remote: post-receive< <ZERO-OID> <TAG-v123> refs/pull/124/head + To <URL/of/upstream.git> + <OID-A>..<OID-B> <COMMIT-B> -> refs/pull/123/head + * [new reference] v123 -> refs/pull/124/head + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + EOF + test_cmp expect actual +' + +# Refs of upstream : master(A) +# Refs of workbench: master(A) tags/v123 +test_expect_success "setup upstream: create tags/v123 ($PROTOCOL)" ' + git -C "$upstream" update-ref refs/heads/topic $A && + git -C "$upstream" update-ref refs/tags/v123 $TAG && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + <COMMIT-A> refs/heads/topic + <TAG-v123> refs/tags/v123 + EOF + test_cmp expect actual +' + +test_expect_success "setup proc-receive hook ($PROTOCOL)" ' + write_script "$upstream/hooks/proc-receive" <<-EOF + printf >&2 "# proc-receive hook\n" + test-tool proc-receive -v \ + -r "ok refs/heads/master" \ + -r "option refname refs/pull/123/head" \ + -r "option old-oid $A" \ + -r "option new-oid $ZERO_OID" \ + -r "ok refs/heads/next" \ + -r "option refname refs/pull/124/head" \ + -r "option new-oid $A" + EOF +' + +# Refs of upstream : master(A) topic(A) tags/v123 +# Refs of workbench: master(A) tags/v123 +# git push : NULL topic(B) NULL next(A) +test_expect_success "proc-receive: create/delete branch, and delete tag ($PROTOCOL)" ' + git -C workbench push origin \ + :refs/heads/master \ + $B:refs/heads/topic \ + $A:refs/heads/next \ + :refs/tags/v123 >out 2>&1 && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + remote: # pre-receive hook + remote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/master + remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/topic + remote: pre-receive< <TAG-v123> <ZERO-OID> refs/tags/v123 + remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next + remote: # proc-receive hook + remote: proc-receive< <COMMIT-A> <ZERO-OID> refs/heads/master + remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/heads/next + remote: proc-receive> ok refs/heads/master + remote: proc-receive> option refname refs/pull/123/head + remote: proc-receive> option old-oid <COMMIT-A> + remote: proc-receive> option new-oid <ZERO-OID> + remote: proc-receive> ok refs/heads/next + remote: proc-receive> option refname refs/pull/124/head + remote: proc-receive> option new-oid <COMMIT-A> + remote: # post-receive hook + remote: post-receive< <COMMIT-A> <ZERO-OID> refs/pull/123/head + remote: post-receive< <COMMIT-A> <COMMIT-B> refs/heads/topic + remote: post-receive< <TAG-v123> <ZERO-OID> refs/tags/v123 + remote: post-receive< <ZERO-OID> <COMMIT-A> refs/pull/124/head + To <URL/of/upstream.git> + - [deleted] refs/pull/123/head + <OID-A>..<OID-B> <COMMIT-B> -> topic + - [deleted] v123 + * [new reference] <COMMIT-A> -> refs/pull/124/head + EOF + test_cmp expect actual && + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output <out >actual && + cat >expect <<-EOF && + <COMMIT-A> refs/heads/master + <COMMIT-B> refs/heads/topic + EOF + test_cmp expect actual +' diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index 759aec9305..dbc724e4c0 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -936,7 +936,7 @@ test_expect_success 'fetching with auto-gc does not lock up' ' git config fetch.unpackLimit 1 && git config gc.autoPackLimit 1 && git config gc.autoDetach false && - GIT_ASK_YESNO="$D/askyesno" git fetch >fetch.out 2>&1 && + GIT_ASK_YESNO="$D/askyesno" git fetch --verbose >fetch.out 2>&1 && test_i18ngrep "Auto packing the repository" fetch.out && ! grep "Should I try again" fetch.out ) diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh index de8e2f1531..bd202ec6f3 100755 --- a/t/t5514-fetch-multiple.sh +++ b/t/t5514-fetch-multiple.sh @@ -108,7 +108,7 @@ test_expect_success 'git fetch --multiple (two remotes)' ' GIT_TRACE=1 git fetch --multiple one two 2>trace && git branch -r > output && test_cmp ../expect output && - grep "built-in: git gc" trace >gc && + grep "built-in: git maintenance" trace >gc && test_line_count = 1 gc ) ' diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 36ad20a849..d11382f769 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -1039,7 +1039,7 @@ test_force_fetch_tag "annotated tag" "-f -a -m'tag message'" test_expect_success 'push --porcelain' ' mk_empty testrepo && echo >.git/foo "To testrepo" && - echo >>.git/foo "* refs/heads/master:refs/remotes/origin/master [new branch]" && + echo >>.git/foo "* refs/heads/master:refs/remotes/origin/master [new reference]" && echo >>.git/foo "Done" && git push >.git/bar --porcelain testrepo refs/heads/master:refs/remotes/origin/master && ( diff --git a/t/t5534-push-signed.sh b/t/t5534-push-signed.sh index 030331f1c5..7e928aff66 100755 --- a/t/t5534-push-signed.sh +++ b/t/t5534-push-signed.sh @@ -273,4 +273,27 @@ test_expect_success GPGSM 'fail without key and heed user.signingkey x509' ' test_cmp expect dst/push-cert-status ' +test_expect_success GPG 'failed atomic push does not execute GPG' ' + prepare_dst && + git -C dst config receive.certnonceseed sekrit && + write_script gpg <<-EOF && + # should check atomic push locally before running GPG. + exit 1 + EOF + test_must_fail env PATH="$TRASH_DIRECTORY:$PATH" git push \ + --signed --atomic --porcelain \ + dst noop ff noff >out 2>&1 && + + test_i18ngrep ! "gpg failed to sign" out && + sed -n -e "/^To dst/,$ p" out >actual && + cat >expect <<-EOF && + To dst + = refs/heads/noop:refs/heads/noop [up to date] + ! refs/heads/ff:refs/heads/ff [rejected] (atomic push failed) + ! refs/heads/noff:refs/heads/noff [rejected] (non-fast-forward) + Done + EOF + test_i18ncmp expect actual +' + test_done diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh index 35408d53fd..781e470aea 100755 --- a/t/t6302-for-each-ref-filter.sh +++ b/t/t6302-for-each-ref-filter.sh @@ -437,8 +437,8 @@ test_expect_success 'check %(if:notequals=<string>)' ' test_cmp expect actual ' -test_expect_success '--merged is incompatible with --no-merged' ' - test_must_fail git for-each-ref --merged HEAD --no-merged HEAD +test_expect_success '--merged is compatible with --no-merged' ' + git for-each-ref --merged HEAD --no-merged HEAD ' test_expect_success 'validate worktree atom' ' diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 74b637deb2..05f411c821 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -2015,8 +2015,8 @@ test_expect_success '--merged can be used in non-list mode' ' test_cmp expect actual ' -test_expect_success '--merged is incompatible with --no-merged' ' - test_must_fail git tag --merged HEAD --no-merged HEAD +test_expect_success '--merged is compatible with --no-merged' ' + git tag --merged HEAD --no-merged HEAD ' test_expect_success '--merged shows merged tags' ' diff --git a/t/t7508-status.sh b/t/t7508-status.sh index 45e1f6ff68..963fed6821 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -814,6 +814,33 @@ test_expect_success 'status -s without relative paths' ' ' +cat >expect <<\EOF + M dir1/modified +A dir2/added +A "file with spaces" +?? dir1/untracked +?? dir2/modified +?? dir2/untracked +?? "file with spaces 2" +?? untracked +EOF + +test_expect_success 'status -s without relative paths' ' + test_when_finished "git rm --cached \"file with spaces\"; rm -f file*" && + >"file with spaces" && + >"file with spaces 2" && + >"expect with spaces" && + git add "file with spaces" && + + git status -s >output && + test_cmp expect output && + + git status -s --ignored >output && + grep "^!! \"expect with spaces\"$" output && + grep -v "^!! " output >output-wo-ignored && + test_cmp expect output-wo-ignored +' + test_expect_success 'dry-run of partial commit excluding new file in index' ' cat >expect <<EOF && On branch master diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh new file mode 100755 index 0000000000..53c883531e --- /dev/null +++ b/t/t7900-maintenance.sh @@ -0,0 +1,65 @@ +#!/bin/sh + +test_description='git maintenance builtin' + +. ./test-lib.sh + +GIT_TEST_COMMIT_GRAPH=0 + +test_expect_success 'help text' ' + test_expect_code 129 git maintenance -h 2>err && + test_i18ngrep "usage: git maintenance run" err && + test_expect_code 128 git maintenance barf 2>err && + test_i18ngrep "invalid subcommand: barf" err && + test_expect_code 129 git maintenance 2>err && + test_i18ngrep "usage: git maintenance" err +' + +test_expect_success 'run [--auto|--quiet]' ' + GIT_TRACE2_EVENT="$(pwd)/run-no-auto.txt" \ + git maintenance run 2>/dev/null && + GIT_TRACE2_EVENT="$(pwd)/run-auto.txt" \ + git maintenance run --auto 2>/dev/null && + GIT_TRACE2_EVENT="$(pwd)/run-no-quiet.txt" \ + git maintenance run --no-quiet 2>/dev/null && + test_subcommand git gc --quiet <run-no-auto.txt && + test_subcommand ! git gc --auto --quiet <run-auto.txt && + test_subcommand git gc --no-quiet <run-no-quiet.txt +' + +test_expect_success 'maintenance.<task>.enabled' ' + git config maintenance.gc.enabled false && + git config maintenance.commit-graph.enabled true && + GIT_TRACE2_EVENT="$(pwd)/run-config.txt" git maintenance run 2>err && + test_subcommand ! git gc --quiet <run-config.txt && + test_subcommand git commit-graph write --split --reachable --no-progress <run-config.txt +' + +test_expect_success 'run --task=<task>' ' + GIT_TRACE2_EVENT="$(pwd)/run-commit-graph.txt" \ + git maintenance run --task=commit-graph 2>/dev/null && + GIT_TRACE2_EVENT="$(pwd)/run-gc.txt" \ + git maintenance run --task=gc 2>/dev/null && + GIT_TRACE2_EVENT="$(pwd)/run-commit-graph.txt" \ + git maintenance run --task=commit-graph 2>/dev/null && + GIT_TRACE2_EVENT="$(pwd)/run-both.txt" \ + git maintenance run --task=commit-graph --task=gc 2>/dev/null && + test_subcommand ! git gc --quiet <run-commit-graph.txt && + test_subcommand git gc --quiet <run-gc.txt && + test_subcommand git gc --quiet <run-both.txt && + test_subcommand git commit-graph write --split --reachable --no-progress <run-commit-graph.txt && + test_subcommand ! git commit-graph write --split --reachable --no-progress <run-gc.txt && + test_subcommand git commit-graph write --split --reachable --no-progress <run-both.txt +' + +test_expect_success 'run --task=bogus' ' + test_must_fail git maintenance run --task=bogus 2>err && + test_i18ngrep "is not a valid task" err +' + +test_expect_success 'run --task duplicate' ' + test_must_fail git maintenance run --task=gc --task=gc 2>err && + test_i18ngrep "cannot be selected multiple times" err +' + +test_done diff --git a/t/t9832-unshelve.sh b/t/t9832-unshelve.sh index e9276c48f4..7194fb2855 100755 --- a/t/t9832-unshelve.sh +++ b/t/t9832-unshelve.sh @@ -29,8 +29,11 @@ test_expect_success 'init depot' ' ) ' +# Create an initial clone, with a commit unrelated to the P4 change +# on HEAD test_expect_success 'initial clone' ' - git p4 clone --dest="$git" //depot/@all + git p4 clone --dest="$git" //depot/@all && + test_commit -C "$git" "unrelated" ' test_expect_success 'create shelved changelist' ' diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index f9904066fe..8d59b90348 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -1628,3 +1628,36 @@ test_path_is_hidden () { case "$("$SYSTEMROOT"/system32/attrib "$1")" in *H*?:*) return 0;; esac return 1 } + +# Check that the given command was invoked as part of the +# trace2-format trace on stdin. +# +# test_subcommand [!] <command> <args>... < <trace> +# +# For example, to look for an invocation of "git upload-pack +# /path/to/repo" +# +# GIT_TRACE2_EVENT=event.log git fetch ... && +# test_subcommand git upload-pack "$PATH" <event.log +# +# If the first parameter passed is !, this instead checks that +# the given command was not called. +# +test_subcommand () { + local negate= + if test "$1" = "!" + then + negate=t + shift + fi + + local expr=$(printf '"%s",' "$@") + expr="${expr%,}" + + if test -n "$negate" + then + ! grep "\[$expr\]" + else + grep "\[$expr\]" + fi +} diff --git a/transport-helper.c b/transport-helper.c index c52c99d829..b573b6c730 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -723,13 +723,61 @@ static int fetch(struct transport *transport, return -1; } +struct push_update_ref_state { + struct ref *hint; + struct ref_push_report *report; + int new_report; +}; + static int push_update_ref_status(struct strbuf *buf, - struct ref **ref, + struct push_update_ref_state *state, struct ref *remote_refs) { char *refname, *msg; int status, forced = 0; + if (starts_with(buf->buf, "option ")) { + struct object_id old_oid, new_oid; + const char *key, *val; + char *p; + + if (!state->hint || !(state->report || state->new_report)) + die(_("'option' without a matching 'ok/error' directive")); + if (state->new_report) { + if (!state->hint->report) { + state->hint->report = xcalloc(1, sizeof(struct ref_push_report)); + state->report = state->hint->report; + } else { + state->report = state->hint->report; + while (state->report->next) + state->report = state->report->next; + state->report->next = xcalloc(1, sizeof(struct ref_push_report)); + state->report = state->report->next; + } + state->new_report = 0; + } + key = buf->buf + 7; + p = strchr(key, ' '); + if (p) + *p++ = '\0'; + val = p; + if (!strcmp(key, "refname")) + state->report->ref_name = xstrdup_or_null(val); + else if (!strcmp(key, "old-oid") && val && + !parse_oid_hex(val, &old_oid, &val)) + state->report->old_oid = oiddup(&old_oid); + else if (!strcmp(key, "new-oid") && val && + !parse_oid_hex(val, &new_oid, &val)) + state->report->new_oid = oiddup(&new_oid); + else if (!strcmp(key, "forced-update")) + state->report->forced_update = 1; + /* Not update remote namespace again. */ + return 1; + } + + state->report = NULL; + state->new_report = 0; + if (starts_with(buf->buf, "ok ")) { status = REF_STATUS_OK; refname = buf->buf + 3; @@ -785,16 +833,16 @@ static int push_update_ref_status(struct strbuf *buf, } } - if (*ref) - *ref = find_ref_by_name(*ref, refname); - if (!*ref) - *ref = find_ref_by_name(remote_refs, refname); - if (!*ref) { + if (state->hint) + state->hint = find_ref_by_name(state->hint, refname); + if (!state->hint) + state->hint = find_ref_by_name(remote_refs, refname); + if (!state->hint) { warning(_("helper reported unexpected status of %s"), refname); return 1; } - if ((*ref)->status != REF_STATUS_NONE) { + if (state->hint->status != REF_STATUS_NONE) { /* * Earlier, the ref was marked not to be pushed, so ignore the ref * status reported by the remote helper if the latter is 'no match'. @@ -803,9 +851,11 @@ static int push_update_ref_status(struct strbuf *buf, return 1; } - (*ref)->status = status; - (*ref)->forced_update |= forced; - (*ref)->remote_status = msg; + if (status == REF_STATUS_OK) + state->new_report = 1; + state->hint->status = status; + state->hint->forced_update |= forced; + state->hint->remote_status = msg; return !(status == REF_STATUS_OK); } @@ -813,37 +863,57 @@ static int push_update_refs_status(struct helper_data *data, struct ref *remote_refs, int flags) { + struct ref *ref; + struct ref_push_report *report; struct strbuf buf = STRBUF_INIT; - struct ref *ref = remote_refs; - int ret = 0; + struct push_update_ref_state state = { remote_refs, NULL, 0 }; for (;;) { - char *private; - if (recvline(data, &buf)) { - ret = 1; - break; + strbuf_release(&buf); + return 1; } - if (!buf.len) break; + push_update_ref_status(&buf, &state, remote_refs); + } + strbuf_release(&buf); - if (push_update_ref_status(&buf, &ref, remote_refs)) - continue; + if (flags & TRANSPORT_PUSH_DRY_RUN || !data->rs.nr || data->no_private_update) + return 0; - if (flags & TRANSPORT_PUSH_DRY_RUN || !data->rs.nr || data->no_private_update) - continue; + /* propagate back the update to the remote namespace */ + for (ref = remote_refs; ref; ref = ref->next) { + char *private; - /* propagate back the update to the remote namespace */ - private = apply_refspecs(&data->rs, ref->name); - if (!private) + if (ref->status != REF_STATUS_OK) continue; - update_ref("update by helper", private, &ref->new_oid, NULL, - 0, 0); - free(private); + + if (!ref->report) { + private = apply_refspecs(&data->rs, ref->name); + if (!private) + continue; + update_ref("update by helper", private, &(ref->new_oid), + NULL, 0, 0); + free(private); + } else { + for (report = ref->report; report; report = report->next) { + private = apply_refspecs(&data->rs, + report->ref_name + ? report->ref_name + : ref->name); + if (!private) + continue; + update_ref("update by helper", private, + report->new_oid + ? report->new_oid + : &(ref->new_oid), + NULL, 0, 0); + free(private); + } + } } - strbuf_release(&buf); - return ret; + return 0; } static void set_common_push_options(struct transport *transport, diff --git a/transport.c b/transport.c index 43e24bf1e5..ffe2115845 100644 --- a/transport.c +++ b/transport.c @@ -432,38 +432,67 @@ int transport_refs_pushed(struct ref *ref) return 0; } -void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int verbose) +static void update_one_tracking_ref(struct remote *remote, char *refname, + struct object_id *new_oid, int deletion, + int verbose) { struct refspec_item rs; - if (ref->status != REF_STATUS_OK && ref->status != REF_STATUS_UPTODATE) - return; - memset(&rs, 0, sizeof(rs)); - rs.src = ref->name; + rs.src = refname; rs.dst = NULL; if (!remote_find_tracking(remote, &rs)) { if (verbose) fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst); - if (ref->deletion) { + if (deletion) delete_ref(NULL, rs.dst, NULL, 0); - } else - update_ref("update by push", rs.dst, &ref->new_oid, + else + update_ref("update by push", rs.dst, new_oid, NULL, 0, 0); free(rs.dst); } } +void transport_update_tracking_ref(struct remote *remote, struct ref *ref, int verbose) +{ + char *refname; + struct object_id *new_oid; + struct ref_push_report *report; + + if (ref->status != REF_STATUS_OK && ref->status != REF_STATUS_UPTODATE) + return; + + report = ref->report; + if (!report) + update_one_tracking_ref(remote, ref->name, &ref->new_oid, + ref->deletion, verbose); + else + for (; report; report = report->next) { + refname = report->ref_name ? (char *)report->ref_name : ref->name; + new_oid = report->new_oid ? report->new_oid : &ref->new_oid; + update_one_tracking_ref(remote, refname, new_oid, + is_null_oid(new_oid), verbose); + } +} + static void print_ref_status(char flag, const char *summary, struct ref *to, struct ref *from, const char *msg, + struct ref_push_report *report, int porcelain, int summary_width) { + const char *to_name; + + if (report && report->ref_name) + to_name = report->ref_name; + else + to_name = to->name; + if (porcelain) { if (from) - fprintf(stdout, "%c\t%s:%s\t", flag, from->name, to->name); + fprintf(stdout, "%c\t%s:%s\t", flag, from->name, to_name); else - fprintf(stdout, "%c\t:%s\t", flag, to->name); + fprintf(stdout, "%c\t:%s\t", flag, to_name); if (msg) fprintf(stdout, "%s (%s)\n", summary, msg); else @@ -477,9 +506,11 @@ static void print_ref_status(char flag, const char *summary, fprintf(stderr, " %s%c %-*s%s ", red, flag, summary_width, summary, reset); if (from) - fprintf(stderr, "%s -> %s", prettify_refname(from->name), prettify_refname(to->name)); + fprintf(stderr, "%s -> %s", + prettify_refname(from->name), + prettify_refname(to_name)); else - fputs(prettify_refname(to->name), stderr); + fputs(prettify_refname(to_name), stderr); if (msg) { fputs(" (", stderr); fputs(msg, stderr); @@ -489,24 +520,52 @@ static void print_ref_status(char flag, const char *summary, } } -static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_width) +static void print_ok_ref_status(struct ref *ref, + struct ref_push_report *report, + int porcelain, int summary_width) { + struct object_id *old_oid; + struct object_id *new_oid; + const char *ref_name; + int forced_update; + + if (report && report->old_oid) + old_oid = report->old_oid; + else + old_oid = &ref->old_oid; + if (report && report->new_oid) + new_oid = report->new_oid; + else + new_oid = &ref->new_oid; + if (report && report->forced_update) + forced_update = report->forced_update; + else + forced_update = ref->forced_update; + if (report && report->ref_name) + ref_name = report->ref_name; + else + ref_name = ref->name; + if (ref->deletion) print_ref_status('-', "[deleted]", ref, NULL, NULL, - porcelain, summary_width); - else if (is_null_oid(&ref->old_oid)) + report, porcelain, summary_width); + else if (is_null_oid(old_oid)) print_ref_status('*', - (starts_with(ref->name, "refs/tags/") ? "[new tag]" : - "[new branch]"), - ref, ref->peer_ref, NULL, porcelain, summary_width); + (starts_with(ref_name, "refs/tags/") + ? "[new tag]" + : (starts_with(ref_name, "refs/heads/") + ? "[new branch]" + : "[new reference]")), + ref, ref->peer_ref, NULL, + report, porcelain, summary_width); else { struct strbuf quickref = STRBUF_INIT; char type; const char *msg; - strbuf_add_unique_abbrev(&quickref, &ref->old_oid, + strbuf_add_unique_abbrev(&quickref, old_oid, DEFAULT_ABBREV); - if (ref->forced_update) { + if (forced_update) { strbuf_addstr(&quickref, "..."); type = '+'; msg = "forced update"; @@ -515,16 +574,17 @@ static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_widt type = ' '; msg = NULL; } - strbuf_add_unique_abbrev(&quickref, &ref->new_oid, + strbuf_add_unique_abbrev(&quickref, new_oid, DEFAULT_ABBREV); print_ref_status(type, quickref.buf, ref, ref->peer_ref, msg, - porcelain, summary_width); + report, porcelain, summary_width); strbuf_release(&quickref); } } -static int print_one_push_status(struct ref *ref, const char *dest, int count, +static int print_one_push_report(struct ref *ref, const char *dest, int count, + struct ref_push_report *report, int porcelain, int summary_width) { if (!count) { @@ -536,65 +596,89 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, switch(ref->status) { case REF_STATUS_NONE: print_ref_status('X', "[no match]", ref, NULL, NULL, - porcelain, summary_width); + report, porcelain, summary_width); break; case REF_STATUS_REJECT_NODELETE: print_ref_status('!', "[rejected]", ref, NULL, "remote does not support deleting refs", - porcelain, summary_width); + report, porcelain, summary_width); break; case REF_STATUS_UPTODATE: print_ref_status('=', "[up to date]", ref, - ref->peer_ref, NULL, porcelain, summary_width); + ref->peer_ref, NULL, + report, porcelain, summary_width); break; case REF_STATUS_REJECT_NONFASTFORWARD: print_ref_status('!', "[rejected]", ref, ref->peer_ref, - "non-fast-forward", porcelain, summary_width); + "non-fast-forward", + report, porcelain, summary_width); break; case REF_STATUS_REJECT_ALREADY_EXISTS: print_ref_status('!', "[rejected]", ref, ref->peer_ref, - "already exists", porcelain, summary_width); + "already exists", + report, porcelain, summary_width); break; case REF_STATUS_REJECT_FETCH_FIRST: print_ref_status('!', "[rejected]", ref, ref->peer_ref, - "fetch first", porcelain, summary_width); + "fetch first", + report, porcelain, summary_width); break; case REF_STATUS_REJECT_NEEDS_FORCE: print_ref_status('!', "[rejected]", ref, ref->peer_ref, - "needs force", porcelain, summary_width); + "needs force", + report, porcelain, summary_width); break; case REF_STATUS_REJECT_STALE: print_ref_status('!', "[rejected]", ref, ref->peer_ref, - "stale info", porcelain, summary_width); + "stale info", + report, porcelain, summary_width); break; case REF_STATUS_REJECT_SHALLOW: print_ref_status('!', "[rejected]", ref, ref->peer_ref, "new shallow roots not allowed", - porcelain, summary_width); + report, porcelain, summary_width); break; case REF_STATUS_REMOTE_REJECT: print_ref_status('!', "[remote rejected]", ref, ref->deletion ? NULL : ref->peer_ref, - ref->remote_status, porcelain, summary_width); + ref->remote_status, + report, porcelain, summary_width); break; case REF_STATUS_EXPECTING_REPORT: print_ref_status('!', "[remote failure]", ref, ref->deletion ? NULL : ref->peer_ref, "remote failed to report status", - porcelain, summary_width); + report, porcelain, summary_width); break; case REF_STATUS_ATOMIC_PUSH_FAILED: print_ref_status('!', "[rejected]", ref, ref->peer_ref, - "atomic push failed", porcelain, summary_width); + "atomic push failed", + report, porcelain, summary_width); break; case REF_STATUS_OK: - print_ok_ref_status(ref, porcelain, summary_width); + print_ok_ref_status(ref, report, porcelain, summary_width); break; } return 1; } +static int print_one_push_status(struct ref *ref, const char *dest, int count, + int porcelain, int summary_width) +{ + struct ref_push_report *report; + int n = 0; + + if (!ref->report) + return print_one_push_report(ref, dest, count, + NULL, porcelain, summary_width); + + for (report = ref->report; report; report = report->next) + print_one_push_report(ref, dest, count + n++, + report, porcelain, summary_width); + return n; +} + static int measure_abbrev(const struct object_id *oid, int sofar) { char hex[GIT_MAX_HEXSZ + 1]; diff --git a/wt-status.c b/wt-status.c index d9872d543b..160d14970a 100644 --- a/wt-status.c +++ b/wt-status.c @@ -259,8 +259,6 @@ static void wt_longstatus_print_trailer(struct wt_status *s) status_printf_ln(s, color(WT_STATUS_HEADER, s), "%s", ""); } -#define quote_path quote_path_relative - static const char *wt_status_unmerged_status_string(int stagemask) { switch (stagemask) { @@ -338,7 +336,7 @@ static void wt_longstatus_print_unmerged_data(struct wt_status *s, memset(padding, ' ', label_width); } - one = quote_path(it->string, s->prefix, &onebuf); + one = quote_path(it->string, s->prefix, &onebuf, 0); status_printf(s, color(WT_STATUS_HEADER, s), "\t"); how = wt_status_unmerged_status_string(d->stagemask); @@ -404,8 +402,8 @@ static void wt_longstatus_print_change_data(struct wt_status *s, if (d->rename_status == status) one_name = d->rename_source; - one = quote_path(one_name, s->prefix, &onebuf); - two = quote_path(two_name, s->prefix, &twobuf); + one = quote_path(one_name, s->prefix, &onebuf, 0); + two = quote_path(two_name, s->prefix, &twobuf, 0); status_printf(s, color(WT_STATUS_HEADER, s), "\t"); what = wt_status_diff_status_string(status); @@ -966,7 +964,7 @@ static void wt_longstatus_print_other(struct wt_status *s, struct string_list_item *it; const char *path; it = &(l->items[i]); - path = quote_path(it->string, s->prefix, &buf); + path = quote_path(it->string, s->prefix, &buf, 0); if (column_active(s->colopts)) { string_list_append(&output, path); continue; @@ -1850,7 +1848,7 @@ static void wt_shortstatus_unmerged(struct string_list_item *it, } else { struct strbuf onebuf = STRBUF_INIT; const char *one; - one = quote_path(it->string, s->prefix, &onebuf); + one = quote_path(it->string, s->prefix, &onebuf, QUOTE_PATH_QUOTE_SP); printf(" %s\n", one); strbuf_release(&onebuf); } @@ -1879,21 +1877,12 @@ static void wt_shortstatus_status(struct string_list_item *it, const char *one; if (d->rename_source) { - one = quote_path(d->rename_source, s->prefix, &onebuf); - if (*one != '"' && strchr(one, ' ') != NULL) { - putchar('"'); - strbuf_addch(&onebuf, '"'); - one = onebuf.buf; - } + one = quote_path(d->rename_source, s->prefix, &onebuf, + QUOTE_PATH_QUOTE_SP); printf("%s -> ", one); strbuf_release(&onebuf); } - one = quote_path(it->string, s->prefix, &onebuf); - if (*one != '"' && strchr(one, ' ') != NULL) { - putchar('"'); - strbuf_addch(&onebuf, '"'); - one = onebuf.buf; - } + one = quote_path(it->string, s->prefix, &onebuf, QUOTE_PATH_QUOTE_SP); printf("%s\n", one); strbuf_release(&onebuf); } @@ -1907,7 +1896,7 @@ static void wt_shortstatus_other(struct string_list_item *it, } else { struct strbuf onebuf = STRBUF_INIT; const char *one; - one = quote_path(it->string, s->prefix, &onebuf); + one = quote_path(it->string, s->prefix, &onebuf, QUOTE_PATH_QUOTE_SP); color_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), "%s", sign); printf(" %s\n", one); strbuf_release(&onebuf); @@ -2224,9 +2213,9 @@ static void wt_porcelain_v2_print_changed_entry( */ sep_char = '\t'; eol_char = '\n'; - path = quote_path(it->string, s->prefix, &buf); + path = quote_path(it->string, s->prefix, &buf, 0); if (d->rename_source) - path_from = quote_path(d->rename_source, s->prefix, &buf_from); + path_from = quote_path(d->rename_source, s->prefix, &buf_from, 0); } if (path_from) @@ -2312,7 +2301,7 @@ static void wt_porcelain_v2_print_unmerged_entry( if (s->null_termination) path_index = it->string; else - path_index = quote_path(it->string, s->prefix, &buf_index); + path_index = quote_path(it->string, s->prefix, &buf_index, 0); fprintf(s->fp, "%c %s %s %06o %06o %06o %06o %s %s %s %s%c", unmerged_prefix, key, submodule_token, @@ -2345,7 +2334,7 @@ static void wt_porcelain_v2_print_other( path = it->string; eol_char = '\0'; } else { - path = quote_path(it->string, s->prefix, &buf); + path = quote_path(it->string, s->prefix, &buf, 0); eol_char = '\n'; } |
