diff options
267 files changed, 7911 insertions, 3036 deletions
diff --git a/Documentation/RelNotes/1.8.3.txt b/Documentation/RelNotes/1.8.3.txt new file mode 100644 index 0000000000..7799cec1dd --- /dev/null +++ b/Documentation/RelNotes/1.8.3.txt @@ -0,0 +1,387 @@ +Git v1.8.3 Release Notes +======================== + +Backward compatibility notes (for Git 2.0) +------------------------------------------ + +When "git push [$there]" does not say what to push, we have used the +traditional "matching" semantics so far (all your branches were sent +to the remote as long as there already are branches of the same name +over there). In Git 2.0, the default will change to the "simple" +semantics that pushes the current branch to the branch with the same +name, only when the current branch is set to integrate with that +remote branch. There is a user preference configuration variable +"push.default" to change this. If you are an old-timer who is used +to the "matching" semantics, you can set it to "matching" to keep the +traditional behaviour. If you want to live in the future early, +you can set it to "simple" today without waiting for Git 2.0. + +When "git add -u" and "git add -A", that does not specify what paths +to add on the command line is run from inside a subdirectory, these +commands will operate on the entire tree in Git 2.0 for consistency +with "git commit -a" and other commands. Because there will be no +mechanism to make "git add -u" behave as if "git add -u .", it is +important for those who are used to "git add -u" (without pathspec) +updating the index only for paths in the current subdirectory to start +training their fingers to explicitly say "git add -u ." when they mean +it before Git 2.0 comes. A warning is issued when these commands are +run without a pathspec and when you have local changes outside the +current directory, because the behaviour in Git 2.0 will be different +from today's version in such a situation. + + +Updates since v1.8.2 +-------------------- + +UI, Workflows & Features + + * "git branch --vv" learned to paint the name of the branch it + integrates with in a different color (color.branch.upstream, + which defaults to blue). + + * "git count-objects" learned "--human-readable" aka "-H" option to + show various large numbers in Ki/Mi/GiB scaled as necessary. + + * "git cherry-pick $blob" and "git cherry-pick $tree" are nonsense, + and a more readable error message e.g. "can't cherry-pick a tree" + is given (we used to say "expected exactly one commit"). + + * The "--annotate" option to "git send-email" can be turned on (or + off) by default with sendemail.annotate configuration variable (you + can use --no-annotate from the command line to override it). + + * The "--cover-letter" option to "git format-patch" can be turned on + (or off) by default with format.coverLetter configuration + variable. By setting it to 'auto', you can turn it on only for a + series with two or more patches. + + * The bash completion support (in contrib/) learned that cherry-pick + takes a few more options than it already knew about. + + * "git help" learned "-g" option to show the list of guides just like + list of commands are given with "-a". + + * A triangular "pull from one place, push to another place" workflow + is supported better by new remote.pushdefault (overrides the + "origin" thing) and branch.*.pushremote (overrides the + branch.*.remote) configuration variables. + + * "git status" learned to report that you are in the middle of a + revert session, just like it does for a cherry-pick and a bisect + session. + + * The handling by "git branch --set-upstream-to" against various forms + of erroneous inputs was suboptimal and has been improved. + + * When the interactive access to git-shell is not enabled, it issues + a message meant to help the system administrator to enable it. + An explicit way to help the end users who connect to the service by + issuing custom messages to refuse such an access has been added. + + * In addition to the case where the user edits the log message with + the "e)dit" option of "am -i", replace the "Applying: this patch" + message with the final log message contents after applymsg hook + munges it. + + * "git status" suggests users to look into using --untracked=no option + when it takes too long. + + * "git status" shows a bit more information to "git status" during a + rebase/bisect session. + + * "git fetch" learned to fetch a commit at the tip of an unadvertised + ref by specifying a raw object name from the command line when the + server side supports this feature. + + * Output from "git log --graph" works better with submodule log + output now. + + * "git count-objects -v" learned to report leftover temporary + packfiles and other garbage in the object store. + + * A new read-only credential helper (in contrib/) to interact with + the .netrc/.authinfo files has been added. + + * "git send-email" can be used with the credential helper system. + + * There was no Porcelain way to say "I no longer am interested in + this submodule", once you express your interest in a submodule with + "submodule init". "submodule deinit" is the way to do so. + + * "git pull --rebase" learned to pass "-v/-q" options to underlying + "git rebase". + + * The new "--follow-tags" option tells "git push" to push relevant + annotated tags when pushing branches out. + + * "git merge" and "git pull" can optionally be told to inspect and + reject when merging a commit that does not carry a trusted GPG + signature. + + * "git mergetool" now feeds files to the "p4merge" backend in the + order that matches the p4 convention, where "theirs" is usually + shown on the left side, which is the opposite from other backend + expects. + + * "show/log" now honors gpg.program configuration just like other + parts of the code that use GnuPG. + + * "git log" that shows the difference between the parent and the + child has been optimized somewhat. + + * "git difftool" allows the user to write into the temporary files + being shown; if the user makes changes to the working tree at the + same time, one of the changes has to be lost in such a case, but it + tells the user what happened and refrains from overwriting the copy + in the working tree. + + * There was no good way to ask "I have a random string that came from + outside world. I want to turn it into a 40-hex object name while + making sure such an object exists". A new peeling suffix ^{object} + can be used for that purpose, together with "rev-parse --verify". + + +Performance, Internal Implementation, etc. + + * Updates for building under msvc. + + * The stack footprint of some codepaths that access an object from a + pack has been shrunk. + + * The logic to coalesce the same lines removed from the parents in + the output from "diff -c/--cc" has been updated, but with an O(n^2) + complexity, so this might turn out to be undesirable. + + * The code to enforce permission bits on files in $GIT_DIR/ for + shared repositories have been simplified. + + * A few codepaths knew how much data they need to put in the + hashtables they use upfront, but still started from a small table + repeatedly growing and rehashing. + + * The API to walk reflog entries from the latest to older, which was + necessary for operations such as "git checkout -", was cumbersome + to use correctly and also inefficient. + + * Codepaths that inspect log-message-to-be and decide when to add a + new Signed-off-by line in various commands have been consolidated. + + * The pkt-line API, implementation and its callers have been cleaned + up to make them more robust. + + * Cygwin port has a faster-but-lying lstat(2) emulation whose + incorrectness does not matter in practice except for a few + codepaths, and setting permission bits to directories is a codepath + that needs to use a more correct one. + + * "git checkout" had repeated pathspec matches on the same paths, + which have been consolidated. Also a bug in "git checkout dir/" + that is started from an unmerged index has been fixed. + + * A few bugfixes to "git rerere" working on corner case merge + conflicts have been applied. + + +Also contains various documentation updates and code clean-ups. + + +Fixes since v1.8.2 +------------------ + +Unless otherwise noted, all the fixes since v1.8.2 in the maintenance +track are contained in this release (see release notes to them for +details). + + * When "upload-pack" fails while generating a pack in response to + "git fetch" (or "git clone"), the receiving side mistakenly said + there was a programming error to trigger the die handler + recursively. + (merge 1ece66b jk/a-thread-only-dies-once later to maint). + + * "rev-list --stdin" and friends kept bogus pointers into input + buffer around as human readble object names. This was not a huge + problem but was exposed by a new change that uses these names in + error output. + (merge 70d26c6 tr/copy-revisions-from-stdin later to maint). + + * Smart-capable HTTP servers were not restricted via the + GIT_NAMESPACE mechanism when talking with commit-walker clients, + like they do when talking with smart HTTP clients. + (merge 6130f86 jk/http-dumb-namespaces later to maint). + + * "git merge-tree" did not omit a merge result that is identical to + "our" side in certain cases. + (merge aacecc3 jk/merge-tree-added-identically later to maint). + + * Perl scripts like "git-svn" closed (not redirecting to /dev/null) + the standard error stream, which is not a very smart thing to do. + Later open may return file descriptor #2 for unrelated purpose, and + error reporting code may write into them. + (merge a749c0b tr/perl-keep-stderr-open later to maint). + + * "git show-branch" was not prepared to show a very long run of + ancestor operators e.g. foobar^2~2^2^2^2...^2~4 correctly. + (merge aaa07e3 jk/show-branch-strbuf later to maint). + + * "git diff --diff-algorithm algo" is also understood as "git diff + --diff-algorithm=algo". + (merge 0895c6d jk/diff-algo-finishing-touches later to maint). + + * The new core.commentchar configuration was not applied to a few + places. + (merge 89c3bbd rt/commentchar-fmt-merge-msg later to maint). + + * "git bundle" did not like a bundle created using a commit without + any message as its one of the prerequistes. + (merge 5446e33 lf/bundle-with-tip-wo-message later to maint). + + * "git log -S/-G" started paying attention to textconv filter, but + there was no way to disable this. Make it honor --no-textconv + option. + (merge 61690bf sr/log-SG-no-textconv later to maint). + + * When used with "-d temporary-directory" option, "git filter-branch" + failed to come back to the original working tree to perform the + final clean-up procedure. + (merge 9727601 jk/filter-branch-come-back-to-original later to maint). + + * "git merge $(git rev-parse v1.8.2)" behaved quite differently from + "git merge v1.8.2", as if v1.8.2 were written as v1.8.2^0 and did + not pay much attention to the annotated tag payload. Make the code + notice the type of the tag object, in addition to the dwim_ref() + based classification the current code uses (i.e. the name appears + in refs/tags/) to decide when to special case merging of tags. + (merge a38d3d7 jc/merge-tag-object later to maint). + + * Fix 1.8.1.x regression that stopped matching "dir" (without + trailing slash) to a directory "dir". + (merge efa5f82 jc/directory-attrs-regression-fix later to maint-1.8.1). + + * "git apply --whitespace=fix" was not prepared to see a line getting + longer after fixing whitespaces (e.g. tab-in-indent aka Python). + (merge 329b26e jc/apply-ws-fix-tab-in-indent later to maint-1.8.1). + + * The prompt string generator (in contrib/completion/) did not notice + when we are in a middle of a "git revert" session. + (merge 3ee4452 rr/prompt-revert-head later to maint). + + * "submodule summary --summary-limit" option did not support + "--option=value" form. + (merge 862ae6c rs/submodule-summary-limit later to maint). + + * "index-pack --fix-thin" used an uninitialized value to compute + delta depths of objects it appends to the resulting pack. + (merge 57165db jk/index-pack-correct-depth-fix later to maint). + + * "index-pack --verify-stat" used a few counters outside protection + of mutex, possibly showing incorrect numbers. + (merge 8f82aad nd/index-pack-threaded-fixes later to maint). + + * The code to keep track of what directory names are known to Git on + platforms with case insensitive filesystems can get confused upon a + hash collision between these pathnames and looped forever. + + * Annotated tags outside refs/tags/ hierarchy were not advertised + correctly to the ls-remote and fetch with recent version of Git. + + * Recent optimization broke shallow clones. + (merge f59de5d jk/peel-ref later to maint). + + * "git cmd -- ':(top'" was not diagnosed as an invalid syntax, and + instead the parser kept reading beyond the end of the string. + + * "git tag -f <tag>" always said "Updated tag '<tag>'" even when + creating a new tag (i.e. not overwriting nor updating). + + * "git p4" did not behave well when the path to the root of the P4 + client was not its real path. + (merge bbd8486 pw/p4-symlinked-root later to maint). + + * "git archive" reports a failure when asked to create an archive out + of an empty tree. It would be more intuitive to give an empty + archive back in such a case. + + * When "format-patch" quoted a non-ascii strings on the header files, + it incorrectly applied rfc2047 and chopped a single character in + the middle of it. + + * An aliased command spawned from a bare repository that does not say + it is bare with "core.bare = yes" is treated as non-bare by mistake. + + * In "git reflog expire", REACHABLE bit was not cleared from the + correct objects. + + * The logic used by "git diff -M --stat" to shorten the names of + files before and after a rename did not work correctly when the + common prefix and suffix between the two filenames overlapped. + + * The "--match=<pattern>" option of "git describe", when used with + "--all" to allow refs that are not annotated tags to be used as a + base of description, did not restrict the output from the command + to those that match the given pattern. + + * Clarify in the documentation "what" gets pushed to "where" when the + command line to "git push" does not say these explicitly. + + * The "--color=<when>" argument to the commands in the diff family + was described poorly. + + * The arguments given to pre-rebase hook were not documented. + + * The v4 index format was not documented. + + * The "--match=<pattern>" argument "git describe" takes uses glob + pattern but it wasn't obvious from the documentation. + + * Some sources failed to compile on systems that lack NI_MAXHOST in + their system header (e.g. z/OS). + + * Add an example use of "--env-filter" in "filter-branch" + documentation. + + * "git bundle verify" did not say "records a complete history" for a + bundle that does not have any prerequisites. + + * In the v1.8.0 era, we changed symbols that do not have to be global + to file scope static, but a few functions in graph.c were used by + CGit from sideways bypassing the entry points of the API the + in-tree users use. + + * "git update-index -h" did not do the usual "-h(elp)" thing. + + * "git index-pack" had a buffer-overflow while preparing an + informational message when the translated version of it was too + long. + + * 'git commit -m "$msg"' used to add an extra newline even when + $msg already ended with one. + + * The SSL peer verification done by "git imap-send" did not ask for + Server Name Indication (RFC 4366), failing to connect SSL/TLS + sites that serve multiple hostnames on a single IP. + + * perl/Git.pm::cat_blob slurped everything in core only to write it + out to a file descriptor, which was not a very smart thing to do. + + * "git branch" did not bother to check nonsense command line + parameters and issue errors in many cases. + + * Verification of signed tags were not done correctly when not in C + or en/US locale. + + * Some platforms and users spell UTF-8 differently; retry with the + most official "UTF-8" when the system does not understand the + user-supplied encoding name that are the common alternative + spellings of UTF-8. + + * When export-subst is used, "zip" output recorded incorrect + size of the file. + + * "git am $maildir/" applied messages in an unexpected order; sort + filenames read from the maildir/ in a way that is more likely to + sort messages in the order the writing MUA meant to, by sorting + numeric segment in numeric order and non-numeric segment in + alphabetical order. + + * "git submodule update", when recursed into sub-submodules, did not + accumulate the prefix paths. diff --git a/Documentation/cat-texi.perl b/Documentation/cat-texi.perl index 828ec62554..87437f8a95 100755 --- a/Documentation/cat-texi.perl +++ b/Documentation/cat-texi.perl @@ -12,6 +12,7 @@ while (<STDIN>) { push @menu, $1; } s/\(\@pxref{\[(URLS|REMOTES)\]}\)//; + s/\@anchor\{[^{}]*\}//g; print TMP; } close TMP; diff --git a/Documentation/config.txt b/Documentation/config.txt index bc750d5794..29559c88af 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -727,9 +727,22 @@ branch.autosetuprebase:: This option defaults to never. branch.<name>.remote:: - When in branch <name>, it tells 'git fetch' and 'git push' which - remote to fetch from/push to. It defaults to `origin` if no remote is - configured. `origin` is also used if you are not on any branch. + When on branch <name>, it tells 'git fetch' and 'git push' + which remote to fetch from/push to. The remote to push to + may be overridden with `remote.pushdefault` (for all branches). + The remote to push to, for the current branch, may be further + overridden by `branch.<name>.pushremote`. If no remote is + configured, or if you are not on any branch, it defaults to + `origin` for fetching and `remote.pushdefault` for pushing. + +branch.<name>.pushremote:: + When on branch <name>, it overrides `branch.<name>.remote` for + pushing. It also overrides `remote.pushdefault` for pushing + from branch <name>. When you pull from one place (e.g. your + upstream) and push to another place (e.g. your own publishing + repository), you would want to set `remote.pushdefault` to + specify the remote to push to for all branches, and use this + option to override it for a specific branch. branch.<name>.merge:: Defines, together with branch.<name>.remote, the upstream branch @@ -794,7 +807,8 @@ color.branch:: color.branch.<slot>:: Use customized color for branch coloration. `<slot>` is one of `current` (the current branch), `local` (a local branch), - `remote` (a remote-tracking branch in refs/remotes/), `plain` (other + `remote` (a remote-tracking branch in refs/remotes/), + `upstream` (upstream tracking branch), `plain` (other refs). + The value for these configuration variables is a list of colors (at most @@ -1096,6 +1110,11 @@ format.signoff:: the rights to submit this work under the same open source license. Please see the 'SubmittingPatches' document for further discussion. +format.coverLetter:: + A boolean that controls whether to generate a cover-letter when + format-patch is invoked, but in addition can be set to "auto", to + generate a cover-letter only when there's more than one patch. + filter.<driver>.clean:: The command which is used to convert the content of a worktree file to a blob upon checkin. See linkgit:gitattributes[5] for @@ -1447,6 +1466,14 @@ http.sslCAPath:: with when fetching or pushing over HTTPS. Can be overridden by the 'GIT_SSL_CAPATH' environment variable. +http.sslTry:: + Attempt to use AUTH SSL/TLS and encrypted data transfers + when connecting via regular FTP protocol. This might be needed + if the FTP server requires it for security reasons or you wish + to connect securely whenever remote FTP server supports it. + Default is false since it might trigger certificate verification + errors on misconfigured servers. + http.maxRequests:: How many HTTP requests to launch in parallel. Can be overridden by the 'GIT_HTTP_MAX_REQUESTS' environment variable. Default is 5. @@ -1898,6 +1925,11 @@ receive.updateserverinfo:: If set to true, git-receive-pack will run git-update-server-info after receiving data from git-push and updating refs. +remote.pushdefault:: + The remote to push to by default. Overrides + `branch.<name>.remote` for all branches, and is overridden by + `branch.<name>.pushremote` for specific branches. + remote.<name>.url:: The URL of a remote repository. See linkgit:git-fetch[1] or linkgit:git-push[1]. @@ -1998,6 +2030,7 @@ sendemail.<identity>.*:: sendemail.aliasesfile:: sendemail.aliasfiletype:: +sendemail.annotate:: sendemail.bcc:: sendemail.cc:: sendemail.cccmd:: @@ -2123,7 +2156,13 @@ uploadpack.hiderefs:: are under the hierarchies listed on the value of this variable is excluded, and is hidden from `git ls-remote`, `git fetch`, etc. An attempt to fetch a hidden ref by `git - fetch` will fail. + fetch` will fail. See also `uploadpack.allowtipsha1inwant`. + +uploadpack.allowtipsha1inwant:: + When `uploadpack.hiderefs` is in effect, allow `upload-pack` + to accept a fetch request that asks for an object at the tip + of a hidden ref (by default, such a request is rejected). + see also `uploadpack.hiderefs`. url.<base>.insteadOf:: Any URL that starts with this value will be rewritten to diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt index b4c2e24849..250e5228a3 100644 --- a/Documentation/git-archive.txt +++ b/Documentation/git-archive.txt @@ -56,7 +56,8 @@ OPTIONS Write the archive to <file> instead of stdout. --worktree-attributes:: - Look for attributes in .gitattributes in working directory too. + Look for attributes in .gitattributes files in the working tree + as well (see <<ATTRIBUTES>>). <extra>:: This can be any options that the archiver backend understands. @@ -120,6 +121,7 @@ tar.<format>.remote:: user-defined formats, but true for the "tar.gz" and "tgz" formats. +[[ATTRIBUTES]] ATTRIBUTES ---------- diff --git a/Documentation/git-commit-tree.txt b/Documentation/git-commit-tree.txt index 86ef56e7c8..cafdc9642d 100644 --- a/Documentation/git-commit-tree.txt +++ b/Documentation/git-commit-tree.txt @@ -10,7 +10,9 @@ SYNOPSIS -------- [verse] 'git commit-tree' <tree> [(-p <parent>)...] < changelog -'git commit-tree' [(-p <parent>)...] [(-m <message>)...] [(-F <file>)...] <tree> +'git commit-tree' [(-p <parent>)...] [-S[<keyid>]] [(-m <message>)...] + [(-F <file>)...] <tree> + DESCRIPTION ----------- @@ -52,6 +54,9 @@ OPTIONS Read the commit log message from the given file. Use `-` to read from the standard input. +-S[<keyid>]:: + GPG-sign commit. + Commit Information ------------------ diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 05f8297368..9b1be5581d 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -207,14 +207,15 @@ variable (see linkgit:git-config[1]). without changing its commit message. --amend:: - Used to amend the tip of the current branch. Prepare the tree - object you would want to replace the latest commit as usual - (this includes the usual -i/-o and explicit paths), and the - commit log editor is seeded with the commit message from the - tip of the current branch. The commit you create replaces the - current tip -- if it was a merge, it will have the parents of - the current tip as parents -- so the current top commit is - discarded. + Replace the tip of the current branch by creating a new + commit. The recorded tree is prepared as usual (including + the effect of the `-i` and `-o` options and explicit + pathspec), and the message from the original commit is used + as the starting point, instead of an empty message, when no + other message is specified from the command line via options + such as `-m`, `-F`, `-c`, etc. The new commit has the same + parents and author as the current one (the `--reset-author` + option can countermand this). + -- It is a rough equivalent for: diff --git a/Documentation/git-count-objects.txt b/Documentation/git-count-objects.txt index 23c80cea64..b300e846f1 100644 --- a/Documentation/git-count-objects.txt +++ b/Documentation/git-count-objects.txt @@ -8,7 +8,7 @@ git-count-objects - Count unpacked number of objects and their disk consumption SYNOPSIS -------- [verse] -'git count-objects' [-v] +'git count-objects' [-v] [-H | --human-readable] DESCRIPTION ----------- @@ -20,11 +20,29 @@ OPTIONS ------- -v:: --verbose:: - In addition to the number of loose objects and disk - space consumed, it reports the number of in-pack - objects, number of packs, disk space consumed by those packs, - and number of objects that can be removed by running - `git prune-packed`. + Report in more detail: ++ +count: the number of loose objects ++ +size: disk space consumed by loose objects, in KiB (unless -H is specified) ++ +in-pack: the number of in-pack objects ++ +size-pack: disk space consumed by the packs, in KiB (unless -H is specified) ++ +prune-packable: the number of loose objects that are also present in +the packs. These objects could be pruned using `git prune-packed`. ++ +garbage: the number of files in object database that are not valid +loose objects nor valid packs ++ +size-garbage: disk space consumed by garbage files, in KiB (unless -H is +specified) + +-H:: +--human-readable:: + +Print sizes in human readable format GIT --- diff --git a/Documentation/git-credential.txt b/Documentation/git-credential.txt index 472f00f607..7da0f13a5c 100644 --- a/Documentation/git-credential.txt +++ b/Documentation/git-credential.txt @@ -56,7 +56,7 @@ For example, if we want a password for `https://example.com/foo.git`, we might generate the following credential description (don't forget the blank line at the end; it tells `git credential` that the application finished feeding all the -infomation it has): +information it has): protocol=https host=example.com diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index 77da564134..bfb106cccd 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -147,6 +147,13 @@ OPTIONS Giving these options is an error when used with `--inetd`; use the facility of inet daemon to achieve the same before spawning 'git daemon' if needed. ++ +Like many programs that switch user id, the daemon does not reset +environment variables such as `$HOME` when it runs git programs, +e.g. `upload-pack` and `receive-pack`. When using this option, you +may also want to set and export `HOME` to point at the home +directory of `<user>` before starting the daemon, and make sure any +Git configuration files in that directory are readable by `<user>`. --enable=<service>:: --disable=<service>:: diff --git a/Documentation/git-difftool.txt b/Documentation/git-difftool.txt index e0e12e9470..8361e6e4e3 100644 --- a/Documentation/git-difftool.txt +++ b/Documentation/git-difftool.txt @@ -72,10 +72,12 @@ with custom merge tool commands and has the same value as `$MERGED`. --symlinks:: --no-symlinks:: 'git difftool''s default behavior is create symlinks to the - working tree when run in `--dir-diff` mode. + working tree when run in `--dir-diff` mode and the right-hand + side of the comparison yields the same content as the file in + the working tree. + - Specifying `--no-symlinks` instructs 'git difftool' to create - copies instead. `--no-symlinks` is the default on Windows. +Specifying `--no-symlinks` instructs 'git difftool' to create copies +instead. `--no-symlinks` is the default on Windows. -x <command>:: --extcmd=<command>:: diff --git a/Documentation/git-fast-export.txt b/Documentation/git-fast-export.txt index d6487e1ce0..feab7a3e4e 100644 --- a/Documentation/git-fast-export.txt +++ b/Documentation/git-fast-export.txt @@ -66,6 +66,8 @@ produced incorrect results if you gave these options. incremental runs. As <file> is only opened and truncated at completion, the same path can also be safely given to \--import-marks. + The file will not be written if no new object has been + marked/exported. --import-marks=<file>:: Before processing any input, load the marks specified in diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 3a62f50eda..39118774af 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -20,7 +20,7 @@ SYNOPSIS [--ignore-if-in-upstream] [--subject-prefix=Subject-Prefix] [(--reroll-count|-v) <n>] [--to=<email>] [--cc=<email>] - [--cover-letter] [--quiet] [--notes[=<ref>]] + [--[no-]cover-letter] [--quiet] [--notes[=<ref>]] [<common diff options>] [ <since> | <revision range> ] @@ -195,7 +195,7 @@ will want to ensure that threading is disabled for `git send-email`. `Cc:`, and custom) headers added so far from config or command line. ---cover-letter:: +--[no-]cover-letter:: In addition to the patches, generate a cover letter file containing the shortlog and the overall diffstat. You can fill in a description in the file before sending it out. @@ -260,6 +260,7 @@ attachments, and sign off patches with configuration variables. cc = <email> attach [ = mime-boundary-string ] signoff = true + coverletter = auto ------------ diff --git a/Documentation/git-help.txt b/Documentation/git-help.txt index e07b6dc19a..b21e9d79be 100644 --- a/Documentation/git-help.txt +++ b/Documentation/git-help.txt @@ -8,31 +8,45 @@ git-help - Display help information about Git SYNOPSIS -------- [verse] -'git help' [-a|--all|-i|--info|-m|--man|-w|--web] [COMMAND] +'git help' [-a|--all] [-g|--guide] + [-i|--info|-m|--man|-w|--web] [COMMAND|GUIDE] DESCRIPTION ----------- -With no options and no COMMAND given, the synopsis of the 'git' +With no options and no COMMAND or GUIDE given, the synopsis of the 'git' command and a list of the most commonly used Git commands are printed on the standard output. -If the option '--all' or '-a' is given, then all available commands are +If the option '--all' or '-a' is given, all available commands are printed on the standard output. -If a Git subcommand is named, a manual page for that subcommand is brought -up. The 'man' program is used by default for this purpose, but this -can be overridden by other options or configuration variables. +If the option '--guide' or '-g' is given, a list of the useful +Git guides is also printed on the standard output. + +If a command, or a guide, is given, a manual page for that command or +guide is brought up. The 'man' program is used by default for this +purpose, but this can be overridden by other options or configuration +variables. Note that `git --help ...` is identical to `git help ...` because the former is internally converted into the latter. +To display the linkgit:git[1] man page, use `git help git`. + +This page can be displayed with 'git help help' or `git help --help` + OPTIONS ------- -a:: --all:: Prints all the available commands on the standard output. This - option supersedes any other option. + option overrides any given command or guide name. + +-g:: +--guides:: + Prints a list of useful guides on the standard output. This + option overrides any given command or guide name. -i:: --info:: diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 577d201c00..eb2883c94c 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -9,7 +9,7 @@ git-push - Update remote refs along with associated objects SYNOPSIS -------- [verse] -'git push' [--all | --mirror | --tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>] +'git push' [--all | --mirror | --tags] [--follow-tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>] [--repo=<repository>] [-f | --force] [--prune] [-v | --verbose] [-u | --set-upstream] [<repository> [<refspec>...]] @@ -117,6 +117,12 @@ already exists on the remote side. addition to refspecs explicitly listed on the command line. +--follow-tags:: + Push all the refs that would be pushed without this option, + and also push annotated tags in `refs/tags` that are missing + from the remote but are pointing at committish that are + reachable from the refs being pushed. + --receive-pack=<git-receive-pack>:: --exec=<git-receive-pack>:: Path to the 'git-receive-pack' program on the remote diff --git a/Documentation/git-remote-ext.txt b/Documentation/git-remote-ext.txt index 58b7facb09..8cfc748ae2 100644 --- a/Documentation/git-remote-ext.txt +++ b/Documentation/git-remote-ext.txt @@ -86,7 +86,7 @@ begins with `ext::`. Examples: edit .ssh/config. "ext::socat -t3600 - ABSTRACT-CONNECT:/git-server %G/somerepo":: - Represents repository with path /somerepo accessable over + Represents repository with path /somerepo accessible over git protocol at abstract namespace address /git-server. "ext::git-server-alias foo %G/repo":: diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 10a116faf8..1f9ed6cfd2 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -60,8 +60,19 @@ OPTIONS instead. --verify:: - The parameter given must be usable as a single, valid - object name. Otherwise barf and abort. + Verify that exactly one parameter is provided, and that it + can be turned into a raw 20-byte SHA-1 that can be used to + access the object database. If so, emit it to the standard + output; otherwise, error out. ++ +If you want to make sure that the output actually names an object in +your object database and/or can be used as a specific type of object +you require, you can add "^{type}" peeling operator to the parmeter. +For example, `git rev-parse "$VAR^{commit}"` will make sure `$VAR` +names an existing object that is a commit-ish (i.e. a commit, or an +annotated tag that points at a commit). To make sure that `$VAR` +names an existing object of any type, `git rev-parse "$VAR^{object}"` +can be used. -q:: --quiet:: @@ -308,12 +319,12 @@ $ git rev-parse --verify HEAD * Print the commit object name from the revision in the $REV shell variable: + ------------ -$ git rev-parse --verify $REV +$ git rev-parse --verify $REV^{commit} ------------ + This will error out if $REV is empty or not a valid revision. -* Same as above: +* Similar to above: + ------------ $ git rev-parse --default master --verify $REV diff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt index 92bac27e05..1d876c2619 100644 --- a/Documentation/git-rm.txt +++ b/Documentation/git-rm.txt @@ -149,6 +149,10 @@ files that aren't ignored are present in the submodules work tree. Ignored files are deemed expendable and won't stop a submodule's work tree from being removed. +If you only want to remove the local checkout of a submodule from your +work tree without committing the removal, +use linkgit:git-submodule[1] `deinit` instead. + EXAMPLES -------- `git rm Documentation/\*.txt`:: diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index 44a1f7c4e8..40a9a9abc1 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -45,8 +45,9 @@ Composing ~~~~~~~~~ --annotate:: - Review and edit each patch you're about to send. See the - CONFIGURATION section for 'sendemail.multiedit'. + Review and edit each patch you're about to send. Default is the value + of 'sendemail.annotate'. See the CONFIGURATION section for + 'sendemail.multiedit'. --bcc=<address>:: Specify a "Bcc:" value for each email. Default is the value of @@ -164,8 +165,8 @@ Sending Furthermore, passwords need not be specified in configuration files or on the command line. If a username has been specified (with '--smtp-user' or a 'sendemail.smtpuser'), but no password has been -specified (with '--smtp-pass' or 'sendemail.smtppass'), then the -user is prompted for a password while the input is masked for privacy. +specified (with '--smtp-pass' or 'sendemail.smtppass'), then +a password is obtained using 'git-credential'. --smtp-server=<host>:: If set, specifies the outgoing SMTP server to use (e.g. diff --git a/Documentation/git-sh-setup.txt b/Documentation/git-sh-setup.txt index 6a9f66d1d9..5d709d02c3 100644 --- a/Documentation/git-sh-setup.txt +++ b/Documentation/git-sh-setup.txt @@ -82,6 +82,12 @@ get_author_ident_from_commit:: outputs code for use with eval to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL and GIT_AUTHOR_DATE variables for a given commit. +create_virtual_base:: + modifies the first file so only lines in common with the + second file remain. If there is insufficient common material, + then the first file is left empty. The result is suitable + as a virtual base input for a 3-way merge. + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-shell.txt b/Documentation/git-shell.txt index 9b9250600f..c35051ba58 100644 --- a/Documentation/git-shell.txt +++ b/Documentation/git-shell.txt @@ -9,25 +9,81 @@ git-shell - Restricted login shell for Git-only SSH access SYNOPSIS -------- [verse] -'git shell' [-c <command> <argument>] +'chsh' -s $(command -v git-shell) <user> +'git clone' <user>`@localhost:/path/to/repo.git` +'ssh' <user>`@localhost` DESCRIPTION ----------- -A login shell for SSH accounts to provide restricted Git access. When -'-c' is given, the program executes <command> non-interactively; -<command> can be one of 'git receive-pack', 'git upload-pack', 'git -upload-archive', 'cvs server', or a command in COMMAND_DIR. The shell -is started in interactive mode when no arguments are given; in this -case, COMMAND_DIR must exist, and any of the executables in it can be -invoked. +This is a login shell for SSH accounts to provide restricted Git access. +It permits execution only of server-side Git commands implementing the +pull/push functionality, plus custom commands present in a subdirectory +named `git-shell-commands` in the user's home directory. -'cvs server' is a special command which executes git-cvsserver. +COMMANDS +-------- + +'git shell' accepts the following commands after the '-c' option: + +'git receive-pack <argument>':: +'git upload-pack <argument>':: +'git upload-archive <argument>':: + Call the corresponding server-side command to support + the client's 'git push', 'git fetch', or 'git archive --remote' + request. +'cvs server':: + Imitate a CVS server. See linkgit:git-cvsserver[1]. + +If a `~/git-shell-commands` directory is present, 'git shell' will +also handle other, custom commands by running +"`git-shell-commands/<command> <arguments>`" from the user's home +directory. + +INTERACTIVE USE +--------------- + +By default, the commands above can be executed only with the '-c' +option; the shell is not interactive. -COMMAND_DIR is the path "$HOME/git-shell-commands". The user must have -read and execute permissions to the directory in order to execute the -programs in it. The programs are executed with a cwd of $HOME, and -<argument> is parsed as a command-line string. +If a `~/git-shell-commands` directory is present, 'git shell' +can also be run interactively (with no arguments). If a `help` +command is present in the `git-shell-commands` directory, it is +run to provide the user with an overview of allowed actions. Then a +"git> " prompt is presented at which one can enter any of the +commands from the `git-shell-commands` directory, or `exit` to close +the connection. + +Generally this mode is used as an administrative interface to allow +users to list repositories they have access to, create, delete, or +rename repositories, or change repository descriptions and +permissions. + +If a `no-interactive-login` command exists, then it is run and the +interactive shell is aborted. + +EXAMPLE +------- + +To disable interactive logins, displaying a greeting instead: ++ +---------------- +$ chsh -s /usr/bin/git-shell +$ mkdir $HOME/git-shell-commands +$ cat >$HOME/git-shell-commands/no-interactive-login <<\EOF +#!/bin/sh +printf '%s\n' "Hi $USER! You've successfully authenticated, but I do not" +printf '%s\n' "provide interactive shell access." +exit 128 +EOF +$ chmod +x $HOME/git-shell-commands/no-interactive-login +---------------- + +SEE ALSO +-------- +ssh(1), +linkgit:git-daemon[1], +contrib/git-shell-commands/README GIT --- diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index c99d795618..74d5bdc59d 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -13,6 +13,7 @@ SYNOPSIS [--reference <repository>] [--] <repository> [<path>] 'git submodule' [--quiet] status [--cached] [--recursive] [--] [<path>...] 'git submodule' [--quiet] init [--] [<path>...] +'git submodule' [--quiet] deinit [-f|--force] [--] <path>... 'git submodule' [--quiet] update [--init] [--remote] [-N|--no-fetch] [-f|--force] [--rebase] [--reference <repository>] [--merge] [--recursive] [--] [<path>...] @@ -135,6 +136,19 @@ init:: the explicit 'init' step if you do not intend to customize any submodule locations. +deinit:: + Unregister the given submodules, i.e. remove the whole + `submodule.$name` section from .git/config together with their work + tree. Further calls to `git submodule update`, `git submodule foreach` + and `git submodule sync` will skip any unregistered submodules until + they are initialized again, so use this command if you don't want to + have a local checkout of the submodule in your work tree anymore. If + you really want to remove a submodule from the repository and commit + that use linkgit:git-rm[1] instead. ++ +If `--force` is specified, the submodule's work tree will be removed even if +it contains local modifications. + update:: Update the registered submodules, i.e. clone missing submodules and checkout the commit specified in the index of the containing repository. @@ -214,8 +228,10 @@ OPTIONS -f:: --force:: - This option is only valid for add and update commands. + This option is only valid for add, deinit and update commands. When running add, allow adding an otherwise ignored submodule path. + When running deinit the submodule work trees will be removed even if + they contain local changes. When running update, throw away local changes in submodules when switching to a different commit; and always run a checkout operation in the submodule, even if the commit listed in the index of the diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 1b8b6498cd..7706d41c87 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -245,7 +245,7 @@ first have already been pushed into SVN. patch), "all" (accept all patches), or "quit". + 'git svn dcommit' returns immediately if answer if "no" or "quit", without - commiting anything to SVN. + committing anything to SVN. 'branch':: Create a branch in the SVN repository. @@ -856,7 +856,7 @@ HANDLING OF SVN BRANCHES ------------------------ If 'git svn' is configured to fetch branches (and --follow-branches is in effect), it sometimes creates multiple Git branches for one -SVN branch, where the addtional branches have names of the form +SVN branch, where the additional branches have names of the form 'branchname@nnn' (with nnn an SVN revision number). These additional branches are created if 'git svn' cannot find a parent commit for the first commit in an SVN branch, to connect the branch to the history of diff --git a/Documentation/git-tools.txt b/Documentation/git-tools.txt index ad8b823f77..78a0d955ec 100644 --- a/Documentation/git-tools.txt +++ b/Documentation/git-tools.txt @@ -109,7 +109,7 @@ Others - *git.el* (contrib/) - This is an Emacs interface for Git. The user interface is modeled on + This is an Emacs interface for Git. The user interface is modelled on pcl-cvs. It has been developed on Emacs 21 and will probably need some tweaking to work on XEmacs. diff --git a/Documentation/gitremote-helpers.txt b/Documentation/gitremote-helpers.txt index 0c91aba861..f506031ae4 100644 --- a/Documentation/gitremote-helpers.txt +++ b/Documentation/gitremote-helpers.txt @@ -174,8 +174,8 @@ ref. This capability can be advertised multiple times. The first applicable refspec takes precedence. The left-hand of refspecs advertised with this capability must cover all refs reported by -the list command. If no 'refspec' capability is advertised, -there is an implied `refspec *:*`. +the list command. If a helper does not need a specific 'refspec' +capability then it should advertise `refspec *:*`. 'bidi-import':: This modifies the 'import' capability. diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt index eb7ba84f1f..2478a3963c 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -100,9 +100,22 @@ to point at the new commit. [[def_detached_HEAD]]detached HEAD:: Normally the <<def_HEAD,HEAD>> stores the name of a - <<def_branch,branch>>. However, Git also allows you to <<def_checkout,check out>> - an arbitrary <<def_commit,commit>> that isn't necessarily the tip of any - particular branch. In this case HEAD is said to be "detached". + <<def_branch,branch>>, and commands that operate on the + history HEAD represents operate on the history leading to the + tip of the branch the HEAD points at. However, Git also + allows you to <<def_checkout,check out>> an arbitrary + <<def_commit,commit>> that isn't necessarily the tip of any + particular branch. The HEAD in such a state is called + "detached". ++ +Note that commands that operate on the history of the current branch +(e.g. `git commit` to build a new history on top of it) still work +while the HEAD is detached. They update the HEAD to point at the tip +of the updated history without affecting any branch. Commands that +update or inquire information _about_ the current branch (e.g. `git +branch --set-upstream-to` that sets what remote tracking branch the +current branch integrates with) obviously do not work, as there is no +(real) current branch to ask about in this state. [[def_dircache]]dircache:: You are *waaaaay* behind. See <<def_index,index>>. diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt index 34a8445828..2adccf8fec 100644 --- a/Documentation/merge-options.txt +++ b/Documentation/merge-options.txt @@ -84,6 +84,11 @@ option can be used to override --squash. Pass merge strategy specific option through to the merge strategy. +--verify-signatures:: +--no-verify-signatures:: + Verify that the commits being merged have good and trusted GPG signatures + and abort the merge in case they do not. + --summary:: --no-summary:: Synonyms to --stat and --no-stat; these are deprecated and will be diff --git a/Documentation/merge-strategies.txt b/Documentation/merge-strategies.txt index 66db80296f..49a9a7d53f 100644 --- a/Documentation/merge-strategies.txt +++ b/Documentation/merge-strategies.txt @@ -48,6 +48,12 @@ patience;; this when the branches to be merged have diverged wildly. See also linkgit:git-diff[1] `--patience`. +diff-algorithm=[patience|minimal|histogram|myers];; + Tells 'merge-recursive' to use a different diff algorithm, which + can help avoid mismerges that occur due to unimportant matching + lines (such as braces from distinct functions). See also + linkgit:git-diff[1] `--diff-algorithm`. + ignore-space-change;; ignore-all-space;; ignore-space-at-eol;; diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 293965524e..afac703f21 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -131,7 +131,8 @@ The placeholders are: - '%B': raw body (unwrapped subject and body) - '%N': commit notes - '%GG': raw verification message from GPG for a signed commit -- '%G?': show either "G" for Good or "B" for Bad for a signed commit +- '%G?': show "G" for a Good signature, "B" for a Bad signature, "U" for a good, + untrusted signature and "N" for no signature - '%GS': show the name of the signer for a signed commit - '%GK': show the key used to sign a signed commit - '%gD': reflog selector, e.g., `refs/stash@{1}` diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt index 314e25da73..8855b1a0ac 100644 --- a/Documentation/revisions.txt +++ b/Documentation/revisions.txt @@ -55,7 +55,7 @@ when you run `git cherry-pick`. + Note that any of the 'refs/*' cases above may come either from the '$GIT_DIR/refs' directory or from the '$GIT_DIR/packed-refs' file. -While the ref name encoding is unspecified, UTF-8 is prefered as +While the ref name encoding is unspecified, UTF-8 is preferred as some output processing may assume ref names in UTF-8. '<refname>@\{<date>\}', e.g. 'master@\{yesterday\}', 'HEAD@\{5 minutes ago\}':: @@ -116,6 +116,11 @@ some output processing may assume ref names in UTF-8. object of that type is found or the object cannot be dereferenced anymore (in which case, barf). '<rev>{caret}0' is a short-hand for '<rev>{caret}\{commit\}'. ++ +'rev{caret}\{object\}' can be used to make sure 'rev' names an +object that exists, without requiring 'rev' to be a tag, and +without dereferencing 'rev'; because a tag is already an object, +it does not have to be dereferenced even once to get to an object. '<rev>{caret}\{\}', e.g. 'v0.99.8{caret}\{\}':: A suffix '{caret}' followed by an empty brace pair diff --git a/Documentation/technical/api-argv-array.txt b/Documentation/technical/api-argv-array.txt index a959517b23..a6b7d83a8e 100644 --- a/Documentation/technical/api-argv-array.txt +++ b/Documentation/technical/api-argv-array.txt @@ -55,7 +55,7 @@ Functions initial, empty state. `argv_array_detach`:: - Detach the argv array from the `struct argv_array`, transfering + Detach the argv array from the `struct argv_array`, transferring ownership of the allocated array and strings. `argv_array_free_detached`:: diff --git a/Documentation/technical/api-credentials.txt b/Documentation/technical/api-credentials.txt index 516fda7412..c1b42a40d3 100644 --- a/Documentation/technical/api-credentials.txt +++ b/Documentation/technical/api-credentials.txt @@ -160,7 +160,7 @@ int foo_login(struct foo_connection *f) break; default: /* - * Some other error occured. We don't know if the + * Some other error occurred. We don't know if the * credential is good or bad, so report nothing to the * credential subsystem. */ diff --git a/Documentation/technical/api-ref-iteration.txt b/Documentation/technical/api-ref-iteration.txt index dbbea95db7..aa1c50f181 100644 --- a/Documentation/technical/api-ref-iteration.txt +++ b/Documentation/technical/api-ref-iteration.txt @@ -35,7 +35,7 @@ Iteration functions * `head_ref_submodule()`, `for_each_ref_submodule()`, `for_each_ref_in_submodule()`, `for_each_tag_ref_submodule()`, `for_each_branch_ref_submodule()`, `for_each_remote_ref_submodule()` - do the same as the functions descibed above but for a specified + do the same as the functions described above but for a specified submodule. * `for_each_rawref()` can be used to learn about broken ref and symref. diff --git a/Documentation/technical/api-strbuf.txt b/Documentation/technical/api-strbuf.txt index 2c59cb2259..3350d97dda 100644 --- a/Documentation/technical/api-strbuf.txt +++ b/Documentation/technical/api-strbuf.txt @@ -230,6 +230,11 @@ which can be used by the programmer of the callback as she sees fit. destination. This is useful for literal data to be fed to either strbuf_expand or to the *printf family of functions. +`strbuf_humanise_bytes`:: + + Append the given byte size as a human-readable string (i.e. 12.23 KiB, + 3.50 MiB). + `strbuf_addf`:: Add a formatted string to the buffer. diff --git a/Documentation/technical/pack-format.txt b/Documentation/technical/pack-format.txt index 0e37ec9de5..a37f1378d0 100644 --- a/Documentation/technical/pack-format.txt +++ b/Documentation/technical/pack-format.txt @@ -26,7 +26,9 @@ Git pack format (deltified representation) n-byte type and length (3-bit type, (n-1)*7+4-bit length) - 20-byte base object name + 20-byte base object name if OBJ_REF_DELTA or a negative relative + offset from the delta object's position in the pack if this + is an OBJ_OFS_DELTA object compressed delta data Observation: length of each object is encoded in a variable diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 26422bfc22..6722e621d1 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.8.2.1 +DEF_VER=v1.8.2.GIT LF=' ' @@ -101,7 +101,7 @@ Issues of note: - "openssl" library is used by git-imap-send to use IMAP over SSL. If you don't need it, use NO_OPENSSL. - By default, git uses OpenSSL for SHA1 but it will use it's own + By default, git uses OpenSSL for SHA1 but it will use its own library (inspired by Mozilla's) with either NO_OPENSSL or BLK_SHA1. Also included is a version optimized for PowerPC (PPC_SHA1). @@ -358,33 +358,39 @@ STRIP ?= strip # Among the variables below, these: # gitexecdir # template_dir -# mandir -# infodir -# htmldir # sysconfdir # can be specified as a relative path some/where/else; # this is interpreted as relative to $(prefix) and "git" at # runtime figures out where they are based on the path to the executable. +# Additionally, the following will be treated as relative by "git" if they +# begin with "$(prefix)/": +# mandir +# infodir +# htmldir # This can help installing the suite in a relocatable way. prefix = $(HOME) bindir_relative = bin bindir = $(prefix)/$(bindir_relative) -mandir = share/man -infodir = share/info +mandir = $(prefix)/share/man +infodir = $(prefix)/share/info gitexecdir = libexec/git-core mergetoolsdir = $(gitexecdir)/mergetools sharedir = $(prefix)/share gitwebdir = $(sharedir)/gitweb localedir = $(sharedir)/locale template_dir = share/git-core/templates -htmldir = share/doc/git-doc +htmldir = $(prefix)/share/doc/git-doc ETC_GITCONFIG = $(sysconfdir)/gitconfig ETC_GITATTRIBUTES = $(sysconfdir)/gitattributes lib = lib # DESTDIR = pathsep = : +mandir_relative = $(patsubst $(prefix)/%,%,$(mandir)) +infodir_relative = $(patsubst $(prefix)/%,%,$(infodir)) +htmldir_relative = $(patsubst $(prefix)/%,%,$(htmldir)) + export prefix bindir sharedir sysconfdir gitwebdir localedir CC = cc @@ -1539,12 +1545,12 @@ ETC_GITATTRIBUTES_SQ = $(subst ','\'',$(ETC_GITATTRIBUTES)) DESTDIR_SQ = $(subst ','\'',$(DESTDIR)) bindir_SQ = $(subst ','\'',$(bindir)) bindir_relative_SQ = $(subst ','\'',$(bindir_relative)) -mandir_SQ = $(subst ','\'',$(mandir)) -infodir_SQ = $(subst ','\'',$(infodir)) +mandir_relative_SQ = $(subst ','\'',$(mandir_relative)) +infodir_relative_SQ = $(subst ','\'',$(infodir_relative)) localedir_SQ = $(subst ','\'',$(localedir)) gitexecdir_SQ = $(subst ','\'',$(gitexecdir)) template_dir_SQ = $(subst ','\'',$(template_dir)) -htmldir_SQ = $(subst ','\'',$(htmldir)) +htmldir_relative_SQ = $(subst ','\'',$(htmldir_relative)) prefix_SQ = $(subst ','\'',$(prefix)) gitwebdir_SQ = $(subst ','\'',$(gitwebdir)) @@ -1676,9 +1682,9 @@ strip: $(PROGRAMS) git$X git.sp git.s git.o: GIT-PREFIX git.sp git.s git.o: EXTRA_CPPFLAGS = \ - '-DGIT_HTML_PATH="$(htmldir_SQ)"' \ - '-DGIT_MAN_PATH="$(mandir_SQ)"' \ - '-DGIT_INFO_PATH="$(infodir_SQ)"' + '-DGIT_HTML_PATH="$(htmldir_relative_SQ)"' \ + '-DGIT_MAN_PATH="$(mandir_relative_SQ)"' \ + '-DGIT_INFO_PATH="$(infodir_relative_SQ)"' git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS) $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \ @@ -1688,9 +1694,9 @@ help.sp help.s help.o: common-cmds.h builtin/help.sp builtin/help.s builtin/help.o: common-cmds.h GIT-PREFIX builtin/help.sp builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \ - '-DGIT_HTML_PATH="$(htmldir_SQ)"' \ - '-DGIT_MAN_PATH="$(mandir_SQ)"' \ - '-DGIT_INFO_PATH="$(infodir_SQ)"' + '-DGIT_HTML_PATH="$(htmldir_relative_SQ)"' \ + '-DGIT_MAN_PATH="$(mandir_relative_SQ)"' \ + '-DGIT_INFO_PATH="$(infodir_relative_SQ)"' version.sp version.s version.o: GIT-VERSION-FILE GIT-USER-AGENT version.sp version.s version.o: EXTRA_CPPFLAGS = \ @@ -1 +1 @@ -Documentation/RelNotes/1.8.2.1.txt
\ No newline at end of file +Documentation/RelNotes/1.8.3.txt
\ No newline at end of file @@ -13,6 +13,7 @@ int advice_commit_before_merge = 1; int advice_resolve_conflict = 1; int advice_implicit_identity = 1; int advice_detached_head = 1; +int advice_set_upstream_failure = 1; static struct { const char *name; @@ -31,6 +32,7 @@ static struct { { "resolveconflict", &advice_resolve_conflict }, { "implicitidentity", &advice_implicit_identity }, { "detachedhead", &advice_detached_head }, + { "setupstreamfailure", &advice_set_upstream_failure }, /* make this an alias for backward compatibility */ { "pushnonfastforward", &advice_push_update_rejected } @@ -16,6 +16,7 @@ extern int advice_commit_before_merge; extern int advice_resolve_conflict; extern int advice_implicit_identity; extern int advice_detached_head; +extern int advice_set_upstream_failure; int git_default_advice_config(const char *var, const char *value); void advise(const char *advice, ...); diff --git a/archive-zip.c b/archive-zip.c index a8d119305f..b2c4fe0e9f 100644 --- a/archive-zip.c +++ b/archive-zip.c @@ -111,8 +111,9 @@ static void copy_le32(unsigned char *dest, unsigned int n) dest[3] = 0xff & (n >> 030); } -static void *zlib_deflate(void *data, unsigned long size, - int compression_level, unsigned long *compressed_size) +static void *zlib_deflate_raw(void *data, unsigned long size, + int compression_level, + unsigned long *compressed_size) { git_zstream stream; unsigned long maxsize; @@ -120,7 +121,7 @@ static void *zlib_deflate(void *data, unsigned long size, int result; memset(&stream, 0, sizeof(stream)); - git_deflate_init(&stream, compression_level); + git_deflate_init_raw(&stream, compression_level); maxsize = git_deflate_bound(&stream, size); buffer = xmalloc(maxsize); @@ -265,14 +266,11 @@ static int write_zip_entry(struct archiver_args *args, } if (buffer && method == 8) { - deflated = zlib_deflate(buffer, size, args->compression_level, - &compressed_size); - if (deflated && compressed_size - 6 < size) { - /* ZLIB --> raw compressed data (see RFC 1950) */ - /* CMF and FLG ... */ - out = (unsigned char *)deflated + 2; - compressed_size -= 6; /* ... and ADLER32 */ - } else { + out = deflated = zlib_deflate_raw(buffer, size, + args->compression_level, + &compressed_size); + if (!out || compressed_size >= size) { + out = buffer; method = 0; compressed_size = size; } @@ -353,7 +351,7 @@ static int write_zip_entry(struct archiver_args *args, unsigned char compressed[STREAM_BUFFER_SIZE * 2]; memset(&zstream, 0, sizeof(zstream)); - git_deflate_init(&zstream, args->compression_level); + git_deflate_init_raw(&zstream, args->compression_level); compressed_size = 0; zstream.next_out = compressed; @@ -370,13 +368,10 @@ static int write_zip_entry(struct archiver_args *args, result = git_deflate(&zstream, 0); if (result != Z_OK) die("deflate error (%d)", result); - out = compressed; - if (!compressed_size) - out += 2; - out_len = zstream.next_out - out; + out_len = zstream.next_out - compressed; if (out_len > 0) { - write_or_die(1, out, out_len); + write_or_die(1, compressed, out_len); compressed_size += out_len; zstream.next_out = compressed; zstream.avail_out = sizeof(compressed); @@ -394,11 +389,8 @@ static int write_zip_entry(struct archiver_args *args, die("deflate error (%d)", result); git_deflate_end(&zstream); - out = compressed; - if (!compressed_size) - out += 2; - out_len = zstream.next_out - out - 4; - write_or_die(1, out, out_len); + out_len = zstream.next_out - compressed; + write_or_die(1, compressed, out_len); compressed_size += out_len; zip_offset += compressed_size; @@ -381,46 +381,13 @@ static struct attr_stack *read_attr_from_file(const char *path, int macro_ok) return res; } -static void *read_index_data(const char *path) -{ - int pos, len; - unsigned long sz; - enum object_type type; - void *data; - struct index_state *istate = use_index ? use_index : &the_index; - - len = strlen(path); - pos = index_name_pos(istate, path, len); - if (pos < 0) { - /* - * We might be in the middle of a merge, in which - * case we would read stage #2 (ours). - */ - int i; - for (i = -pos - 1; - (pos < 0 && i < istate->cache_nr && - !strcmp(istate->cache[i]->name, path)); - i++) - if (ce_stage(istate->cache[i]) == 2) - pos = i; - } - if (pos < 0) - return NULL; - data = read_sha1_file(istate->cache[pos]->sha1, &type, &sz); - if (!data || type != OBJ_BLOB) { - free(data); - return NULL; - } - return data; -} - static struct attr_stack *read_attr_from_index(const char *path, int macro_ok) { struct attr_stack *res; char *buf, *sp; int lineno = 0; - buf = read_index_data(path); + buf = read_blob_data_from_index(use_index ? use_index : &the_index, path, NULL); if (!buf) return NULL; @@ -525,9 +525,9 @@ struct commit_list *filter_skipped(struct commit_list *list, * is increased by one between each call, but that should not matter * for this application. */ -static int get_prn(int count) { +static unsigned get_prn(unsigned count) { count = count * 1103515245 + 12345; - return ((unsigned)(count/65536) % PRN_MODULO); + return (count/65536) % PRN_MODULO; } /* @@ -57,7 +57,7 @@ void install_branch_config(int flag, const char *local, const char *origin, cons if (remote_is_branch && !strcmp(local, shortname) && !origin) { - warning("Not setting branch %s as its own upstream.", + warning(_("Not setting branch %s as its own upstream."), local); return; } @@ -78,25 +78,25 @@ void install_branch_config(int flag, const char *local, const char *origin, cons if (flag & BRANCH_CONFIG_VERBOSE) { if (remote_is_branch && origin) - printf(rebasing ? - "Branch %s set up to track remote branch %s from %s by rebasing.\n" : - "Branch %s set up to track remote branch %s from %s.\n", - local, shortname, origin); + printf_ln(rebasing ? + _("Branch %s set up to track remote branch %s from %s by rebasing.") : + _("Branch %s set up to track remote branch %s from %s."), + local, shortname, origin); else if (remote_is_branch && !origin) - printf(rebasing ? - "Branch %s set up to track local branch %s by rebasing.\n" : - "Branch %s set up to track local branch %s.\n", - local, shortname); + printf_ln(rebasing ? + _("Branch %s set up to track local branch %s by rebasing.") : + _("Branch %s set up to track local branch %s."), + local, shortname); else if (!remote_is_branch && origin) - printf(rebasing ? - "Branch %s set up to track remote ref %s by rebasing.\n" : - "Branch %s set up to track remote ref %s.\n", - local, remote); + printf_ln(rebasing ? + _("Branch %s set up to track remote ref %s by rebasing.") : + _("Branch %s set up to track remote ref %s."), + local, remote); else if (!remote_is_branch && !origin) - printf(rebasing ? - "Branch %s set up to track local ref %s by rebasing.\n" : - "Branch %s set up to track local ref %s.\n", - local, remote); + printf_ln(rebasing ? + _("Branch %s set up to track local ref %s by rebasing.") : + _("Branch %s set up to track local ref %s."), + local, remote); else die("BUG: impossible combination of %d and %p", remote_is_branch, origin); @@ -115,7 +115,7 @@ static int setup_tracking(const char *new_ref, const char *orig_ref, int config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE; if (strlen(new_ref) > 1024 - 7 - 7 - 1) - return error("Tracking not set up: name too long: %s", + return error(_("Tracking not set up: name too long: %s"), new_ref); memset(&tracking, 0, sizeof(tracking)); @@ -134,7 +134,7 @@ static int setup_tracking(const char *new_ref, const char *orig_ref, } if (tracking.matches > 1) - return error("Not tracking: ambiguous information for ref %s", + return error(_("Not tracking: ambiguous information for ref %s"), orig_ref); install_branch_config(config_flags, new_ref, tracking.remote, @@ -179,12 +179,12 @@ int validate_new_branchname(const char *name, struct strbuf *ref, int force, int attr_only) { if (strbuf_check_branch_ref(ref, name)) - die("'%s' is not a valid branch name.", name); + die(_("'%s' is not a valid branch name."), name); if (!ref_exists(ref->buf)) return 0; else if (!force && !attr_only) - die("A branch named '%s' already exists.", ref->buf + strlen("refs/heads/")); + die(_("A branch named '%s' already exists."), ref->buf + strlen("refs/heads/")); if (!attr_only) { const char *head; @@ -192,11 +192,25 @@ int validate_new_branchname(const char *name, struct strbuf *ref, head = resolve_ref_unsafe("HEAD", sha1, 0, NULL); if (!is_bare_repository() && head && !strcmp(head, ref->buf)) - die("Cannot force update the current branch."); + die(_("Cannot force update the current branch.")); } return 1; } +static const char upstream_not_branch[] = +N_("Cannot setup tracking information; starting point '%s' is not a branch."); +static const char upstream_missing[] = +N_("the requested upstream branch '%s' does not exist"); +static const char upstream_advice[] = +N_("\n" +"If you are planning on basing your work on an upstream\n" +"branch that already exists at the remote, you may need to\n" +"run \"git fetch\" to retrieve it.\n" +"\n" +"If you are planning to push out a new local branch that\n" +"will track its remote counterpart, you may want to use\n" +"\"git push -u\" to set the upstream config as you push."); + void create_branch(const char *head, const char *name, const char *start_name, int force, int reflog, int clobber_head, @@ -224,38 +238,47 @@ void create_branch(const char *head, } real_ref = NULL; - if (get_sha1(start_name, sha1)) - die("Not a valid object name: '%s'.", start_name); + if (get_sha1(start_name, sha1)) { + if (explicit_tracking) { + if (advice_set_upstream_failure) { + error(_(upstream_missing), start_name); + advise(_(upstream_advice)); + exit(1); + } + die(_(upstream_missing), start_name); + } + die(_("Not a valid object name: '%s'."), start_name); + } switch (dwim_ref(start_name, strlen(start_name), sha1, &real_ref)) { case 0: /* Not branching from any existing branch */ if (explicit_tracking) - die("Cannot setup tracking information; starting point is not a branch."); + die(_(upstream_not_branch), start_name); break; case 1: /* Unique completion -- good, only if it is a real branch */ if (prefixcmp(real_ref, "refs/heads/") && prefixcmp(real_ref, "refs/remotes/")) { if (explicit_tracking) - die("Cannot setup tracking information; starting point is not a branch."); + die(_(upstream_not_branch), start_name); else real_ref = NULL; } break; default: - die("Ambiguous object name: '%s'.", start_name); + die(_("Ambiguous object name: '%s'."), start_name); break; } if ((commit = lookup_commit_reference(sha1)) == NULL) - die("Not a valid branch point: '%s'.", start_name); + die(_("Not a valid branch point: '%s'."), start_name); hashcpy(sha1, commit->object.sha1); if (!dont_change_ref) { lock = lock_any_ref_for_update(ref.buf, NULL, 0); if (!lock) - die_errno("Failed to lock ref for update"); + die_errno(_("Failed to lock ref for update")); } if (reflog) @@ -273,7 +296,7 @@ void create_branch(const char *head, if (!dont_change_ref) if (write_ref_sha1(lock, sha1, msg) < 0) - die_errno("Failed to write ref"); + die_errno(_("Failed to write ref")); strbuf_release(&ref); free(real_ref); diff --git a/builtin/add.c b/builtin/add.c index ab1c9e8fb7..daf02c6aff 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -26,8 +26,52 @@ static int take_worktree_changes; struct update_callback_data { int flags; int add_errors; + const char *implicit_dot; + size_t implicit_dot_len; }; +static const char *option_with_implicit_dot; +static const char *short_option_with_implicit_dot; + +static void warn_pathless_add(void) +{ + static int shown; + assert(option_with_implicit_dot && short_option_with_implicit_dot); + + if (shown) + return; + shown = 1; + + /* + * To be consistent with "git add -p" and most Git + * commands, we should default to being tree-wide, but + * this is not the original behavior and can't be + * changed until users trained themselves not to type + * "git add -u" or "git add -A". For now, we warn and + * keep the old behavior. Later, the behavior can be changed + * to tree-wide, keeping the warning for a while, and + * eventually we can drop the warning. + */ + warning(_("The behavior of 'git add %s (or %s)' with no path argument from a\n" + "subdirectory of the tree will change in Git 2.0 and should not be used anymore.\n" + "To add content for the whole tree, run:\n" + "\n" + " git add %s :/\n" + " (or git add %s :/)\n" + "\n" + "To restrict the command to the current directory, run:\n" + "\n" + " git add %s .\n" + " (or git add %s .)\n" + "\n" + "With the current Git version, the command is restricted to " + "the current directory.\n" + ""), + option_with_implicit_dot, short_option_with_implicit_dot, + option_with_implicit_dot, short_option_with_implicit_dot, + option_with_implicit_dot, short_option_with_implicit_dot); +} + static int fix_unmerged_status(struct diff_filepair *p, struct update_callback_data *data) { @@ -54,10 +98,26 @@ static void update_callback(struct diff_queue_struct *q, { int i; struct update_callback_data *data = cbdata; + const char *implicit_dot = data->implicit_dot; + size_t implicit_dot_len = data->implicit_dot_len; for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; const char *path = p->one->path; + /* + * Check if "git add -A" or "git add -u" was run from a + * subdirectory with a modified file outside that directory, + * and warn if so. + * + * "git add -u" will behave like "git add -u :/" instead of + * "git add -u ." in the future. This warning prepares for + * that change. + */ + if (implicit_dot && + strncmp_icase(path, implicit_dot, implicit_dot_len)) { + warn_pathless_add(); + continue; + } switch (fix_unmerged_status(p, data)) { default: die(_("unexpected diff status %c"), p->status); @@ -85,20 +145,34 @@ int add_files_to_cache(const char *prefix, const char **pathspec, int flags) { struct update_callback_data data; struct rev_info rev; + + memset(&data, 0, sizeof(data)); + data.flags = flags & ~ADD_CACHE_IMPLICIT_DOT; + if ((flags & ADD_CACHE_IMPLICIT_DOT) && prefix) { + /* + * Check for modified files throughout the worktree so + * update_callback has a chance to warn about changes + * outside the cwd. + */ + data.implicit_dot = prefix; + data.implicit_dot_len = strlen(prefix); + pathspec = NULL; + } + init_revisions(&rev, prefix); setup_revisions(0, NULL, &rev, NULL); init_pathspec(&rev.prune_data, pathspec); rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = update_callback; - data.flags = flags; - data.add_errors = 0; rev.diffopt.format_callback_data = &data; rev.max_count = 0; /* do not compare unmerged paths with stage #2 */ run_diff_files(&rev, DIFF_RACY_IS_MODIFIED); return !!data.add_errors; } -static char *prune_directory(struct dir_struct *dir, const char **pathspec, int prefix) +#define WARN_IMPLICIT_DOT (1u << 0) +static char *prune_directory(struct dir_struct *dir, const char **pathspec, + int prefix, unsigned flag) { char *seen; int i, specs; @@ -115,6 +189,16 @@ static char *prune_directory(struct dir_struct *dir, const char **pathspec, int if (match_pathspec(pathspec, entry->name, entry->len, prefix, seen)) *dst++ = entry; + else if (flag & WARN_IMPLICIT_DOT) + /* + * "git add -A" was run from a subdirectory with a + * new file outside that directory. + * + * "git add -A" will behave like "git add -A :/" + * instead of "git add -A ." in the future. + * Warn about the coming behavior change. + */ + warn_pathless_add(); } dir->nr = dst - dir->entries; add_pathspec_matches_against_index(pathspec, seen, specs); @@ -321,35 +405,6 @@ static int add_files(struct dir_struct *dir, int flags) return exit_status; } -static void warn_pathless_add(const char *option_name, const char *short_name) { - /* - * To be consistent with "git add -p" and most Git - * commands, we should default to being tree-wide, but - * this is not the original behavior and can't be - * changed until users trained themselves not to type - * "git add -u" or "git add -A". For now, we warn and - * keep the old behavior. Later, the behavior can be changed - * to tree-wide, keeping the warning for a while, and - * eventually we can drop the warning. - */ - warning(_("The behavior of 'git add %s (or %s)' with no path argument from a\n" - "subdirectory of the tree will change in Git 2.0 and should not be used anymore.\n" - "To add content for the whole tree, run:\n" - "\n" - " git add %s :/\n" - " (or git add %s :/)\n" - "\n" - "To restrict the command to the current directory, run:\n" - "\n" - " git add %s .\n" - " (or git add %s .)\n" - "\n" - "With the current Git version, the command is restricted to the current directory."), - option_name, short_name, - option_name, short_name, - option_name, short_name); -} - int cmd_add(int argc, const char **argv, const char *prefix) { int exit_status = 0; @@ -360,8 +415,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) int add_new_files; int require_pathspec; char *seen = NULL; - const char *option_with_implicit_dot = NULL; - const char *short_option_with_implicit_dot = NULL; + int implicit_dot = 0; git_config(add_config, NULL); @@ -391,11 +445,9 @@ int cmd_add(int argc, const char **argv, const char *prefix) } if (option_with_implicit_dot && !argc) { static const char *here[2] = { ".", NULL }; - if (prefix) - warn_pathless_add(option_with_implicit_dot, - short_option_with_implicit_dot); argc = 1; argv = here; + implicit_dot = 1; } add_new_files = !take_worktree_changes && !refresh_only; @@ -408,7 +460,8 @@ int cmd_add(int argc, const char **argv, const char *prefix) (intent_to_add ? ADD_CACHE_INTENT : 0) | (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) | (!(addremove || take_worktree_changes) - ? ADD_CACHE_IGNORE_REMOVAL : 0)); + ? ADD_CACHE_IGNORE_REMOVAL : 0)) | + (implicit_dot ? ADD_CACHE_IMPLICIT_DOT : 0); if (require_pathspec && argc == 0) { fprintf(stderr, _("Nothing specified, nothing added.\n")); @@ -432,9 +485,10 @@ int cmd_add(int argc, const char **argv, const char *prefix) } /* This picks up the paths that are not tracked */ - baselen = fill_directory(&dir, pathspec); + baselen = fill_directory(&dir, implicit_dot ? NULL : pathspec); if (pathspec) - seen = prune_directory(&dir, pathspec, baselen); + seen = prune_directory(&dir, pathspec, baselen, + implicit_dot ? WARN_IMPLICIT_DOT : 0); } if (refresh_only) { diff --git a/builtin/apply.c b/builtin/apply.c index 06f5320b18..30eefc3c7b 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -1921,7 +1921,7 @@ static int parse_binary(char *buffer, unsigned long size, struct patch *patch) } /* - * Read the patch text in "buffer" taht extends for "size" bytes; stop + * Read the patch text in "buffer" that extends for "size" bytes; stop * reading after seeing a single patch (i.e. changes to a single file). * Create fragments (i.e. patch hunks) and hang them to the given patch. * Return the number of bytes consumed, so that the caller can call us @@ -2117,10 +2117,10 @@ static void update_pre_post_images(struct image *preimage, /* * Adjust the common context lines in postimage. This can be - * done in-place when we are just doing whitespace fixing, - * which does not make the string grow, but needs a new buffer - * when ignoring whitespace causes the update, since in this case - * we could have e.g. tabs converted to multiple spaces. + * done in-place when we are shrinking it with whitespace + * fixing, but needs a new buffer when ignoring whitespace or + * expanding leading tabs to spaces. + * * We trust the caller to tell us if the update can be done * in place (postlen==0) or not. */ @@ -2185,7 +2185,7 @@ static int match_fragment(struct image *img, int i; char *fixed_buf, *buf, *orig, *target; struct strbuf fixed; - size_t fixed_len; + size_t fixed_len, postlen; int preimage_limit; if (preimage->nr + try_lno <= img->nr) { @@ -2335,6 +2335,7 @@ static int match_fragment(struct image *img, strbuf_init(&fixed, preimage->len + 1); orig = preimage->buf; target = img->buf + try; + postlen = 0; for (i = 0; i < preimage_limit; i++) { size_t oldlen = preimage->line[i].len; size_t tgtlen = img->line[try_lno + i].len; @@ -2362,6 +2363,7 @@ static int match_fragment(struct image *img, match = (tgtfix.len == fixed.len - fixstart && !memcmp(tgtfix.buf, fixed.buf + fixstart, fixed.len - fixstart)); + postlen += tgtfix.len; strbuf_release(&tgtfix); if (!match) @@ -2399,8 +2401,10 @@ static int match_fragment(struct image *img, * hunk match. Update the context lines in the postimage. */ fixed_buf = strbuf_detach(&fixed, &fixed_len); + if (postlen < postimage->len) + postlen = 0; update_pre_post_images(preimage, postimage, - fixed_buf, fixed_len, 0); + fixed_buf, fixed_len, postlen); return 1; unmatch_exit: @@ -3025,7 +3029,7 @@ static struct patch *in_fn_table(const char *name) * * The latter is needed to deal with a case where two paths A and B * are swapped by first renaming A to B and then renaming B to A; - * moving A to B should not be prevented due to presense of B as we + * moving A to B should not be prevented due to presence of B as we * will remove it in a later patch. */ #define PATH_TO_BE_DELETED ((struct patch *) -2) @@ -3509,7 +3513,7 @@ static int check_patch(struct patch *patch) * * A patch to swap-rename between A and B would first rename A * to B and then rename B to A. While applying the first one, - * the presense of B should not stop A from getting renamed to + * the presence of B should not stop A from getting renamed to * B; ask to_be_deleted() about the later rename. Removal of * B and rename from A to B is handled the same way by asking * was_deleted(). diff --git a/builtin/archive.c b/builtin/archive.c index 9a1cfd3dac..49178f159e 100644 --- a/builtin/archive.c +++ b/builtin/archive.c @@ -27,8 +27,8 @@ static int run_remote_archiver(int argc, const char **argv, const char *remote, const char *exec, const char *name_hint) { - char buf[LARGE_PACKET_MAX]; - int fd[2], i, len, rv; + char *buf; + int fd[2], i, rv; struct transport *transport; struct remote *_remote; @@ -53,21 +53,18 @@ static int run_remote_archiver(int argc, const char **argv, packet_write(fd[1], "argument %s\n", argv[i]); packet_flush(fd[1]); - len = packet_read_line(fd[0], buf, sizeof(buf)); - if (!len) + buf = packet_read_line(fd[0], NULL); + if (!buf) die(_("git archive: expected ACK/NAK, got EOF")); - if (buf[len-1] == '\n') - buf[--len] = 0; if (strcmp(buf, "ACK")) { - if (len > 5 && !prefixcmp(buf, "NACK ")) + if (!prefixcmp(buf, "NACK ")) die(_("git archive: NACK %s"), buf + 5); - if (len > 4 && !prefixcmp(buf, "ERR ")) + if (!prefixcmp(buf, "ERR ")) die(_("remote error: %s"), buf + 4); die(_("git archive: protocol error")); } - len = packet_read_line(fd[0], buf, sizeof(buf)); - if (len) + if (packet_read_line(fd[0], NULL)) die(_("git archive: expected a flush")); /* Now, start reading from fd[0] and spit it out to stdout */ diff --git a/builtin/branch.c b/builtin/branch.c index 00d17d25d1..083689063f 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -18,6 +18,7 @@ #include "string-list.h" #include "column.h" #include "utf8.h" +#include "wt-status.h" static const char * const builtin_branch_usage[] = { N_("git branch [options] [-r | -a] [--merged | --no-merged]"), @@ -40,13 +41,15 @@ static char branch_colors[][COLOR_MAXLEN] = { GIT_COLOR_RED, /* REMOTE */ GIT_COLOR_NORMAL, /* LOCAL */ GIT_COLOR_GREEN, /* CURRENT */ + GIT_COLOR_BLUE, /* UPSTREAM */ }; enum color_branch { BRANCH_COLOR_RESET = 0, BRANCH_COLOR_PLAIN = 1, BRANCH_COLOR_REMOTE = 2, BRANCH_COLOR_LOCAL = 3, - BRANCH_COLOR_CURRENT = 4 + BRANCH_COLOR_CURRENT = 4, + BRANCH_COLOR_UPSTREAM = 5 }; static enum merge_filter { @@ -71,6 +74,8 @@ static int parse_branch_color_slot(const char *var, int ofs) return BRANCH_COLOR_LOCAL; if (!strcasecmp(var+ofs, "current")) return BRANCH_COLOR_CURRENT; + if (!strcasecmp(var+ofs, "upstream")) + return BRANCH_COLOR_UPSTREAM; return -1; } @@ -417,36 +422,52 @@ static void fill_tracking_info(struct strbuf *stat, const char *branch_name, int ours, theirs; char *ref = NULL; struct branch *branch = branch_get(branch_name); + struct strbuf fancy = STRBUF_INIT; if (!stat_tracking_info(branch, &ours, &theirs)) { if (branch && branch->merge && branch->merge[0]->dst && - show_upstream_ref) - strbuf_addf(stat, "[%s] ", - shorten_unambiguous_ref(branch->merge[0]->dst, 0)); + show_upstream_ref) { + ref = shorten_unambiguous_ref(branch->merge[0]->dst, 0); + if (want_color(branch_use_color)) + strbuf_addf(stat, "[%s%s%s] ", + branch_get_color(BRANCH_COLOR_UPSTREAM), + ref, branch_get_color(BRANCH_COLOR_RESET)); + else + strbuf_addf(stat, "[%s] ", ref); + } return; } - if (show_upstream_ref) + if (show_upstream_ref) { ref = shorten_unambiguous_ref(branch->merge[0]->dst, 0); + if (want_color(branch_use_color)) + strbuf_addf(&fancy, "%s%s%s", + branch_get_color(BRANCH_COLOR_UPSTREAM), + ref, branch_get_color(BRANCH_COLOR_RESET)); + else + strbuf_addstr(&fancy, ref); + } + if (!ours) { if (ref) - strbuf_addf(stat, _("[%s: behind %d]"), ref, theirs); + strbuf_addf(stat, _("[%s: behind %d]"), fancy.buf, theirs); else strbuf_addf(stat, _("[behind %d]"), theirs); } else if (!theirs) { if (ref) - strbuf_addf(stat, _("[%s: ahead %d]"), ref, ours); + strbuf_addf(stat, _("[%s: ahead %d]"), fancy.buf, ours); else strbuf_addf(stat, _("[ahead %d]"), ours); } else { if (ref) strbuf_addf(stat, _("[%s: ahead %d, behind %d]"), - ref, ours, theirs); + fancy.buf, ours, theirs); else strbuf_addf(stat, _("[ahead %d, behind %d]"), ours, theirs); } + strbuf_release(&fancy); strbuf_addch(stat, ' '); free(ref); } @@ -550,6 +571,29 @@ static int calc_maxwidth(struct ref_list *refs) return w; } +static char *get_head_description(void) +{ + struct strbuf desc = STRBUF_INIT; + struct wt_status_state state; + memset(&state, 0, sizeof(state)); + wt_status_get_state(&state, 1); + if (state.rebase_in_progress || + state.rebase_interactive_in_progress) + strbuf_addf(&desc, _("(no branch, rebasing %s)"), + state.branch); + else if (state.bisect_in_progress) + strbuf_addf(&desc, _("(no branch, bisect started on %s)"), + state.branch); + else if (state.detached_from) + strbuf_addf(&desc, _("(detached from %s)"), + state.detached_from); + else + strbuf_addstr(&desc, _("(no branch)")); + free(state.branch); + free(state.onto); + free(state.detached_from); + return strbuf_detach(&desc, NULL); +} static void show_detached(struct ref_list *ref_list) { @@ -557,7 +601,7 @@ static void show_detached(struct ref_list *ref_list) if (head_commit && is_descendant_of(head_commit, ref_list->with_commit)) { struct ref_item item; - item.name = xstrdup(_("(no branch)")); + item.name = get_head_description(); item.width = utf8_strwidth(item.name); item.kind = REF_LOCAL_BRANCH; item.dest = NULL; @@ -880,7 +924,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (edit_branch_description(branch_name)) return 1; } else if (rename) { - if (argc == 1) + if (!argc) + die(_("branch name required")); + else if (argc == 1) rename_branch(head, argv[0], rename > 1); else if (argc == 2) rename_branch(argv[0], argv[1], rename > 1); diff --git a/builtin/checkout.c b/builtin/checkout.c index a9c1b5a95f..eb51872347 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -271,24 +271,55 @@ static int checkout_paths(const struct checkout_opts *opts, ; ps_matched = xcalloc(1, pos); + /* + * Make sure all pathspecs participated in locating the paths + * to be checked out. + */ for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; + ce->ce_flags &= ~CE_MATCHED; if (opts->source_tree && !(ce->ce_flags & CE_UPDATE)) + /* + * "git checkout tree-ish -- path", but this entry + * is in the original index; it will not be checked + * out to the working tree and it does not matter + * if pathspec matched this entry. We will not do + * anything to this entry at all. + */ continue; - match_pathspec(opts->pathspec, ce->name, ce_namelen(ce), 0, ps_matched); + /* + * Either this entry came from the tree-ish we are + * checking the paths out of, or we are checking out + * of the index. + * + * If it comes from the tree-ish, we already know it + * matches the pathspec and could just stamp + * CE_MATCHED to it from update_some(). But we still + * need ps_matched and read_tree_recursive (and + * eventually tree_entry_interesting) cannot fill + * ps_matched yet. Once it can, we can avoid calling + * match_pathspec() for _all_ entries when + * opts->source_tree != NULL. + */ + if (match_pathspec(opts->pathspec, ce->name, ce_namelen(ce), + 0, ps_matched)) + ce->ce_flags |= CE_MATCHED; } - if (report_path_error(ps_matched, opts->pathspec, opts->prefix)) + if (report_path_error(ps_matched, opts->pathspec, opts->prefix)) { + free(ps_matched); return 1; + } + free(ps_matched); /* "checkout -m path" to recreate conflicted state */ if (opts->merge) - unmerge_cache(opts->pathspec); + unmerge_marked_index(&the_index); /* Any unmerged paths? */ for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; - if (match_pathspec(opts->pathspec, ce->name, ce_namelen(ce), 0, NULL)) { + if (ce->ce_flags & CE_MATCHED) { if (!ce_stage(ce)) continue; if (opts->force) { @@ -313,9 +344,7 @@ static int checkout_paths(const struct checkout_opts *opts, state.refresh_cache = 1; for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; - if (opts->source_tree && !(ce->ce_flags & CE_UPDATE)) - continue; - if (match_pathspec(opts->pathspec, ce->name, ce_namelen(ce), 0, NULL)) { + if (ce->ce_flags & CE_MATCHED) { if (!ce_stage(ce)) { errs |= checkout_entry(ce, &state, NULL); continue; @@ -700,7 +729,7 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs) "If you want to keep them by creating a new branch, " "this may be a good time\nto do so with:\n\n" " git branch new_branch_name %s\n\n"), - sha1_to_hex(commit->object.sha1)); + find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); } /* diff --git a/builtin/clone.c b/builtin/clone.c index e0aaf13583..f9c380eb6c 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -23,6 +23,7 @@ #include "branch.h" #include "remote.h" #include "run-command.h" +#include "connected.h" /* * Overall FIXMEs: @@ -376,10 +377,32 @@ static void clone_local(const char *src_repo, const char *dest_repo) static const char *junk_work_tree; static const char *junk_git_dir; static pid_t junk_pid; +enum { + JUNK_LEAVE_NONE, + JUNK_LEAVE_REPO, + JUNK_LEAVE_ALL +} junk_mode = JUNK_LEAVE_NONE; + +static const char junk_leave_repo_msg[] = +N_("Clone succeeded, but checkout failed.\n" + "You can inspect what was checked out with 'git status'\n" + "and retry the checkout with 'git checkout -f HEAD'\n"); static void remove_junk(void) { struct strbuf sb = STRBUF_INIT; + + switch (junk_mode) { + case JUNK_LEAVE_REPO: + warning("%s", _(junk_leave_repo_msg)); + /* fall-through */ + case JUNK_LEAVE_ALL: + return; + default: + /* proceed to removal */ + break; + } + if (getpid() != junk_pid) return; if (junk_git_dir) { @@ -485,12 +508,37 @@ static void write_followtags(const struct ref *refs, const char *msg) } } +static int iterate_ref_map(void *cb_data, unsigned char sha1[20]) +{ + struct ref **rm = cb_data; + struct ref *ref = *rm; + + /* + * Skip anything missing a peer_ref, which we are not + * actually going to write a ref for. + */ + while (ref && !ref->peer_ref) + ref = ref->next; + /* Returning -1 notes "end of list" to the caller. */ + if (!ref) + return -1; + + hashcpy(sha1, ref->old_sha1); + *rm = ref->next; + return 0; +} + static void update_remote_refs(const struct ref *refs, const struct ref *mapped_refs, const struct ref *remote_head_points_at, const char *branch_top, const char *msg) { + const struct ref *rm = mapped_refs; + + if (check_everything_connected(iterate_ref_map, 0, &rm)) + die(_("remote did not send all necessary objects")); + if (refs) { write_remote_refs(mapped_refs); if (option_single_branch) @@ -579,7 +627,8 @@ static int checkout(void) tree = parse_tree_indirect(sha1); parse_tree(tree); init_tree_desc(&t, tree->buffer, tree->size); - unpack_trees(1, &t, &opts); + if (unpack_trees(1, &t, &opts) < 0) + die(_("unable to checkout working tree")); if (write_cache(fd, active_cache, active_nr) || commit_locked_index(lock_file)) @@ -898,12 +947,13 @@ int cmd_clone(int argc, const char **argv, const char *prefix) transport_unlock_pack(transport); transport_disconnect(transport); + junk_mode = JUNK_LEAVE_REPO; err = checkout(); strbuf_release(&reflog_msg); strbuf_release(&branch_top); strbuf_release(&key); strbuf_release(&value); - junk_pid = 0; + junk_mode = JUNK_LEAVE_ALL; return err; } diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c index eac901a0ee..f641ff2a89 100644 --- a/builtin/commit-tree.c +++ b/builtin/commit-tree.c @@ -10,7 +10,7 @@ #include "utf8.h" #include "gpg-interface.h" -static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-S<signer>] [-m <message>] [-F <file>] <sha1> <changelog"; +static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-S[<keyid>]] [-m <message>] [-F <file>] <sha1> <changelog"; static void new_parent(struct commit *parent, struct commit_list **parents_p) { diff --git a/builtin/commit.c b/builtin/commit.c index d21d07a1a8..46204375e7 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -702,7 +702,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, previous = eol; } - append_signoff(&sb, ignore_footer); + append_signoff(&sb, ignore_footer, 0); } if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len) diff --git a/builtin/count-objects.c b/builtin/count-objects.c index 9afaa88f77..a7f70cb858 100644 --- a/builtin/count-objects.c +++ b/builtin/count-objects.c @@ -9,11 +9,22 @@ #include "builtin.h" #include "parse-options.h" +static unsigned long garbage; +static off_t size_garbage; + +static void real_report_garbage(const char *desc, const char *path) +{ + struct stat st; + if (!stat(path, &st)) + size_garbage += st.st_size; + warning("%s: %s", desc, path); + garbage++; +} + static void count_objects(DIR *d, char *path, int len, int verbose, unsigned long *loose, off_t *loose_size, - unsigned long *packed_loose, - unsigned long *garbage) + unsigned long *packed_loose) { struct dirent *ent; while ((ent = readdir(d)) != NULL) { @@ -46,9 +57,11 @@ static void count_objects(DIR *d, char *path, int len, int verbose, } if (bad) { if (verbose) { - error("garbage found: %.*s/%s", - len + 2, path, ent->d_name); - (*garbage)++; + struct strbuf sb = STRBUF_INIT; + strbuf_addf(&sb, "%.*s/%s", + len + 2, path, ent->d_name); + report_garbage("garbage found", sb.buf); + strbuf_release(&sb); } continue; } @@ -66,20 +79,22 @@ static void count_objects(DIR *d, char *path, int len, int verbose, } static char const * const count_objects_usage[] = { - N_("git count-objects [-v]"), + N_("git count-objects [-v] [-H | --human-readable]"), NULL }; int cmd_count_objects(int argc, const char **argv, const char *prefix) { - int i, verbose = 0; + int i, verbose = 0, human_readable = 0; const char *objdir = get_object_directory(); int len = strlen(objdir); char *path = xmalloc(len + 50); - unsigned long loose = 0, packed = 0, packed_loose = 0, garbage = 0; + unsigned long loose = 0, packed = 0, packed_loose = 0; off_t loose_size = 0; struct option opts[] = { OPT__VERBOSE(&verbose, N_("be verbose")), + OPT_BOOL('H', "human-readable", &human_readable, + N_("print sizes in human readable format")), OPT_END(), }; @@ -87,6 +102,8 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix) /* we do not take arguments other than flags for now */ if (argc) usage_with_options(count_objects_usage, opts); + if (verbose) + report_garbage = real_report_garbage; memcpy(path, objdir, len); if (len && objdir[len-1] != '/') path[len++] = '/'; @@ -97,13 +114,16 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix) if (!d) continue; count_objects(d, path, len, verbose, - &loose, &loose_size, &packed_loose, &garbage); + &loose, &loose_size, &packed_loose); closedir(d); } if (verbose) { struct packed_git *p; unsigned long num_pack = 0; off_t size_pack = 0; + struct strbuf loose_buf = STRBUF_INIT; + struct strbuf pack_buf = STRBUF_INIT; + struct strbuf garbage_buf = STRBUF_INIT; if (!packed_git) prepare_packed_git(); for (p = packed_git; p; p = p->next) { @@ -115,16 +135,40 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix) size_pack += p->pack_size + p->index_size; num_pack++; } + + if (human_readable) { + strbuf_humanise_bytes(&loose_buf, loose_size); + strbuf_humanise_bytes(&pack_buf, size_pack); + strbuf_humanise_bytes(&garbage_buf, size_garbage); + } else { + strbuf_addf(&loose_buf, "%lu", + (unsigned long)(loose_size / 1024)); + strbuf_addf(&pack_buf, "%lu", + (unsigned long)(size_pack / 1024)); + strbuf_addf(&garbage_buf, "%lu", + (unsigned long)(size_garbage / 1024)); + } + printf("count: %lu\n", loose); - printf("size: %lu\n", (unsigned long) (loose_size / 1024)); + printf("size: %s\n", loose_buf.buf); printf("in-pack: %lu\n", packed); printf("packs: %lu\n", num_pack); - printf("size-pack: %lu\n", (unsigned long) (size_pack / 1024)); + printf("size-pack: %s\n", pack_buf.buf); printf("prune-packable: %lu\n", packed_loose); printf("garbage: %lu\n", garbage); + printf("size-garbage: %s\n", garbage_buf.buf); + strbuf_release(&loose_buf); + strbuf_release(&pack_buf); + strbuf_release(&garbage_buf); + } else { + struct strbuf buf = STRBUF_INIT; + if (human_readable) + strbuf_humanise_bytes(&buf, loose_size); + else + strbuf_addf(&buf, "%lu kilobytes", + (unsigned long)(loose_size / 1024)); + printf("%lu objects, %s\n", loose, buf.buf); + strbuf_release(&buf); } - else - printf("%lu objects, %lu kilobytes\n", - loose, (unsigned long) (loose_size / 1024)); return 0; } diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 77dffd1ce3..725c0a7dca 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -43,7 +43,7 @@ static int parse_opt_signed_tag_mode(const struct option *opt, else if (!strcmp(arg, "strip")) signed_tag_mode = STRIP; else - return error("Unknown signed-tag mode: %s", arg); + return error("Unknown signed-tags mode: %s", arg); return 0; } @@ -113,12 +113,13 @@ static void show_progress(void) printf("progress %d objects\n", counter); } -static void handle_object(const unsigned char *sha1) +static void export_blob(const unsigned char *sha1) { unsigned long size; enum object_type type; char *buf; struct object *object; + int eaten; if (no_data) return; @@ -126,16 +127,18 @@ static void handle_object(const unsigned char *sha1) if (is_null_sha1(sha1)) return; - object = parse_object(sha1); - if (!object) - die ("Could not read blob %s", sha1_to_hex(sha1)); - - if (object->flags & SHOWN) + object = lookup_object(sha1); + if (object && object->flags & SHOWN) return; buf = read_sha1_file(sha1, &type, &size); if (!buf) die ("Could not read blob %s", sha1_to_hex(sha1)); + if (check_sha1_signature(sha1, buf, size, typename(type)) < 0) + die("sha1 mismatch in blob %s", sha1_to_hex(sha1)); + object = parse_object_buffer(sha1, type, size, buf, &eaten); + if (!object) + die("Could not read blob %s", sha1_to_hex(sha1)); mark_next_object(object); @@ -147,7 +150,8 @@ static void handle_object(const unsigned char *sha1) show_progress(); object->flags |= SHOWN; - free(buf); + if (!eaten) + free(buf); } static int depth_first(const void *a_, const void *b_) @@ -312,7 +316,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev) /* Export the referenced blobs, and remember the marks. */ for (i = 0; i < diff_queued_diff.nr; i++) if (!S_ISGITLINK(diff_queued_diff.queue[i]->two->mode)) - handle_object(diff_queued_diff.queue[i]->two->sha1); + export_blob(diff_queued_diff.queue[i]->two->sha1); mark_next_object(&commit->object); if (!is_encoding_utf8(encoding)) @@ -416,7 +420,7 @@ static void handle_tag(const char *name, struct tag *tag) switch(signed_tag_mode) { case ABORT: die ("Encountered signed tag %s; use " - "--signed-tag=<mode> to handle it.", + "--signed-tags=<mode> to handle it.", sha1_to_hex(tag->object.sha1)); case WARN: warning ("Exporting signed tag %s", @@ -512,7 +516,7 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info, commit = (struct commit *)tag; break; case OBJ_BLOB: - handle_object(tag->object.sha1); + export_blob(tag->object.sha1); continue; default: /* OBJ_TAG (nested tags) is already handled */ warning("Tag points to object of unexpected type %s, skipping.", @@ -614,9 +618,12 @@ static void import_marks(char *input_file) || *mark_end != ' ' || get_sha1(mark_end + 1, sha1)) die("corrupt mark line: %s", line); + if (last_idnum < mark) + last_idnum = mark; + object = parse_object(sha1); if (!object) - die ("Could not read blob %s", sha1_to_hex(sha1)); + continue; if (object->flags & SHOWN) error("Object %s already has a mark", sha1_to_hex(sha1)); @@ -626,8 +633,6 @@ static void import_marks(char *input_file) continue; mark_object(object, mark); - if (last_idnum < mark) - last_idnum = mark; object->flags |= SHOWN; } @@ -641,6 +646,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) struct string_list extra_refs = STRING_LIST_INIT_NODUP; struct commit *commit; char *export_filename = NULL, *import_filename = NULL; + uint32_t lastimportid; struct option options[] = { OPT_INTEGER(0, "progress", &progress, N_("show progress after <n> objects")), @@ -684,6 +690,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) if (import_filename) import_marks(import_filename); + lastimportid = last_idnum; if (import_filename && revs.prune_data.nr) full_tree = 1; @@ -706,7 +713,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) handle_tags_and_duplicates(&extra_refs); - if (export_filename) + if (export_filename && lastimportid != last_idnum) export_marks(export_filename); if (use_done_feature) diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index 940ae35dc2..aba4465552 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -7,12 +7,31 @@ static const char fetch_pack_usage[] = "[--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] " "[--no-progress] [-v] [<host>:]<directory> [<refs>...]"; +static void add_sought_entry_mem(struct ref ***sought, int *nr, int *alloc, + const char *name, int namelen) +{ + struct ref *ref = xcalloc(1, sizeof(*ref) + namelen + 1); + + memcpy(ref->name, name, namelen); + ref->name[namelen] = '\0'; + (*nr)++; + ALLOC_GROW(*sought, *nr, *alloc); + (*sought)[*nr - 1] = ref; +} + +static void add_sought_entry(struct ref ***sought, int *nr, int *alloc, + const char *string) +{ + add_sought_entry_mem(sought, nr, alloc, string, strlen(string)); +} + int cmd_fetch_pack(int argc, const char **argv, const char *prefix) { int i, ret; struct ref *ref = NULL; const char *dest = NULL; - struct string_list sought = STRING_LIST_INIT_DUP; + struct ref **sought = NULL; + int nr_sought = 0, alloc_sought = 0; int fd[2]; char *pack_lockfile = NULL; char **pack_lockfile_ptr = NULL; @@ -94,27 +113,24 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) * refs from the standard input: */ for (; i < argc; i++) - string_list_append(&sought, xstrdup(argv[i])); + add_sought_entry(&sought, &nr_sought, &alloc_sought, argv[i]); if (args.stdin_refs) { if (args.stateless_rpc) { /* in stateless RPC mode we use pkt-line to read * from stdin, until we get a flush packet */ - static char line[1000]; for (;;) { - int n = packet_read_line(0, line, sizeof(line)); - if (!n) + char *line = packet_read_line(0, NULL); + if (!line) break; - if (line[n-1] == '\n') - n--; - string_list_append(&sought, xmemdupz(line, n)); + add_sought_entry(&sought, &nr_sought, &alloc_sought, line); } } else { /* read from stdin one ref per line, until EOF */ struct strbuf line = STRBUF_INIT; while (strbuf_getline(&line, stdin, '\n') != EOF) - string_list_append(&sought, strbuf_detach(&line, NULL)); + add_sought_entry(&sought, &nr_sought, &alloc_sought, line.buf); strbuf_release(&line); } } @@ -128,10 +144,10 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) args.verbose ? CONNECT_VERBOSE : 0); } - get_remote_heads(fd[0], &ref, 0, NULL); + get_remote_heads(fd[0], NULL, 0, &ref, 0, NULL); ref = fetch_pack(&args, fd, conn, ref, dest, - &sought, pack_lockfile_ptr); + sought, nr_sought, pack_lockfile_ptr); if (pack_lockfile) { printf("lock %s\n", pack_lockfile); fflush(stdout); @@ -141,7 +157,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) if (finish_connect(conn)) return 1; - ret = !ref || sought.nr; + ret = !ref; /* * If the heads to pull were given, we should have consumed @@ -149,8 +165,13 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) * remote no-such-ref' would silently succeed without issuing * an error. */ - for (i = 0; i < sought.nr; i++) - error("no such remote ref %s", sought.items[i].string); + for (i = 0; i < nr_sought; i++) { + if (!sought[i] || sought[i]->matched) + continue; + error("no such remote ref %s", sought[i]->name); + ret = 1; + } + while (ref) { printf("%s %s\n", sha1_to_hex(ref->old_sha1), ref->name); diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index 265a9253bf..1c04070869 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -287,10 +287,10 @@ static void credit_people(struct strbuf *out, const char *me; if (kind == 'a') { - label = "\n# By "; + label = "By"; me = git_author_info(IDENT_NO_DATE); } else { - label = "\n# Via "; + label = "Via"; me = git_committer_info(IDENT_NO_DATE); } @@ -300,7 +300,7 @@ static void credit_people(struct strbuf *out, (me = skip_prefix(me, them->items->string)) != NULL && skip_prefix(me, " <"))) return; - strbuf_addstr(out, label); + strbuf_addf(out, "\n%c %s ", comment_line_char, label); add_people_count(out, them); } @@ -503,14 +503,18 @@ static void fmt_merge_msg_sigs(struct strbuf *out) } else { if (tag_number == 2) { struct strbuf tagline = STRBUF_INIT; - strbuf_addf(&tagline, "\n# %s\n", - origins.items[first_tag].string); + strbuf_addch(&tagline, '\n'); + strbuf_add_commented_lines(&tagline, + origins.items[first_tag].string, + strlen(origins.items[first_tag].string)); strbuf_insert(&tagbuf, 0, tagline.buf, tagline.len); strbuf_release(&tagline); } - strbuf_addf(&tagbuf, "\n# %s\n", - origins.items[i].string); + strbuf_addch(&tagbuf, '\n'); + strbuf_add_commented_lines(&tagbuf, + origins.items[i].string, + strlen(origins.items[i].string)); fmt_tag_signature(&tagbuf, &sig, buf, len); } strbuf_release(&sig); diff --git a/builtin/help.c b/builtin/help.c index d1d71816a9..062957f629 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -36,10 +36,12 @@ enum help_format { static const char *html_path; static int show_all = 0; +static int show_guides = 0; static unsigned int colopts; static enum help_format help_format = HELP_FORMAT_NONE; static struct option builtin_help_options[] = { - OPT_BOOLEAN('a', "all", &show_all, N_("print all available commands")), + OPT_BOOL('a', "all", &show_all, N_("print all available commands")), + OPT_BOOL('g', "guides", &show_guides, N_("print list of useful guides")), OPT_SET_INT('m', "man", &help_format, N_("show man page"), HELP_FORMAT_MAN), OPT_SET_INT('w', "web", &help_format, N_("show manual in web browser"), HELP_FORMAT_WEB), @@ -49,7 +51,7 @@ static struct option builtin_help_options[] = { }; static const char * const builtin_help_usage[] = { - N_("git help [--all] [--man|--web|--info] [command]"), + N_("git help [--all] [--guides] [--man|--web|--info] [command]"), NULL }; @@ -413,6 +415,37 @@ static void show_html_page(const char *git_cmd) open_html(page_path.buf); } +static struct { + const char *name; + const char *help; +} common_guides[] = { + { "attributes", N_("Defining attributes per path") }, + { "glossary", N_("A Git glossary") }, + { "ignore", N_("Specifies intentionally untracked files to ignore") }, + { "modules", N_("Defining submodule properties") }, + { "revisions", N_("Specifying revisions and ranges for Git") }, + { "tutorial", N_("A tutorial introduction to Git (for version 1.5.1 or newer)") }, + { "workflows", N_("An overview of recommended workflows with Git") }, +}; + +static void list_common_guides_help(void) +{ + int i, longest = 0; + + for (i = 0; i < ARRAY_SIZE(common_guides); i++) { + if (longest < strlen(common_guides[i].name)) + longest = strlen(common_guides[i].name); + } + + puts(_("The common Git guides are:\n")); + for (i = 0; i < ARRAY_SIZE(common_guides); i++) { + printf(" %s ", common_guides[i].name); + mput_char(' ', longest - strlen(common_guides[i].name)); + puts(_(common_guides[i].help)); + } + putchar('\n'); +} + int cmd_help(int argc, const char **argv, const char *prefix) { int nongit; @@ -428,7 +461,16 @@ int cmd_help(int argc, const char **argv, const char *prefix) git_config(git_help_config, NULL); printf(_("usage: %s%s"), _(git_usage_string), "\n\n"); list_commands(colopts, &main_cmds, &other_cmds); + } + + if (show_guides) + list_common_guides_help(); + + if (show_all || show_guides) { printf("%s\n", _(git_more_info_string)); + /* + * We're done. Ignore any remaining args + */ return 0; } diff --git a/builtin/log.c b/builtin/log.c index 8f0b2e84fe..ad46f72950 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -23,6 +23,7 @@ #include "streaming.h" #include "version.h" #include "mailmap.h" +#include "gpg-interface.h" /* Set a default date-time format for git log ("log.date" config variable) */ static const char *default_date_mode = NULL; @@ -99,9 +100,9 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, int quiet = 0, source = 0, mailmap = 0; const struct option builtin_log_options[] = { - OPT_BOOLEAN(0, "quiet", &quiet, N_("suppress diff output")), - OPT_BOOLEAN(0, "source", &source, N_("show source")), - OPT_BOOLEAN(0, "use-mailmap", &mailmap, N_("Use mail map file")), + OPT_BOOL(0, "quiet", &quiet, N_("suppress diff output")), + OPT_BOOL(0, "source", &source, N_("show source")), + OPT_BOOL(0, "use-mailmap", &mailmap, N_("Use mail map file")), { OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"), PARSE_OPT_OPTARG, decorate_callback}, OPT_END() @@ -367,6 +368,8 @@ static int git_log_config(const char *var, const char *value, void *cb) if (grep_config(var, value, cb) < 0) return -1; + if (git_gpg_config(var, value, cb) < 0) + return -1; return git_diff_ui_config(var, value, cb); } @@ -619,6 +622,14 @@ static void add_header(const char *value) static int thread; static int do_signoff; static const char *signature = git_version_string; +static int config_cover_letter; + +enum { + COVER_UNSET, + COVER_OFF, + COVER_ON, + COVER_AUTO +}; static int git_format_config(const char *var, const char *value, void *cb) { @@ -680,6 +691,14 @@ static int git_format_config(const char *var, const char *value, void *cb) } if (!strcmp(var, "format.signature")) return git_config_string(&signature, var, value); + if (!strcmp(var, "format.coverletter")) { + if (value && !strcasecmp(value, "auto")) { + config_cover_letter = COVER_AUTO; + return 0; + } + config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF; + return 0; + } return git_log_config(var, value, cb); } @@ -791,9 +810,37 @@ static void add_branch_description(struct strbuf *buf, const char *branch_name) } } +static char *find_branch_name(struct rev_info *rev) +{ + int i, positive = -1; + unsigned char branch_sha1[20]; + const unsigned char *tip_sha1; + const char *ref; + char *full_ref, *branch = NULL; + + for (i = 0; i < rev->cmdline.nr; i++) { + if (rev->cmdline.rev[i].flags & UNINTERESTING) + continue; + if (positive < 0) + positive = i; + else + return NULL; + } + if (positive < 0) + return NULL; + ref = rev->cmdline.rev[positive].name; + tip_sha1 = rev->cmdline.rev[positive].item->sha1; + if (dwim_ref(ref, strlen(ref), branch_sha1, &full_ref) && + !prefixcmp(full_ref, "refs/heads/") && + !hashcmp(tip_sha1, branch_sha1)) + branch = xstrdup(full_ref + strlen("refs/heads/")); + free(full_ref); + return branch; +} + static void make_cover_letter(struct rev_info *rev, int use_stdout, struct commit *origin, - int nr, struct commit **list, struct commit *head, + int nr, struct commit **list, const char *branch_name, int quiet) { @@ -807,6 +854,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, struct diff_options opts; int need_8bit_cte = 0; struct pretty_print_context pp = {0}; + struct commit *head = list[0]; if (rev->commit_format != CMIT_FMT_EMAIL) die(_("Cover letter needs email format")); @@ -824,6 +872,9 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, if (has_non_ascii(list[i]->buffer)) need_8bit_cte = 1; + if (!branch_name) + branch_name = find_branch_name(rev); + msg = body; pp.fmt = CMIT_FMT_EMAIL; pp.date_mode = DATE_RFC2822; @@ -1030,45 +1081,6 @@ static int cc_callback(const struct option *opt, const char *arg, int unset) return 0; } -static char *find_branch_name(struct rev_info *rev) -{ - int i, positive = -1; - unsigned char branch_sha1[20]; - const unsigned char *tip_sha1; - const char *ref; - char *full_ref, *branch = NULL; - - for (i = 0; i < rev->cmdline.nr; i++) { - if (rev->cmdline.rev[i].flags & UNINTERESTING) - continue; - if (positive < 0) - positive = i; - else - return NULL; - } - if (0 <= positive) { - ref = rev->cmdline.rev[positive].name; - tip_sha1 = rev->cmdline.rev[positive].item->sha1; - } else if (!rev->cmdline.nr && rev->pending.nr == 1 && - !strcmp(rev->pending.objects[0].name, "HEAD")) { - /* - * No actual ref from command line, but "HEAD" from - * rev->def was added in setup_revisions() - * e.g. format-patch --cover-letter -12 - */ - ref = "HEAD"; - tip_sha1 = rev->pending.objects[0].item->sha1; - } else { - return NULL; - } - if (dwim_ref(ref, strlen(ref), branch_sha1, &full_ref) && - !prefixcmp(full_ref, "refs/heads/") && - !hashcmp(tip_sha1, branch_sha1)) - branch = xstrdup(full_ref + strlen("refs/heads/")); - free(full_ref); - return branch; -} - int cmd_format_patch(int argc, const char **argv, const char *prefix) { struct commit *commit; @@ -1080,13 +1092,12 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) int start_number = -1; int just_numbers = 0; int ignore_if_in_upstream = 0; - int cover_letter = 0; + int cover_letter = -1; int boundary_count = 0; int no_binary_diff = 0; - struct commit *origin = NULL, *head = NULL; + struct commit *origin = NULL; const char *in_reply_to = NULL; struct patch_ids ids; - char *add_signoff = NULL; struct strbuf buf = STRBUF_INIT; int use_patch_format = 0; int quiet = 0; @@ -1099,12 +1110,12 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) { OPTION_CALLBACK, 'N', "no-numbered", &numbered, NULL, N_("use [PATCH] even with multiple patches"), PARSE_OPT_NOARG, no_numbered_callback }, - OPT_BOOLEAN('s', "signoff", &do_signoff, N_("add Signed-off-by:")), - OPT_BOOLEAN(0, "stdout", &use_stdout, + OPT_BOOL('s', "signoff", &do_signoff, N_("add Signed-off-by:")), + OPT_BOOL(0, "stdout", &use_stdout, N_("print patches to standard out")), - OPT_BOOLEAN(0, "cover-letter", &cover_letter, + OPT_BOOL(0, "cover-letter", &cover_letter, N_("generate a cover letter")), - OPT_BOOLEAN(0, "numbered-files", &just_numbers, + OPT_BOOL(0, "numbered-files", &just_numbers, N_("use simple number sequence for output file names")), OPT_STRING(0, "suffix", &fmt_patch_suffix, N_("sfx"), N_("use <sfx> instead of '.patch'")), @@ -1193,16 +1204,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.subject_prefix = strbuf_detach(&sprefix, NULL); } - if (do_signoff) { - const char *committer; - const char *endpos; - committer = git_committer_info(IDENT_STRICT); - endpos = strchr(committer, '>'); - if (!endpos) - die(_("bogus committer info %s"), committer); - add_signoff = xmemdupz(committer, endpos - committer + 1); - } - for (i = 0; i < extra_hdr.nr; i++) { strbuf_addstr(&buf, extra_hdr.items[i].string); strbuf_addch(&buf, '\n'); @@ -1288,28 +1289,36 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) } if (rev.pending.nr == 1) { + int check_head = 0; + if (rev.max_count < 0 && !rev.show_root_diff) { /* * This is traditional behaviour of "git format-patch * origin" that prepares what the origin side still * does not have. */ - unsigned char sha1[20]; - const char *ref; - rev.pending.objects[0].item->flags |= UNINTERESTING; add_head_to_pending(&rev); - ref = resolve_ref_unsafe("HEAD", sha1, 1, NULL); - if (ref && !prefixcmp(ref, "refs/heads/")) - branch_name = xstrdup(ref + strlen("refs/heads/")); - else - branch_name = xstrdup(""); /* no branch */ + check_head = 1; } /* * Otherwise, it is "format-patch -22 HEAD", and/or * "format-patch --root HEAD". The user wants * get_revision() to do the usual traversal. */ + + if (!strcmp(rev.pending.objects[0].name, "HEAD")) + check_head = 1; + + if (check_head) { + unsigned char sha1[20]; + const char *ref; + ref = resolve_ref_unsafe("HEAD", sha1, 1, NULL); + if (ref && !prefixcmp(ref, "refs/heads/")) + branch_name = xstrdup(ref + strlen("refs/heads/")); + else + branch_name = xstrdup(""); /* no branch */ + } } /* @@ -1318,29 +1327,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) */ rev.show_root_diff = 1; - if (cover_letter) { - /* - * NEEDSWORK:randomly pick one positive commit to show - * diffstat; this is often the tip and the command - * happens to do the right thing in most cases, but a - * complex command like "--cover-letter a b c ^bottom" - * picks "c" and shows diffstat between bottom..c - * which may not match what the series represents at - * all and totally broken. - */ - int i; - for (i = 0; i < rev.pending.nr; i++) { - struct object *o = rev.pending.objects[i].item; - if (!(o->flags & UNINTERESTING)) - head = (struct commit *)o; - } - /* There is nothing to show; it is not an error, though. */ - if (!head) - return 0; - if (!branch_name) - branch_name = find_branch_name(&rev); - } - if (ignore_if_in_upstream) { /* Don't say anything if head and upstream are the same. */ if (rev.pending.nr == 2) { @@ -1372,11 +1358,21 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) list = xrealloc(list, nr * sizeof(list[0])); list[nr - 1] = commit; } + if (nr == 0) + /* nothing to do */ + return 0; total = nr; if (!keep_subject && auto_number && total > 1) numbered = 1; if (numbered) rev.total = total + start_number - 1; + if (cover_letter == -1) { + if (config_cover_letter == COVER_AUTO) + cover_letter = (total > 1); + else + cover_letter = (config_cover_letter == COVER_ON); + } + if (in_reply_to || thread || cover_letter) rev.ref_message_ids = xcalloc(1, sizeof(struct string_list)); if (in_reply_to) { @@ -1389,11 +1385,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (thread) gen_message_id(&rev, "cover"); make_cover_letter(&rev, use_stdout, - origin, nr, list, head, branch_name, quiet); + origin, nr, list, branch_name, quiet); total++; start_number--; } - rev.add_signoff = add_signoff; + rev.add_signoff = do_signoff; while (0 <= --nr) { int shown; commit = list[nr]; diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index bc912e399e..ed25d81b88 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -155,6 +155,11 @@ static int same_entry(struct name_entry *a, struct name_entry *b) a->mode == b->mode; } +static int both_empty(struct name_entry *a, struct name_entry *b) +{ + return !(a->sha1 || b->sha1); +} + static struct merge_list *create_entry(unsigned stage, unsigned mode, const unsigned char *sha1, const char *path) { struct merge_list *res = xcalloc(1, sizeof(*res)); @@ -297,13 +302,10 @@ static void unresolved(const struct traverse_info *info, struct name_entry n[3]) static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *info) { /* Same in both? */ - if (same_entry(entry+1, entry+2)) { - if (entry[0].sha1) { - /* Modified identically */ - resolve(info, NULL, entry+1); - return mask; - } - /* "Both added the same" is left unresolved */ + if (same_entry(entry+1, entry+2) || both_empty(entry+0, entry+2)) { + /* Modified, added or removed identically */ + resolve(info, NULL, entry+1); + return mask; } if (same_entry(entry+0, entry+1)) { @@ -319,12 +321,10 @@ static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, s */ } - if (same_entry(entry+0, entry+2)) { - if (entry[1].sha1 && !S_ISDIR(entry[1].mode)) { - /* We modified, they did not touch -- take ours */ - resolve(info, NULL, entry+1); - return mask; - } + if (same_entry(entry+0, entry+2) || both_empty(entry+0, entry+2)) { + /* We added, modified or removed, they did not touch -- take ours */ + resolve(info, NULL, entry+1); + return mask; } unresolved(info, entry); diff --git a/builtin/merge.c b/builtin/merge.c index 7c8922c8b0..3e2daa37c3 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -49,7 +49,7 @@ static const char * const builtin_merge_usage[] = { static int show_diffstat = 1, shortlog_len = -1, squash; static int option_commit = 1, allow_fast_forward = 1; static int fast_forward_only, option_edit = -1; -static int allow_trivial = 1, have_message; +static int allow_trivial = 1, have_message, verify_signatures; static int overwrite_ignore = 1; static struct strbuf merge_msg = STRBUF_INIT; static struct strategy **use_strategies; @@ -199,6 +199,8 @@ static struct option builtin_merge_options[] = { OPT_BOOLEAN(0, "ff-only", &fast_forward_only, N_("abort if fast-forward is not possible")), OPT_RERERE_AUTOUPDATE(&allow_rerere_auto), + OPT_BOOL(0, "verify-signatures", &verify_signatures, + N_("Verify that the named commit has a valid GPG signature")), OPT_CALLBACK('s', "strategy", &use_strategies, N_("strategy"), N_("merge strategy to use"), option_parse_strategy), OPT_CALLBACK('X', "strategy-option", &xopts, N_("option=value"), @@ -516,6 +518,19 @@ static void merge_name(const char *remote, struct strbuf *msg) strbuf_release(&line); goto cleanup; } + + if (remote_head->util) { + struct merge_remote_desc *desc; + desc = merge_remote_util(remote_head); + if (desc && desc->obj && desc->obj->type == OBJ_TAG) { + strbuf_addf(msg, "%s\t\t%s '%s'\n", + sha1_to_hex(desc->obj->sha1), + typename(desc->obj->type), + remote); + goto cleanup; + } + } + strbuf_addf(msg, "%s\t\tcommit '%s'\n", sha1_to_hex(remote_head->object.sha1), remote); cleanup: @@ -1233,6 +1248,39 @@ int cmd_merge(int argc, const char **argv, const char *prefix) usage_with_options(builtin_merge_usage, builtin_merge_options); + if (verify_signatures) { + for (p = remoteheads; p; p = p->next) { + struct commit *commit = p->item; + char hex[41]; + struct signature_check signature_check; + memset(&signature_check, 0, sizeof(signature_check)); + + check_commit_signature(commit, &signature_check); + + strcpy(hex, find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); + switch (signature_check.result) { + case 'G': + break; + case 'U': + die(_("Commit %s has an untrusted GPG signature, " + "allegedly by %s."), hex, signature_check.signer); + case 'B': + die(_("Commit %s has a bad GPG signature " + "allegedly by %s."), hex, signature_check.signer); + default: /* 'N' */ + die(_("Commit %s does not have a GPG signature."), hex); + } + if (verbosity >= 0 && signature_check.result == 'G') + printf(_("Commit %s has a good GPG signature by %s\n"), + hex, signature_check.signer); + + free(signature_check.gpg_output); + free(signature_check.gpg_status); + free(signature_check.signer); + free(signature_check.key); + } + } + strbuf_addstr(&buf, "merge"); for (p = remoteheads; p; p = p->next) strbuf_addf(&buf, " %s", merge_remote_util(p->item)->name); diff --git a/builtin/push.c b/builtin/push.c index 42b129d36c..909c34dfda 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -322,7 +322,7 @@ static int push_with_options(struct transport *transport, int flags) static int do_push(const char *repo, int flags) { int i, errs; - struct remote *remote = remote_get(repo); + struct remote *remote = pushremote_get(repo); const char **url; int url_nr; @@ -437,6 +437,8 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BIT(0, "prune", &flags, N_("prune locally removed refs"), TRANSPORT_PUSH_PRUNE), OPT_BIT(0, "no-verify", &flags, N_("bypass pre-push hook"), TRANSPORT_PUSH_NO_HOOK), + OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant tags"), + TRANSPORT_PUSH_FOLLOW_TAGS), OPT_END() }; diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 62ba6e7a3d..ccebd74f16 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -754,17 +754,15 @@ static struct command *read_head_info(void) struct command *commands = NULL; struct command **p = &commands; for (;;) { - static char line[1000]; + char *line; unsigned char old_sha1[20], new_sha1[20]; struct command *cmd; char *refname; int len, reflen; - len = packet_read_line(0, line, sizeof(line)); - if (!len) + line = packet_read_line(0, &len); + if (!line) break; - if (line[len-1] == '\n') - line[--len] = 0; if (len < 83 || line[40] != ' ' || line[81] != ' ' || @@ -932,7 +930,7 @@ static void report(struct command *commands, const char *unpack_status) if (use_sideband) send_sideband(1, 1, buf.buf, buf.len, use_sideband); else - safe_write(1, buf.buf, buf.len); + write_or_die(1, buf.buf, buf.len); strbuf_release(&buf); } diff --git a/builtin/rm.c b/builtin/rm.c index dabfcf6890..7b91d52f39 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -110,7 +110,7 @@ static int check_local_mod(unsigned char *head, int index_only) ce = active_cache[pos]; if (lstat(ce->name, &st) < 0) { - if (errno != ENOENT) + if (errno != ENOENT && errno != ENOTDIR) warning("'%s': %s", ce->name, strerror(errno)); /* It already vanished from the working tree */ continue; diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 57a46b2654..152c4ea092 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -79,7 +79,7 @@ static void print_helper_status(struct ref *ref) } strbuf_addch(&buf, '\n'); - safe_write(1, buf.buf, buf.len); + write_or_die(1, buf.buf, buf.len); } strbuf_release(&buf); } @@ -207,7 +207,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) memset(&extra_have, 0, sizeof(extra_have)); - get_remote_heads(fd[0], &remote_refs, REF_NORMAL, &extra_have); + get_remote_heads(fd[0], NULL, 0, &remote_refs, REF_NORMAL, &extra_have); transport_verify_remote_names(nr_refspecs, refspecs); diff --git a/builtin/show-branch.c b/builtin/show-branch.c index d208fd6c68..90fc6b1b9d 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -162,29 +162,28 @@ static void name_commits(struct commit_list *list, nth = 0; while (parents) { struct commit *p = parents->item; - char newname[1000], *en; + struct strbuf newname = STRBUF_INIT; parents = parents->next; nth++; if (p->util) continue; - en = newname; switch (n->generation) { case 0: - en += sprintf(en, "%s", n->head_name); + strbuf_addstr(&newname, n->head_name); break; case 1: - en += sprintf(en, "%s^", n->head_name); + strbuf_addf(&newname, "%s^", n->head_name); break; default: - en += sprintf(en, "%s~%d", - n->head_name, n->generation); + strbuf_addf(&newname, "%s~%d", + n->head_name, n->generation); break; } if (nth == 1) - en += sprintf(en, "^"); + strbuf_addch(&newname, '^'); else - en += sprintf(en, "^%d", nth); - name_commit(p, xstrdup(newname), 0); + strbuf_addf(&newname, "^%d", nth); + name_commit(p, strbuf_detach(&newname, NULL), 0); i++; name_first_parent_chain(p); } diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c index b928beb8ed..af2da35e7d 100644 --- a/builtin/upload-archive.c +++ b/builtin/upload-archive.c @@ -7,6 +7,7 @@ #include "pkt-line.h" #include "sideband.h" #include "run-command.h" +#include "argv-array.h" static const char upload_archive_usage[] = "git upload-archive <repo>"; @@ -18,51 +19,31 @@ static const char deadchild[] = int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix) { - const char *sent_argv[MAX_ARGS]; + struct argv_array sent_argv = ARGV_ARRAY_INIT; const char *arg_cmd = "argument "; - char *p, buf[4096]; - int sent_argc; - int len; if (argc != 2) usage(upload_archive_usage); - if (strlen(argv[1]) + 1 > sizeof(buf)) - die("insanely long repository name"); - - strcpy(buf, argv[1]); /* enter-repo smudges its argument */ - - if (!enter_repo(buf, 0)) - die("'%s' does not appear to be a git repository", buf); + if (!enter_repo(argv[1], 0)) + die("'%s' does not appear to be a git repository", argv[1]); /* put received options in sent_argv[] */ - sent_argc = 1; - sent_argv[0] = "git-upload-archive"; - for (p = buf;;) { - /* This will die if not enough free space in buf */ - len = packet_read_line(0, p, (buf + sizeof buf) - p); - if (len == 0) + argv_array_push(&sent_argv, "git-upload-archive"); + for (;;) { + char *buf = packet_read_line(0, NULL); + if (!buf) break; /* got a flush */ - if (sent_argc > MAX_ARGS - 2) - die("Too many options (>%d)", MAX_ARGS - 2); + if (sent_argv.argc > MAX_ARGS) + die("Too many options (>%d)", MAX_ARGS - 1); - if (p[len-1] == '\n') { - p[--len] = 0; - } - if (len < strlen(arg_cmd) || - strncmp(arg_cmd, p, strlen(arg_cmd))) + if (prefixcmp(buf, arg_cmd)) die("'argument' token or flush expected"); - - len -= strlen(arg_cmd); - memmove(p, p + strlen(arg_cmd), len); - sent_argv[sent_argc++] = p; - p += len; - *p++ = 0; + argv_array_push(&sent_argv, buf + strlen(arg_cmd)); } - sent_argv[sent_argc] = NULL; /* parse all options sent by the client */ - return write_archive(sent_argc, sent_argv, prefix, 0, NULL, 1); + return write_archive(sent_argv.argc, sent_argv.argv, prefix, 0, NULL, 1); } __attribute__((format (printf, 1, 2))) @@ -57,7 +57,7 @@ static int parse_bundle_header(int fd, struct bundle_header *header, * followed by SP and subject line. */ if (get_sha1_hex(buf.buf, sha1) || - (40 <= buf.len && !isspace(buf.buf[40])) || + (buf.len > 40 && !isspace(buf.buf[40])) || (!is_prereq && buf.len <= 40)) { if (report_path) error(_("unrecognized header: %s%s (%d)"), @@ -34,6 +34,7 @@ int git_inflate(git_zstream *, int flush); void git_deflate_init(git_zstream *, int level); void git_deflate_init_gzip(git_zstream *, int level); +void git_deflate_init_raw(git_zstream *, int level); void git_deflate_end(git_zstream *); int git_deflate_abort(git_zstream *); int git_deflate_end_gently(git_zstream *); @@ -161,6 +162,9 @@ struct cache_entry { #define CE_UNPACKED (1 << 24) #define CE_NEW_SKIP_WORKTREE (1 << 25) +/* used to temporarily mark paths matched by pathspecs */ +#define CE_MATCHED (1 << 26) + /* * Extended on-disk flags */ @@ -307,6 +311,7 @@ extern void free_name_hash(struct index_state *istate); #define resolve_undo_clear() resolve_undo_clear_index(&the_index) #define unmerge_cache_entry_at(at) unmerge_index_entry_at(&the_index, at) #define unmerge_cache(pathspec) unmerge_index(&the_index, pathspec) +#define read_blob_data_from_cache(path, sz) read_blob_data_from_index(&the_index, (path), (sz)) #endif enum object_type { @@ -461,11 +466,13 @@ extern int remove_file_from_index(struct index_state *, const char *path); #define ADD_CACHE_IGNORE_ERRORS 4 #define ADD_CACHE_IGNORE_REMOVAL 8 #define ADD_CACHE_INTENT 16 +#define ADD_CACHE_IMPLICIT_DOT 32 /* internal to "git add -u/-A" */ extern int add_to_index(struct index_state *, const char *path, struct stat *, int flags); extern int add_file_to_index(struct index_state *, const char *path, int flags); extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, int refresh); extern int ce_same_name(struct cache_entry *a, struct cache_entry *b); extern int index_name_is_other(const struct index_state *, const char *, int); +extern void *read_blob_data_from_index(struct index_state *, const char *, unsigned long *); /* do stat comparison even if CE_VALID is true */ #define CE_MATCH_IGNORE_VALID 01 @@ -715,8 +722,7 @@ enum sharedrepo { PERM_EVERYBODY = 0664 }; int git_config_perm(const char *var, const char *value); -int set_shared_perm(const char *path, int mode); -#define adjust_shared_perm(path) set_shared_perm((path), 0) +int adjust_shared_perm(const char *path); int safe_create_leading_directories(char *path); int safe_create_leading_directories_const(const char *path); int mkdir_in_gitdir(const char *path); @@ -1019,7 +1025,8 @@ struct ref { force:1, forced_update:1, merge:1, - deletion:1; + deletion:1, + matched:1; enum { REF_STATUS_NONE = 0, REF_STATUS_OK, @@ -1051,7 +1058,9 @@ struct extra_have_objects { int nr, alloc; unsigned char (*array)[20]; }; -extern struct ref **get_remote_heads(int in, struct ref **list, unsigned int flags, struct extra_have_objects *); +extern struct ref **get_remote_heads(int in, char *src_buf, size_t src_len, + struct ref **list, unsigned int flags, + struct extra_have_objects *); extern int server_supports(const char *feature); extern int parse_feature_request(const char *features, const char *feature); extern const char *server_feature_value(const char *feature, int *len_ret); @@ -1059,6 +1068,9 @@ extern const char *parse_feature_value(const char *feature_list, const char *fea extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path); +/* A hook for count-objects to report invalid files in pack directory */ +extern void (*report_garbage)(const char *desc, const char *path); + extern void prepare_packed_git(void); extern void reprepare_packed_git(void); extern void install_packed_git(struct packed_git *pack); diff --git a/combine-diff.c b/combine-diff.c index 35d41cd56d..77d7872aaf 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -5,6 +5,7 @@ #include "diffcore.h" #include "quote.h" #include "xdiff-interface.h" +#include "xdiff/xmacros.h" #include "log-tree.h" #include "refs.h" #include "userdiff.h" @@ -73,16 +74,24 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, /* Lines lost from parent */ struct lline { - struct lline *next; + struct lline *next, *prev; int len; unsigned long parent_map; char line[FLEX_ARRAY]; }; +/* Lines lost from current parent (before coalescing) */ +struct plost { + struct lline *lost_head, *lost_tail; + int len; +}; + /* Lines surviving in the merge result */ struct sline { - struct lline *lost_head, **lost_tail; - struct lline *next_lost; + /* Accumulated and coalesced lost lines */ + struct lline *lost; + int lenlost; + struct plost plost; char *bol; int len; /* bit 0 up to (N-1) are on if the parent has this line (i.e. @@ -94,6 +103,172 @@ struct sline { unsigned long *p_lno; }; +static int match_string_spaces(const char *line1, int len1, + const char *line2, int len2, + long flags) +{ + if (flags & XDF_WHITESPACE_FLAGS) { + for (; len1 > 0 && XDL_ISSPACE(line1[len1 - 1]); len1--); + for (; len2 > 0 && XDL_ISSPACE(line2[len2 - 1]); len2--); + } + + if (!(flags & (XDF_IGNORE_WHITESPACE | XDF_IGNORE_WHITESPACE_CHANGE))) + return (len1 == len2 && !memcmp(line1, line2, len1)); + + while (len1 > 0 && len2 > 0) { + len1--; + len2--; + if (XDL_ISSPACE(line1[len1]) || XDL_ISSPACE(line2[len2])) { + if ((flags & XDF_IGNORE_WHITESPACE_CHANGE) && + (!XDL_ISSPACE(line1[len1]) || !XDL_ISSPACE(line2[len2]))) + return 0; + + for (; len1 > 0 && XDL_ISSPACE(line1[len1]); len1--); + for (; len2 > 0 && XDL_ISSPACE(line2[len2]); len2--); + } + if (line1[len1] != line2[len2]) + return 0; + } + + if (flags & XDF_IGNORE_WHITESPACE) { + /* Consume remaining spaces */ + for (; len1 > 0 && XDL_ISSPACE(line1[len1 - 1]); len1--); + for (; len2 > 0 && XDL_ISSPACE(line2[len2 - 1]); len2--); + } + + /* We matched full line1 and line2 */ + if (!len1 && !len2) + return 1; + + return 0; +} + +enum coalesce_direction { MATCH, BASE, NEW }; + +/* Coalesce new lines into base by finding LCS */ +static struct lline *coalesce_lines(struct lline *base, int *lenbase, + struct lline *new, int lennew, + unsigned long parent, long flags) +{ + int **lcs; + enum coalesce_direction **directions; + struct lline *baseend, *newend = NULL; + int i, j, origbaselen = *lenbase; + + if (new == NULL) + return base; + + if (base == NULL) { + *lenbase = lennew; + return new; + } + + /* + * Coalesce new lines into base by finding the LCS + * - Create the table to run dynamic programing + * - Compute the LCS + * - Then reverse read the direction structure: + * - If we have MATCH, assign parent to base flag, and consume + * both baseend and newend + * - Else if we have BASE, consume baseend + * - Else if we have NEW, insert newend lline into base and + * consume newend + */ + lcs = xcalloc(origbaselen + 1, sizeof(int*)); + directions = xcalloc(origbaselen + 1, sizeof(enum coalesce_direction*)); + for (i = 0; i < origbaselen + 1; i++) { + lcs[i] = xcalloc(lennew + 1, sizeof(int)); + directions[i] = xcalloc(lennew + 1, sizeof(enum coalesce_direction)); + directions[i][0] = BASE; + } + for (j = 1; j < lennew + 1; j++) + directions[0][j] = NEW; + + for (i = 1, baseend = base; i < origbaselen + 1; i++) { + for (j = 1, newend = new; j < lennew + 1; j++) { + if (match_string_spaces(baseend->line, baseend->len, + newend->line, newend->len, flags)) { + lcs[i][j] = lcs[i - 1][j - 1] + 1; + directions[i][j] = MATCH; + } else if (lcs[i][j - 1] >= lcs[i - 1][j]) { + lcs[i][j] = lcs[i][j - 1]; + directions[i][j] = NEW; + } else { + lcs[i][j] = lcs[i - 1][j]; + directions[i][j] = BASE; + } + if (newend->next) + newend = newend->next; + } + if (baseend->next) + baseend = baseend->next; + } + + for (i = 0; i < origbaselen + 1; i++) + free(lcs[i]); + free(lcs); + + /* At this point, baseend and newend point to the end of each lists */ + i--; + j--; + while (i != 0 || j != 0) { + if (directions[i][j] == MATCH) { + baseend->parent_map |= 1<<parent; + baseend = baseend->prev; + newend = newend->prev; + i--; + j--; + } else if (directions[i][j] == NEW) { + struct lline *lline; + + lline = newend; + /* Remove lline from new list and update newend */ + if (lline->prev) + lline->prev->next = lline->next; + else + new = lline->next; + if (lline->next) + lline->next->prev = lline->prev; + + newend = lline->prev; + j--; + + /* Add lline to base list */ + if (baseend) { + lline->next = baseend->next; + lline->prev = baseend; + if (lline->prev) + lline->prev->next = lline; + } + else { + lline->next = base; + base = lline; + } + (*lenbase)++; + + if (lline->next) + lline->next->prev = lline; + + } else { + baseend = baseend->prev; + i--; + } + } + + newend = new; + while (newend) { + struct lline *lline = newend; + newend = newend->next; + free(lline); + } + + for (i = 0; i < origbaselen + 1; i++) + free(directions[i]); + free(directions); + + return base; +} + static char *grab_blob(const unsigned char *sha1, unsigned int mode, unsigned long *size, struct userdiff_driver *textconv, const char *path) @@ -129,29 +304,19 @@ static void append_lost(struct sline *sline, int n, const char *line, int len) if (line[len-1] == '\n') len--; - /* Check to see if we can squash things */ - if (sline->lost_head) { - lline = sline->next_lost; - while (lline) { - if (lline->len == len && - !memcmp(lline->line, line, len)) { - lline->parent_map |= this_mask; - sline->next_lost = lline->next; - return; - } - lline = lline->next; - } - } - lline = xmalloc(sizeof(*lline) + len + 1); lline->len = len; lline->next = NULL; + lline->prev = sline->plost.lost_tail; + if (lline->prev) + lline->prev->next = lline; + else + sline->plost.lost_head = lline; + sline->plost.lost_tail = lline; + sline->plost.len++; lline->parent_map = this_mask; memcpy(lline->line, line, len); lline->line[len] = 0; - *sline->lost_tail = lline; - sline->lost_tail = &lline->next; - sline->next_lost = NULL; } struct combine_diff_state { @@ -194,7 +359,6 @@ static void consume_line(void *state_, char *line, unsigned long len) xcalloc(state->num_parent, sizeof(unsigned long)); state->sline[state->nb-1].p_lno[state->n] = state->ob; - state->lost_bucket->next_lost = state->lost_bucket->lost_head; return; } if (!state->lost_bucket) @@ -215,7 +379,7 @@ static void combine_diff(const unsigned char *parent, unsigned int mode, struct sline *sline, unsigned int cnt, int n, int num_parent, int result_deleted, struct userdiff_driver *textconv, - const char *path) + const char *path, long flags) { unsigned int p_lno, lno; unsigned long nmask = (1UL << n); @@ -231,7 +395,7 @@ static void combine_diff(const unsigned char *parent, unsigned int mode, parent_file.ptr = grab_blob(parent, mode, &sz, textconv, path); parent_file.size = sz; memset(&xpp, 0, sizeof(xpp)); - xpp.flags = 0; + xpp.flags = flags; memset(&xecfg, 0, sizeof(xecfg)); memset(&state, 0, sizeof(state)); state.nmask = nmask; @@ -255,8 +419,18 @@ static void combine_diff(const unsigned char *parent, unsigned int mode, struct lline *ll; sline[lno].p_lno[n] = p_lno; + /* Coalesce new lines */ + if (sline[lno].plost.lost_head) { + struct sline *sl = &sline[lno]; + sl->lost = coalesce_lines(sl->lost, &sl->lenlost, + sl->plost.lost_head, + sl->plost.len, n, flags); + sl->plost.lost_head = sl->plost.lost_tail = NULL; + sl->plost.len = 0; + } + /* How many lines would this sline advance the p_lno? */ - ll = sline[lno].lost_head; + ll = sline[lno].lost; while (ll) { if (ll->parent_map & nmask) p_lno++; /* '-' means parent had it */ @@ -276,7 +450,7 @@ static int interesting(struct sline *sline, unsigned long all_mask) /* If some parents lost lines here, or if we have added to * some parent, it is interesting. */ - return ((sline->flag & all_mask) || sline->lost_head); + return ((sline->flag & all_mask) || sline->lost); } static unsigned long adjust_hunk_tail(struct sline *sline, @@ -459,7 +633,7 @@ static int make_hunks(struct sline *sline, unsigned long cnt, has_interesting = 0; for (j = i; j < hunk_end && !has_interesting; j++) { unsigned long this_diff = sline[j].flag & all_mask; - struct lline *ll = sline[j].lost_head; + struct lline *ll = sline[j].lost; if (this_diff) { /* This has some changes. Is it the * same as others? @@ -613,7 +787,7 @@ static void dump_sline(struct sline *sline, const char *line_prefix, int j; unsigned long p_mask; struct sline *sl = &sline[lno++]; - ll = (sl->flag & no_pre_delete) ? NULL : sl->lost_head; + ll = (sl->flag & no_pre_delete) ? NULL : sl->lost; while (ll) { printf("%s%s", line_prefix, c_old); for (j = 0; j < num_parent; j++) { @@ -664,7 +838,7 @@ static void reuse_combine_diff(struct sline *sline, unsigned long cnt, jmask = (1UL<<j); for (lno = 0; lno <= cnt; lno++) { - struct lline *ll = sline->lost_head; + struct lline *ll = sline->lost; sline->p_lno[i] = sline->p_lno[j]; while (ll) { if (ll->parent_map & jmask) @@ -923,10 +1097,6 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, sline = xcalloc(cnt+2, sizeof(*sline)); sline[0].bol = result; - for (lno = 0; lno <= cnt + 1; lno++) { - sline[lno].lost_tail = &sline[lno].lost_head; - sline[lno].flag = 0; - } for (lno = 0, cp = result; cp < result + result_size; cp++) { if (*cp == '\n') { sline[lno].len = cp - sline[lno].bol; @@ -962,7 +1132,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, elem->parent[i].mode, &result_file, sline, cnt, i, num_parent, result_deleted, - textconv, elem->path); + textconv, elem->path, opt->xdl_opts); } show_hunks = make_hunks(sline, cnt, num_parent, dense); @@ -976,8 +1146,8 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, free(result); for (lno = 0; lno < cnt; lno++) { - if (sline[lno].lost_head) { - struct lline *ll = sline[lno].lost_head; + if (sline[lno].lost) { + struct lline *ll = sline[lno].lost; while (ll) { struct lline *tmp = ll; ll = ll->next; @@ -463,14 +463,23 @@ static void clear_commit_marks_1(struct commit_list **plist, } } -void clear_commit_marks(struct commit *commit, unsigned int mark) +void clear_commit_marks_many(int nr, struct commit **commit, unsigned int mark) { struct commit_list *list = NULL; - commit_list_insert(commit, &list); + + while (nr--) { + commit_list_insert(*commit, &list); + commit++; + } while (list) clear_commit_marks_1(&list, pop_commit(&list), mark); } +void clear_commit_marks(struct commit *commit, unsigned int mark) +{ + clear_commit_marks_many(1, &commit, mark); +} + void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark) { struct object *object; @@ -797,8 +806,7 @@ struct commit_list *get_merge_bases_many(struct commit *one, if (!result || !result->next) { if (cleanup) { clear_commit_marks(one, all_flags); - for (i = 0; i < n; i++) - clear_commit_marks(twos[i], all_flags); + clear_commit_marks_many(n, twos, all_flags); } return result; } @@ -816,8 +824,7 @@ struct commit_list *get_merge_bases_many(struct commit *one, free_commit_list(result); clear_commit_marks(one, all_flags); - for (i = 0; i < n; i++) - clear_commit_marks(twos[i], all_flags); + clear_commit_marks_many(n, twos, all_flags); cnt = remove_redundant(rslt, cnt); result = NULL; @@ -834,7 +841,7 @@ struct commit_list *get_merge_bases(struct commit *one, struct commit *two, } /* - * Is "commit" a decendant of one of the elements on the "with_commit" list? + * Is "commit" a descendant of one of the elements on the "with_commit" list? */ int is_descendant_of(struct commit *commit, struct commit_list *with_commit) { @@ -852,25 +859,36 @@ int is_descendant_of(struct commit *commit, struct commit_list *with_commit) } /* - * Is "commit" an ancestor of (i.e. reachable from) the "reference"? + * Is "commit" an ancestor of one of the "references"? */ -int in_merge_bases(struct commit *commit, struct commit *reference) +int in_merge_bases_many(struct commit *commit, int nr_reference, struct commit **reference) { struct commit_list *bases; - int ret = 0; + int ret = 0, i; - if (parse_commit(commit) || parse_commit(reference)) + if (parse_commit(commit)) return ret; + for (i = 0; i < nr_reference; i++) + if (parse_commit(reference[i])) + return ret; - bases = paint_down_to_common(commit, 1, &reference); + bases = paint_down_to_common(commit, nr_reference, reference); if (commit->object.flags & PARENT2) ret = 1; clear_commit_marks(commit, all_flags); - clear_commit_marks(reference, all_flags); + clear_commit_marks_many(nr_reference, reference, all_flags); free_commit_list(bases); return ret; } +/* + * Is "commit" an ancestor of (i.e. reachable from) the "reference"? + */ +int in_merge_bases(struct commit *commit, struct commit *reference) +{ + return in_merge_bases_many(commit, 1, &reference); +} + struct commit_list *reduce_heads(struct commit_list *heads) { struct commit_list *p; @@ -1023,6 +1041,76 @@ free_return: free(buf); } +static struct { + char result; + const char *check; +} sigcheck_gpg_status[] = { + { 'G', "\n[GNUPG:] GOODSIG " }, + { 'B', "\n[GNUPG:] BADSIG " }, + { 'U', "\n[GNUPG:] TRUST_NEVER" }, + { 'U', "\n[GNUPG:] TRUST_UNDEFINED" }, +}; + +static void parse_gpg_output(struct signature_check *sigc) +{ + const char *buf = sigc->gpg_status; + int i; + + /* Iterate over all search strings */ + for (i = 0; i < ARRAY_SIZE(sigcheck_gpg_status); i++) { + const char *found, *next; + + if (!prefixcmp(buf, sigcheck_gpg_status[i].check + 1)) { + /* At the very beginning of the buffer */ + found = buf + strlen(sigcheck_gpg_status[i].check + 1); + } else { + found = strstr(buf, sigcheck_gpg_status[i].check); + if (!found) + continue; + found += strlen(sigcheck_gpg_status[i].check); + } + sigc->result = sigcheck_gpg_status[i].result; + /* The trust messages are not followed by key/signer information */ + if (sigc->result != 'U') { + sigc->key = xmemdupz(found, 16); + found += 17; + next = strchrnul(found, '\n'); + sigc->signer = xmemdupz(found, next - found); + } + } +} + +void check_commit_signature(const struct commit* commit, struct signature_check *sigc) +{ + struct strbuf payload = STRBUF_INIT; + struct strbuf signature = STRBUF_INIT; + struct strbuf gpg_output = STRBUF_INIT; + struct strbuf gpg_status = STRBUF_INIT; + int status; + + sigc->result = 'N'; + + if (parse_signed_commit(commit->object.sha1, + &payload, &signature) <= 0) + goto out; + status = verify_signed_buffer(payload.buf, payload.len, + signature.buf, signature.len, + &gpg_output, &gpg_status); + if (status && !gpg_output.len) + goto out; + sigc->gpg_output = strbuf_detach(&gpg_output, NULL); + sigc->gpg_status = strbuf_detach(&gpg_status, NULL); + parse_gpg_output(sigc); + + out: + strbuf_release(&gpg_status); + strbuf_release(&gpg_output); + strbuf_release(&payload); + strbuf_release(&signature); +} + + + void append_merge_tag_headers(struct commit_list *parents, struct commit_extra_header ***tail) { @@ -5,6 +5,7 @@ #include "tree.h" #include "strbuf.h" #include "decorate.h" +#include "gpg-interface.h" struct commit_list { struct commit *item; @@ -137,6 +138,7 @@ struct commit *pop_most_recent_commit(struct commit_list **list, struct commit *pop_commit(struct commit_list **stack); void clear_commit_marks(struct commit *commit, unsigned int mark); +void clear_commit_marks_many(int nr, struct commit **commit, unsigned int mark); void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark); /* @@ -164,7 +166,7 @@ extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *r extern struct commit_list *get_merge_bases_many(struct commit *one, int n, struct commit **twos, int cleanup); extern struct commit_list *get_octopus_merge_bases(struct commit_list *in); -/* largest postive number a signed 32-bit integer can contain */ +/* largest positive number a signed 32-bit integer can contain */ #define INFINITE_DEPTH 0x7fffffff extern int register_shallow(const unsigned char *sha1); @@ -176,6 +178,7 @@ extern struct commit_list *get_shallow_commits(struct object_array *heads, int is_descendant_of(struct commit *, struct commit_list *); int in_merge_bases(struct commit *, struct commit *); +int in_merge_bases_many(struct commit *, int, struct commit **); extern int interactive_add(int argc, const char **argv, const char *prefix, int patch); extern int run_add_interactive(const char *revision, const char *patch_mode, @@ -230,4 +233,13 @@ extern void print_commit_list(struct commit_list *list, const char *format_cur, const char *format_last); +/* + * Check the signature of the given commit. The result of the check is stored + * in sig->check_result, 'G' for a good signature, 'U' for a good signature + * from an untrusted signer, 'B' for a bad signature and 'N' for no signature + * at all. This may allocate memory for sig->gpg_output, sig->gpg_status, + * sig->signer and sig->key. + */ +extern void check_commit_signature(const struct commit* commit, struct signature_check *sigc); + #endif /* COMMIT_H */ diff --git a/compat/cygwin.c b/compat/cygwin.c index 5428858875..871b41d23a 100644 --- a/compat/cygwin.c +++ b/compat/cygwin.c @@ -1,3 +1,4 @@ +#define CYGWIN_C #define WIN32_LEAN_AND_MEAN #ifdef CYGWIN_V15_WIN32API #include "../git-compat-util.h" @@ -10,6 +11,18 @@ #endif #include "../cache.h" /* to read configuration */ +/* + * Return POSIX permission bits, regardless of core.ignorecygwinfstricks + */ +int cygwin_get_st_mode_bits(const char *path, int *mode) +{ + struct stat st; + if (lstat(path, &st) < 0) + return -1; + *mode = st.st_mode; + return 0; +} + static inline void filetime_to_timespec(const FILETIME *ft, struct timespec *ts) { long long winTime = ((long long)ft->dwHighDateTime << 32) + diff --git a/compat/cygwin.h b/compat/cygwin.h index a3229f5b4f..c04965a2e0 100644 --- a/compat/cygwin.h +++ b/compat/cygwin.h @@ -4,6 +4,11 @@ typedef int (*stat_fn_t)(const char*, struct stat*); extern stat_fn_t cygwin_stat_fn; extern stat_fn_t cygwin_lstat_fn; +int cygwin_get_st_mode_bits(const char *path, int *mode); +#define get_st_mode_bits(p,m) cygwin_get_st_mode_bits((p),(m)) +#ifndef CYGWIN_C +/* cygwin.c needs the original lstat() */ #define stat(path, buf) (*cygwin_stat_fn)(path, buf) #define lstat(path, buf) (*cygwin_lstat_fn)(path, buf) +#endif diff --git a/compat/msvc.h b/compat/msvc.h index aa4b56315a..96b6d605da 100644 --- a/compat/msvc.h +++ b/compat/msvc.h @@ -12,6 +12,8 @@ #define __attribute__(x) #define strncasecmp _strnicmp #define ftruncate _chsize +#define strtoull _strtoui64 +#define strtoll _strtoi64 static __inline int strcasecmp (const char *s1, const char *s2) { diff --git a/compat/nedmalloc/malloc.c.h b/compat/nedmalloc/malloc.c.h index ff7c2c4fd8..1401a67274 100644 --- a/compat/nedmalloc/malloc.c.h +++ b/compat/nedmalloc/malloc.c.h @@ -4778,7 +4778,7 @@ void* dlmalloc(size_t bytes) { void dlfree(void* mem) { /* - Consolidate freed chunks with preceeding or succeeding bordering + Consolidate freed chunks with preceding or succeeding bordering free chunks, if they exist, and then place in a bin. Intermixed with special cases for top, dv, mmapped chunks, and usage errors. */ @@ -5680,10 +5680,10 @@ History: Wolfram Gloger (Gloger@lrz.uni-muenchen.de). * Use last_remainder in more cases. * Pack bins using idea from colin@nyx10.cs.du.edu - * Use ordered bins instead of best-fit threshhold + * Use ordered bins instead of best-fit threshold * Eliminate block-local decls to simplify tracing and debugging. * Support another case of realloc via move into top - * Fix error occuring when initial sbrk_base not word-aligned. + * Fix error occurring when initial sbrk_base not word-aligned. * Rely on page size for units instead of SBRK_UNIT to avoid surprises about sbrk alignment conventions. * Add mallinfo, mallopt. Thanks to Raymond Nijssen diff --git a/compat/obstack.h b/compat/obstack.h index d178bd6716..ceb4bdbcdd 100644 --- a/compat/obstack.h +++ b/compat/obstack.h @@ -128,7 +128,7 @@ extern "C" { #define __BPTR_ALIGN(B, P, A) ((B) + (((P) - (B) + (A)) & ~(A))) -/* Similiar to _BPTR_ALIGN (B, P, A), except optimize the common case +/* Similar to _BPTR_ALIGN (B, P, A), except optimize the common case where pointers can be converted to integers, aligned as integers, and converted back again. If PTR_INT_TYPE is narrower than a pointer (e.g., the AS/400), play it safe and compute the alignment diff --git a/compat/precompose_utf8.c b/compat/precompose_utf8.c index 8cf59558e1..030174db51 100644 --- a/compat/precompose_utf8.c +++ b/compat/precompose_utf8.c @@ -134,7 +134,7 @@ struct dirent_prec_psx *precompose_utf8_readdir(PREC_DIR *prec_dir) if (prec_dir->ic_precompose == (iconv_t)-1) { die("iconv_open(%s,%s) failed, but needed:\n" " precomposed unicode is not supported.\n" - " If you wnat to use decomposed unicode, run\n" + " If you want to use decomposed unicode, run\n" " \"git config core.precomposeunicode false\"\n", repo_encoding, path_encoding); } else { diff --git a/compat/regex/regcomp.c b/compat/regex/regcomp.c index 8c96ed942c..d0025bd58d 100644 --- a/compat/regex/regcomp.c +++ b/compat/regex/regcomp.c @@ -2095,7 +2095,7 @@ peek_token_bracket (re_token_t *token, re_string_t *input, reg_syntax_t syntax) /* Entry point of the parser. Parse the regular expression REGEXP and return the structure tree. - If an error is occured, ERR is set by error code, and return NULL. + If an error has occurred, ERR is set by error code, and return NULL. This function build the following tree, from regular expression <reg_exp>: CAT / \ @@ -3715,7 +3715,7 @@ build_charclass_op (re_dfa_t *dfa, RE_TRANSLATE_TYPE trans, /* This is intended for the expressions like "a{1,3}". Fetch a number from `input', and return the number. Return -1, if the number field is empty like "{,1}". - Return -2, If an error is occured. */ + Return -2, if an error has occurred. */ static int fetch_number (re_string_t *input, re_token_t *token, reg_syntax_t syntax) diff --git a/compat/regex/regex.c b/compat/regex/regex.c index 3dd8dfa01f..6aaae00327 100644 --- a/compat/regex/regex.c +++ b/compat/regex/regex.c @@ -22,7 +22,7 @@ #include "config.h" #endif -/* Make sure noone compiles this code with a C++ compiler. */ +/* Make sure no one compiles this code with a C++ compiler. */ #ifdef __cplusplus # error "This is C code, use a C compiler" #endif diff --git a/compat/regex/regex_internal.c b/compat/regex/regex_internal.c index 193854cf5b..d4121f2f4f 100644 --- a/compat/regex/regex_internal.c +++ b/compat/regex/regex_internal.c @@ -1284,7 +1284,7 @@ re_node_set_merge (re_node_set *dest, const re_node_set *src) /* Insert the new element ELEM to the re_node_set* SET. SET should not already have ELEM. - return -1 if an error is occured, return 1 otherwise. */ + return -1 if an error has occurred, return 1 otherwise. */ static int internal_function @@ -1341,7 +1341,7 @@ re_node_set_insert (re_node_set *set, int elem) /* Insert the new element ELEM to the re_node_set* SET. SET should not already have any element greater than or equal to ELEM. - Return -1 if an error is occured, return 1 otherwise. */ + Return -1 if an error has occurred, return 1 otherwise. */ static int internal_function @@ -1416,7 +1416,7 @@ re_node_set_remove_at (re_node_set *set, int idx) /* Add the token TOKEN to dfa->nodes, and return the index of the token. - Or return -1, if an error will be occured. */ + Or return -1, if an error has occurred. */ static int internal_function diff --git a/compat/vcbuild/include/sys/poll.h b/compat/vcbuild/include/sys/poll.h deleted file mode 100644 index 0d8552a2c6..0000000000 --- a/compat/vcbuild/include/sys/poll.h +++ /dev/null @@ -1 +0,0 @@ -/* Intentionally empty file to support building git with MSVC */ diff --git a/compat/vcbuild/include/unistd.h b/compat/vcbuild/include/unistd.h index b14fcf94da..c65c2cd566 100644 --- a/compat/vcbuild/include/unistd.h +++ b/compat/vcbuild/include/unistd.h @@ -49,6 +49,9 @@ typedef int64_t off64_t; #define INTMAX_MAX _I64_MAX #define UINTMAX_MAX _UI64_MAX +#define UINT32_MAX 0xffffffff /* 4294967295U */ + +#define STDIN_FILENO 0 #define STDOUT_FILENO 1 #define STDERR_FILENO 2 diff --git a/config.mak.in b/config.mak.in index fa02bdd82a..e6a6d0f941 100644 --- a/config.mak.in +++ b/config.mak.in @@ -22,9 +22,3 @@ docdir = @docdir@ mandir = @mandir@ htmldir = @htmldir@ - -srcdir = @srcdir@ -VPATH = @srcdir@ - -export exec_prefix mandir -export srcdir VPATH diff --git a/config.mak.uname b/config.mak.uname index e09af8fc13..d78fd3df5b 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -326,7 +326,6 @@ ifeq ($(uname_S),Windows) # NEEDS_LIBICONV = YesPlease NO_ICONV = YesPlease NO_STRTOUMAX = YesPlease - NO_STRTOULL = YesPlease NO_MKDTEMP = YesPlease NO_MKSTEMPS = YesPlease SNPRINTF_RETURNS_BOGUS = YesPlease @@ -343,6 +342,9 @@ ifeq ($(uname_S),Windows) NO_CURL = YesPlease NO_PYTHON = YesPlease BLK_SHA1 = YesPlease + ETAGS_TARGET = ETAGS + NO_INET_PTON = YesPlease + NO_INET_NTOP = YesPlease NO_POSIX_GOODIES = UnfortunatelyYes NATIVE_CRLF = YesPlease DEFAULT_HELP_FORMAT = html @@ -505,6 +507,7 @@ ifneq (,$(findstring MINGW,$(uname_S))) compat/win32/dirent.o EXTLIBS += -lws2_32 PTHREAD_LIBS = + NATIVE_CRLF = YesPlease X = .exe SPARSE_FLAGS = -Wno-one-bit-signed-bitfield ifneq (,$(wildcard ../THIS_IS_MSYSGIT)) @@ -62,8 +62,8 @@ static void die_initial_contact(int got_at_least_one_head) /* * Read all the refs from the other end */ -struct ref **get_remote_heads(int in, struct ref **list, - unsigned int flags, +struct ref **get_remote_heads(int in, char *src_buf, size_t src_len, + struct ref **list, unsigned int flags, struct extra_have_objects *extra_have) { int got_at_least_one_head = 0; @@ -72,18 +72,19 @@ struct ref **get_remote_heads(int in, struct ref **list, for (;;) { struct ref *ref; unsigned char old_sha1[20]; - static char buffer[1000]; char *name; int len, name_len; + char *buffer = packet_buffer; - len = packet_read(in, buffer, sizeof(buffer)); + len = packet_read(in, &src_buf, &src_len, + packet_buffer, sizeof(packet_buffer), + PACKET_READ_GENTLE_ON_EOF | + PACKET_READ_CHOMP_NEWLINE); if (len < 0) die_initial_contact(got_at_least_one_head); if (!len) break; - if (buffer[len-1] == '\n') - buffer[--len] = 0; if (len > 4 && !prefixcmp(buffer, "ERR ")) die("remote error: %s", buffer + 4); diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 93eba46750..6df62c2173 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -53,19 +53,6 @@ __gitdir () fi } -__gitcomp_1 () -{ - local c IFS=$' \t\n' - for c in $1; do - c="$c$2" - case $c in - --*=*|*.) ;; - *) c="$c " ;; - esac - printf '%s\n' "$c" - done -} - # The following function is based on code from: # # bash_completion - programmable completion functions for bash 3.2+ @@ -195,8 +182,18 @@ _get_comp_words_by_ref () } fi -# Generates completion reply with compgen, appending a space to possible -# completion words, if necessary. +__gitcompadd () +{ + local i=0 + for x in $1; do + if [[ "$x" == "$3"* ]]; then + COMPREPLY[i++]="$2$x$4" + fi + done +} + +# Generates completion reply, appending a space to possible completion words, +# if necessary. # It accepts 1 to 4 arguments: # 1: List of possible completion words. # 2: A prefix to be added to each possible completion word (optional). @@ -208,19 +205,25 @@ __gitcomp () case "$cur_" in --*=) - COMPREPLY=() ;; *) - local IFS=$'\n' - COMPREPLY=($(compgen -P "${2-}" \ - -W "$(__gitcomp_1 "${1-}" "${4-}")" \ - -- "$cur_")) + local c i=0 IFS=$' \t\n' + for c in $1; do + c="$c${4-}" + if [[ $c == "$cur_"* ]]; then + case $c in + --*=*|*.) ;; + *) c="$c " ;; + esac + COMPREPLY[i++]="${2-}$c" + fi + done ;; esac } -# Generates completion reply with compgen from newline-separated possible -# completion words by appending a space to all of them. +# Generates completion reply from newline-separated possible completion words +# by appending a space to all of them. # It accepts 1 to 4 arguments: # 1: List of possible completion words, separated by a single newline. # 2: A prefix to be added to each possible completion word (optional). @@ -231,7 +234,7 @@ __gitcomp () __gitcomp_nl () { local IFS=$'\n' - COMPREPLY=($(compgen -P "${2-}" -S "${4- }" -W "$1" -- "${3-$cur}")) + __gitcompadd "$1" "${2-}" "${3-$cur}" "${4- }" } # Generates completion reply with compgen from newline-separated possible @@ -614,7 +617,6 @@ __git_complete_remote_or_refspec () case "$cmd" in push) no_complete_refspec=1 ;; fetch) - COMPREPLY=() return ;; *) ;; @@ -630,7 +632,6 @@ __git_complete_remote_or_refspec () return fi if [ $no_complete_refspec = 1 ]; then - COMPREPLY=() return fi [ "$remote" = "." ] && remote= @@ -951,7 +952,6 @@ _git_am () " return esac - COMPREPLY=() } _git_apply () @@ -971,7 +971,6 @@ _git_apply () " return esac - COMPREPLY=() } _git_add () @@ -1031,7 +1030,6 @@ _git_bisect () __gitcomp_nl "$(__git_refs)" ;; *) - COMPREPLY=() ;; esac } @@ -1124,9 +1122,14 @@ _git_cherry () _git_cherry_pick () { + local dir="$(__gitdir)" + if [ -f "$dir"/CHERRY_PICK_HEAD ]; then + __gitcomp "--continue --quit --abort" + return + fi case "$cur" in --*) - __gitcomp "--edit --no-commit" + __gitcomp "--edit --no-commit --signoff --strategy= --mainline" ;; *) __gitcomp_nl "$(__git_refs)" @@ -1170,7 +1173,6 @@ _git_clone () return ;; esac - COMPREPLY=() } _git_commit () @@ -1354,7 +1356,6 @@ _git_fsck () return ;; esac - COMPREPLY=() } _git_gc () @@ -1365,7 +1366,6 @@ _git_gc () return ;; esac - COMPREPLY=() } _git_gitk () @@ -1442,7 +1442,6 @@ _git_init () return ;; esac - COMPREPLY=() } _git_ls_files () @@ -1578,7 +1577,6 @@ _git_mergetool () return ;; esac - COMPREPLY=() } _git_merge_base () @@ -1831,7 +1829,7 @@ _git_config () local remote="${prev#remote.}" remote="${remote%.fetch}" if [ -z "$cur" ]; then - COMPREPLY=("refs/heads/") + __gitcompadd "refs/heads/" "" "" "" return fi __gitcomp_nl "$(__git_refs_remotes "$remote")" @@ -1891,7 +1889,6 @@ _git_config () return ;; *.*) - COMPREPLY=() return ;; esac @@ -2272,7 +2269,6 @@ _git_remote () __gitcomp "$c" ;; *) - COMPREPLY=() ;; esac } @@ -2388,8 +2384,6 @@ _git_stash () *) if [ -z "$(__git_find_on_cmdline "$save_opts")" ]; then __gitcomp "$subcommands" - else - COMPREPLY=() fi ;; esac @@ -2402,14 +2396,12 @@ _git_stash () __gitcomp "--index --quiet" ;; show,--*|drop,--*|branch,--*) - COMPREPLY=() ;; show,*|apply,*|drop,*|pop,*|branch,*) __gitcomp_nl "$(git --git-dir="$(__gitdir)" stash list \ | sed -n -e 's/:.*//p')" ;; *) - COMPREPLY=() ;; esac fi @@ -2419,7 +2411,7 @@ _git_submodule () { __git_has_doubledash && return - local subcommands="add status init update summary foreach sync" + local subcommands="add status init deinit update summary foreach sync" if [ -z "$(__git_find_on_cmdline "$subcommands")" ]; then case "$cur" in --*) @@ -2526,7 +2518,6 @@ _git_svn () __gitcomp "--revision= --parent" ;; *) - COMPREPLY=() ;; esac fi @@ -2551,13 +2542,10 @@ _git_tag () case "$prev" in -m|-F) - COMPREPLY=() ;; -*|tag) if [ $f = 1 ]; then __gitcomp_nl "$(__git_tags)" - else - COMPREPLY=() fi ;; *) diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh index 341422a766..756a951459 100644 --- a/contrib/completion/git-prompt.sh +++ b/contrib/completion/git-prompt.sh @@ -282,6 +282,8 @@ __git_ps1 () r="|MERGING" elif [ -f "$g/CHERRY_PICK_HEAD" ]; then r="|CHERRY-PICKING" + elif [ -f "$g/REVERT_HEAD" ]; then + r="|REVERTING" elif [ -f "$g/BISECT_LOG" ]; then r="|BISECTING" fi diff --git a/contrib/credential/netrc/Makefile b/contrib/credential/netrc/Makefile new file mode 100644 index 0000000000..51b76138a5 --- /dev/null +++ b/contrib/credential/netrc/Makefile @@ -0,0 +1,5 @@ +test: + ./test.pl + +testverbose: + ./test.pl -d -v diff --git a/contrib/credential/netrc/git-credential-netrc b/contrib/credential/netrc/git-credential-netrc new file mode 100755 index 0000000000..6c51c43885 --- /dev/null +++ b/contrib/credential/netrc/git-credential-netrc @@ -0,0 +1,421 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Getopt::Long; +use File::Basename; + +my $VERSION = "0.1"; + +my %options = ( + help => 0, + debug => 0, + verbose => 0, + insecure => 0, + file => [], + + # identical token maps, e.g. host -> host, will be inserted later + tmap => { + port => 'protocol', + machine => 'host', + path => 'path', + login => 'username', + user => 'username', + password => 'password', + } + ); + +# Map each credential protocol token to itself on the netrc side. +foreach (values %{$options{tmap}}) { + $options{tmap}->{$_} = $_; +} + +# Now, $options{tmap} has a mapping from the netrc format to the Git credential +# helper protocol. + +# Next, we build the reverse token map. + +# When $rmap{foo} contains 'bar', that means that what the Git credential helper +# protocol calls 'bar' is found as 'foo' in the netrc/authinfo file. Keys in +# %rmap are what we expect to read from the netrc/authinfo file. + +my %rmap; +foreach my $k (keys %{$options{tmap}}) { + push @{$rmap{$options{tmap}->{$k}}}, $k; +} + +Getopt::Long::Configure("bundling"); + +# TODO: maybe allow the token map $options{tmap} to be configurable. +GetOptions(\%options, + "help|h", + "debug|d", + "insecure|k", + "verbose|v", + "file|f=s@", + ); + +if ($options{help}) { + my $shortname = basename($0); + $shortname =~ s/git-credential-//; + + print <<EOHIPPUS; + +$0 [-f AUTHFILE1] [-f AUTHFILEN] [-d] [-v] [-k] get + +Version $VERSION by tzz\@lifelogs.com. License: BSD. + +Options: + + -f|--file AUTHFILE : specify netrc-style files. Files with the .gpg extension + will be decrypted by GPG before parsing. Multiple -f + arguments are OK. They are processed in order, and the + first matching entry found is returned via the credential + helper protocol (see below). + + When no -f option is given, .authinfo.gpg, .netrc.gpg, + .authinfo, and .netrc files in your home directory are used + in this order. + + -k|--insecure : ignore bad file ownership or permissions + + -d|--debug : turn on debugging (developer info) + + -v|--verbose : be more verbose (show files and information found) + +To enable this credential helper: + + git config credential.helper '$shortname -f AUTHFILE1 -f AUTHFILE2' + +(Note that Git will prepend "git-credential-" to the helper name and look for it +in the path.) + +...and if you want lots of debugging info: + + git config credential.helper '$shortname -f AUTHFILE -d' + +...or to see the files opened and data found: + + git config credential.helper '$shortname -f AUTHFILE -v' + +Only "get" mode is supported by this credential helper. It opens every AUTHFILE +and looks for the first entry that matches the requested search criteria: + + 'port|protocol': + The protocol that will be used (e.g., https). (protocol=X) + + 'machine|host': + The remote hostname for a network credential. (host=X) + + 'path': + The path with which the credential will be used. (path=X) + + 'login|user|username': + The credential’s username, if we already have one. (username=X) + +Thus, when we get this query on STDIN: + +host=github.com +protocol=https +username=tzz + +this credential helper will look for the first entry in every AUTHFILE that +matches + +machine github.com port https login tzz + +OR + +machine github.com protocol https login tzz + +OR... etc. acceptable tokens as listed above. Any unknown tokens are +simply ignored. + +Then, the helper will print out whatever tokens it got from the entry, including +"password" tokens, mapping back to Git's helper protocol; e.g. "port" is mapped +back to "protocol". Any redundant entry tokens (part of the original query) are +skipped. + +Again, note that only the first matching entry from all the AUTHFILEs, processed +in the sequence given on the command line, is used. + +Netrc/authinfo tokens can be quoted as 'STRING' or "STRING". + +No caching is performed by this credential helper. + +EOHIPPUS + + exit 0; +} + +my $mode = shift @ARGV; + +# Credentials must get a parameter, so die if it's missing. +die "Syntax: $0 [-f AUTHFILE1] [-f AUTHFILEN] [-d] get" unless defined $mode; + +# Only support 'get' mode; with any other unsupported ones we just exit. +exit 0 unless $mode eq 'get'; + +my $files = $options{file}; + +# if no files were given, use a predefined list. +# note that .gpg files come first +unless (scalar @$files) { + my @candidates = qw[ + ~/.authinfo.gpg + ~/.netrc.gpg + ~/.authinfo + ~/.netrc + ]; + + $files = $options{file} = [ map { glob $_ } @candidates ]; +} + +my $query = read_credential_data_from_stdin(); + +FILE: +foreach my $file (@$files) { + my $gpgmode = $file =~ m/\.gpg$/; + unless (-r $file) { + log_verbose("Unable to read $file; skipping it"); + next FILE; + } + + # the following check is copied from Net::Netrc, for non-GPG files + # OS/2 and Win32 do not handle stat in a way compatible with this check :-( + unless ($gpgmode || $options{insecure} || + $^O eq 'os2' + || $^O eq 'MSWin32' + || $^O eq 'MacOS' + || $^O =~ /^cygwin/) { + my @stat = stat($file); + + if (@stat) { + if ($stat[2] & 077) { + log_verbose("Insecure $file (mode=%04o); skipping it", + $stat[2] & 07777); + next FILE; + } + + if ($stat[4] != $<) { + log_verbose("Not owner of $file; skipping it"); + next FILE; + } + } + } + + my @entries = load_netrc($file, $gpgmode); + + unless (scalar @entries) { + if ($!) { + log_verbose("Unable to open $file: $!"); + } else { + log_verbose("No netrc entries found in $file"); + } + + next FILE; + } + + my $entry = find_netrc_entry($query, @entries); + if ($entry) { + print_credential_data($entry, $query); + # we're done! + last FILE; + } +} + +exit 0; + +sub load_netrc { + my $file = shift @_; + my $gpgmode = shift @_; + + my $io; + if ($gpgmode) { + my @cmd = (qw(gpg --decrypt), $file); + log_verbose("Using GPG to open $file: [@cmd]"); + open $io, "-|", @cmd; + } else { + log_verbose("Opening $file..."); + open $io, '<', $file; + } + + # nothing to do if the open failed (we log the error later) + return unless $io; + + # Net::Netrc does this, but the functionality is merged with the file + # detection logic, so we have to extract just the part we need + my @netrc_entries = net_netrc_loader($io); + + # these entries will use the credential helper protocol token names + my @entries; + + foreach my $nentry (@netrc_entries) { + my %entry; + my $num_port; + + if (!defined $nentry->{machine}) { + next; + } + if (defined $nentry->{port} && $nentry->{port} =~ m/^\d+$/) { + $num_port = $nentry->{port}; + delete $nentry->{port}; + } + + # create the new entry for the credential helper protocol + $entry{$options{tmap}->{$_}} = $nentry->{$_} foreach keys %$nentry; + + # for "host X port Y" where Y is an integer (captured by + # $num_port above), set the host to "X:Y" + if (defined $entry{host} && defined $num_port) { + $entry{host} = join(':', $entry{host}, $num_port); + } + + push @entries, \%entry; + } + + return @entries; +} + +sub net_netrc_loader { + my $fh = shift @_; + my @entries; + my ($mach, $macdef, $tok, @tok); + + LINE: + while (<$fh>) { + undef $macdef if /\A\n\Z/; + + if ($macdef) { + next LINE; + } + + s/^\s*//; + chomp; + + while (length && s/^("((?:[^"]+|\\.)*)"|((?:[^\\\s]+|\\.)*))\s*//) { + (my $tok = $+) =~ s/\\(.)/$1/g; + push(@tok, $tok); + } + + TOKEN: + while (@tok) { + if ($tok[0] eq "default") { + shift(@tok); + $mach = { machine => undef }; + next TOKEN; + } + + $tok = shift(@tok); + + if ($tok eq "machine") { + my $host = shift @tok; + $mach = { machine => $host }; + push @entries, $mach; + } elsif (exists $options{tmap}->{$tok}) { + unless ($mach) { + log_debug("Skipping token $tok because no machine was given"); + next TOKEN; + } + + my $value = shift @tok; + unless (defined $value) { + log_debug("Token $tok had no value, skipping it."); + next TOKEN; + } + + # Following line added by rmerrell to remove '/' escape char in .netrc + $value =~ s/\/\\/\\/g; + $mach->{$tok} = $value; + } elsif ($tok eq "macdef") { # we ignore macros + next TOKEN unless $mach; + my $value = shift @tok; + $macdef = 1; + } + } + } + + return @entries; +} + +sub read_credential_data_from_stdin { + # the query: start with every token with no value + my %q = map { $_ => undef } values(%{$options{tmap}}); + + while (<STDIN>) { + next unless m/^([^=]+)=(.+)/; + + my ($token, $value) = ($1, $2); + die "Unknown search token $token" unless exists $q{$token}; + $q{$token} = $value; + log_debug("We were given search token $token and value $value"); + } + + foreach (sort keys %q) { + log_debug("Searching for %s = %s", $_, $q{$_} || '(any value)'); + } + + return \%q; +} + +# takes the search tokens and then a list of entries +# each entry is a hash reference +sub find_netrc_entry { + my $query = shift @_; + + ENTRY: + foreach my $entry (@_) + { + my $entry_text = join ', ', map { "$_=$entry->{$_}" } keys %$entry; + foreach my $check (sort keys %$query) { + if (defined $query->{$check}) { + log_debug("compare %s [%s] to [%s] (entry: %s)", + $check, + $entry->{$check}, + $query->{$check}, + $entry_text); + unless ($query->{$check} eq $entry->{$check}) { + next ENTRY; + } + } else { + log_debug("OK: any value satisfies check $check"); + } + } + + return $entry; + } + + # nothing was found + return; +} + +sub print_credential_data { + my $entry = shift @_; + my $query = shift @_; + + log_debug("entry has passed all the search checks"); + TOKEN: + foreach my $git_token (sort keys %$entry) { + log_debug("looking for useful token $git_token"); + # don't print unknown (to the credential helper protocol) tokens + next TOKEN unless exists $query->{$git_token}; + + # don't print things asked in the query (the entry matches them) + next TOKEN if defined $query->{$git_token}; + + log_debug("FOUND: $git_token=$entry->{$git_token}"); + printf "%s=%s\n", $git_token, $entry->{$git_token}; + } +} +sub log_verbose { + return unless $options{verbose}; + printf STDERR @_; + printf STDERR "\n"; +} + +sub log_debug { + return unless $options{debug}; + printf STDERR @_; + printf STDERR "\n"; +} diff --git a/contrib/credential/netrc/test.netrc b/contrib/credential/netrc/test.netrc new file mode 100644 index 0000000000..ba119a937f --- /dev/null +++ b/contrib/credential/netrc/test.netrc @@ -0,0 +1,13 @@ +machine imap login tzz@lifelogs.com port imaps password letmeknow +machine imap login bob port imaps password bobwillknow + +# comment test + +machine imap2 login tzz port 1099 password tzzknow +machine imap2 login bob password bobwillknow + +# another command + +machine github.com + multilinetoken anothervalue + login carol password carolknows diff --git a/contrib/credential/netrc/test.pl b/contrib/credential/netrc/test.pl new file mode 100755 index 0000000000..169b6463c3 --- /dev/null +++ b/contrib/credential/netrc/test.pl @@ -0,0 +1,106 @@ +#!/usr/bin/perl + +use warnings; +use strict; +use Test; +use IPC::Open2; + +BEGIN { plan tests => 15 } + +my @global_credential_args = @ARGV; +my $netrc = './test.netrc'; +print "# Testing insecure file, nothing should be found\n"; +chmod 0644, $netrc; +my $cred = run_credential(['-f', $netrc, 'get'], + { host => 'github.com' }); + +ok(scalar keys %$cred, 0, "Got 0 keys from insecure file"); + +print "# Testing missing file, nothing should be found\n"; +chmod 0644, $netrc; +$cred = run_credential(['-f', '///nosuchfile///', 'get'], + { host => 'github.com' }); + +ok(scalar keys %$cred, 0, "Got 0 keys from missing file"); + +chmod 0600, $netrc; + +print "# Testing with invalid data\n"; +$cred = run_credential(['-f', $netrc, 'get'], + "bad data"); +ok(scalar keys %$cred, 4, "Got first found keys with bad data"); + +print "# Testing netrc file for a missing corovamilkbar entry\n"; +$cred = run_credential(['-f', $netrc, 'get'], + { host => 'corovamilkbar' }); + +ok(scalar keys %$cred, 0, "Got no corovamilkbar keys"); + +print "# Testing netrc file for a github.com entry\n"; +$cred = run_credential(['-f', $netrc, 'get'], + { host => 'github.com' }); + +ok(scalar keys %$cred, 2, "Got 2 Github keys"); + +ok($cred->{password}, 'carolknows', "Got correct Github password"); +ok($cred->{username}, 'carol', "Got correct Github username"); + +print "# Testing netrc file for a username-specific entry\n"; +$cred = run_credential(['-f', $netrc, 'get'], + { host => 'imap', username => 'bob' }); + +ok(scalar keys %$cred, 2, "Got 2 username-specific keys"); + +ok($cred->{password}, 'bobwillknow', "Got correct user-specific password"); +ok($cred->{protocol}, 'imaps', "Got correct user-specific protocol"); + +print "# Testing netrc file for a host:port-specific entry\n"; +$cred = run_credential(['-f', $netrc, 'get'], + { host => 'imap2:1099' }); + +ok(scalar keys %$cred, 2, "Got 2 host:port-specific keys"); + +ok($cred->{password}, 'tzzknow', "Got correct host:port-specific password"); +ok($cred->{username}, 'tzz', "Got correct host:port-specific username"); + +print "# Testing netrc file that 'host:port kills host' entry\n"; +$cred = run_credential(['-f', $netrc, 'get'], + { host => 'imap2' }); + +ok(scalar keys %$cred, 2, "Got 2 'host:port kills host' keys"); + +ok($cred->{password}, 'bobwillknow', "Got correct 'host:port kills host' password"); +ok($cred->{username}, 'bob', "Got correct 'host:port kills host' username"); + +sub run_credential +{ + my $args = shift @_; + my $data = shift @_; + my $pid = open2(my $chld_out, my $chld_in, + './git-credential-netrc', @global_credential_args, + @$args); + + die "Couldn't open pipe to netrc credential helper: $!" unless $pid; + + if (ref $data eq 'HASH') + { + print $chld_in "$_=$data->{$_}\n" foreach sort keys %$data; + } + else + { + print $chld_in "$data\n"; + } + + close $chld_in; + my %ret; + + while (<$chld_out>) + { + chomp; + next unless m/^([^=]+)=(.+)/; + + $ret{$1} = $2; + } + + return \%ret; +} diff --git a/contrib/mw-to-git/git-remote-mediawiki.perl b/contrib/mw-to-git/git-remote-mediawiki.perl index 094129de09..9c14c1f88d 100755 --- a/contrib/mw-to-git/git-remote-mediawiki.perl +++ b/contrib/mw-to-git/git-remote-mediawiki.perl @@ -28,7 +28,7 @@ use warnings; use constant SLASH_REPLACEMENT => "%2F"; # It's not always possible to delete pages (may require some -# priviledges). Deleted pages are replaced with this content. +# privileges). Deleted pages are replaced with this content. use constant DELETED_CONTENT => "[[Category:Deleted]]\n"; # It's not possible to create empty pages. New empty files in Git are @@ -841,7 +841,7 @@ sub mw_import_ref { if ($fetch_from == 1 && $n == 0) { print STDERR "You appear to have cloned an empty MediaWiki.\n"; # Something has to be done remote-helper side. If nothing is done, an error is - # thrown saying that HEAD is refering to unknown object 0000000000000000000 + # thrown saying that HEAD is referring to unknown object 0000000000000000000 # and the clone fails. } } @@ -1067,7 +1067,7 @@ sub mw_push_file { my $file_content; if ($page_deleted) { # Deleting a page usually requires - # special priviledges. A common + # special privileges. A common # convention is to replace the page # with this content instead: $file_content = DELETED_CONTENT; diff --git a/contrib/mw-to-git/t/README b/contrib/mw-to-git/t/README index 96e97390cf..03f6ee5d72 100644 --- a/contrib/mw-to-git/t/README +++ b/contrib/mw-to-git/t/README @@ -25,7 +25,7 @@ Principles and Technical Choices The test environment makes it easy to install and manipulate one or several MediaWiki instances. To allow developers to run the testsuite -easily, the environment does not require root priviledge (except to +easily, the environment does not require root privilege (except to install the required packages if needed). It starts a webserver instance on the user's account (using lighttpd greatly helps for that), and does not need a separate database daemon (thanks to the use @@ -81,7 +81,7 @@ parameters, please refer to the `test-gitmw-lib.sh` and ** `test_check_wiki_precond`: Check if the tests must be skipped or not. Please use this function -at the beggining of each new test file. +at the beginning of each new test file. ** `wiki_getpage`: Fetch a given page from the wiki and puts its content in the @@ -113,7 +113,7 @@ Tests if a given page exists on the wiki. ** `wiki_reset`: Reset the wiki, i.e. flush the database. Use this function at the -begining of each new test, except if the test re-uses the same wiki +beginning of each new test, except if the test re-uses the same wiki (and history) as the previous test. How to write a new test diff --git a/contrib/mw-to-git/t/install-wiki/LocalSettings.php b/contrib/mw-to-git/t/install-wiki/LocalSettings.php index 29f125116b..745e47e881 100644 --- a/contrib/mw-to-git/t/install-wiki/LocalSettings.php +++ b/contrib/mw-to-git/t/install-wiki/LocalSettings.php @@ -88,7 +88,7 @@ $wgShellLocale = "en_US.utf8"; ## Set $wgCacheDirectory to a writable directory on the web server ## to make your wiki go slightly faster. The directory should not -## be publically accessible from the web. +## be publicly accessible from the web. #$wgCacheDirectory = "$IP/cache"; # Site language code, should be one of the list in ./languages/Names.php diff --git a/contrib/mw-to-git/t/t9362-mw-to-git-utf8.sh b/contrib/mw-to-git/t/t9362-mw-to-git-utf8.sh index b6405ce262..37021e200a 100755 --- a/contrib/mw-to-git/t/t9362-mw-to-git-utf8.sh +++ b/contrib/mw-to-git/t/t9362-mw-to-git-utf8.sh @@ -139,7 +139,7 @@ test_expect_success 'character $ in file name (git -> mw) ' ' ' -test_expect_failure 'capital at the begining of file names' ' +test_expect_failure 'capital at the beginning of file names' ' wiki_reset && git clone mediawiki::'"$WIKI_URL"' mw_dir_10 && ( @@ -156,7 +156,7 @@ test_expect_failure 'capital at the begining of file names' ' ' -test_expect_failure 'special character at the begining of file name from mw to git' ' +test_expect_failure 'special character at the beginning of file name from mw to git' ' wiki_reset && git clone mediawiki::'"$WIKI_URL"' mw_dir_11 && wiki_editpage {char_1 "expect to be renamed {char_1" false && @@ -189,7 +189,7 @@ test_expect_success 'Push page with title containing ":" other than namespace se wiki_page_exist NotANameSpace:Page ' -test_expect_success 'test of correct formating for file name from mw to git' ' +test_expect_success 'test of correct formatting for file name from mw to git' ' wiki_reset && git clone mediawiki::'"$WIKI_URL"' mw_dir_12 && wiki_editpage char_%_7b_1 "expect to be renamed char{_1" false && @@ -207,7 +207,7 @@ test_expect_success 'test of correct formating for file name from mw to git' ' ' -test_expect_failure 'test of correct formating for file name begining with special character' ' +test_expect_failure 'test of correct formatting for file name beginning with special character' ' wiki_reset && git clone mediawiki::'"$WIKI_URL"' mw_dir_13 && ( @@ -215,7 +215,7 @@ test_expect_failure 'test of correct formating for file name begining with speci echo "my new file {char_1" >\{char_1.mw && echo "my new file [char_2" >\[char_2.mw && git add . && - git commit -am "commiting some exotic file name..." && + git commit -am "committing some exotic file name..." && git push && git pull ) && @@ -226,7 +226,7 @@ test_expect_failure 'test of correct formating for file name begining with speci ' -test_expect_success 'test of correct formating for file name from git to mw' ' +test_expect_success 'test of correct formatting for file name from git to mw' ' wiki_reset && git clone mediawiki::'"$WIKI_URL"' mw_dir_14 && ( @@ -234,7 +234,7 @@ test_expect_success 'test of correct formating for file name from git to mw' ' echo "my new file char{_1" >Char\{_1.mw && echo "my new file char[_2" >Char\[_2.mw && git add . && - git commit -m "commiting some exotic file name..." && + git commit -m "committing some exotic file name..." && git push ) && wiki_getallpage ref_page_14 && diff --git a/contrib/remote-helpers/Makefile b/contrib/remote-helpers/Makefile index 9a76575f78..239161de33 100644 --- a/contrib/remote-helpers/Makefile +++ b/contrib/remote-helpers/Makefile @@ -3,6 +3,7 @@ TESTS := $(wildcard test*.sh) export T := $(addprefix $(CURDIR)/,$(TESTS)) export MAKE := $(MAKE) -e export PATH := $(CURDIR):$(PATH) +export TEST_LINT := test-lint-executable test-lint-shell-syntax test: $(MAKE) -C ../../t $@ diff --git a/contrib/remote-helpers/git-remote-bzr b/contrib/remote-helpers/git-remote-bzr index c5822e4ac9..aa7bc97bee 100755 --- a/contrib/remote-helpers/git-remote-bzr +++ b/contrib/remote-helpers/git-remote-bzr @@ -25,6 +25,7 @@ bzrlib.plugin.load_plugins() import bzrlib.generate_ids import bzrlib.transport +import bzrlib.errors import sys import os @@ -183,15 +184,24 @@ def get_filechanges(cur, prev): changes = cur.changes_from(prev) + def u(s): + return s.encode('utf-8') + for path, fid, kind in changes.added: - modified[path] = fid + modified[u(path)] = fid for path, fid, kind in changes.removed: - removed[path] = None + removed[u(path)] = None for path, fid, kind, mod, _ in changes.modified: - modified[path] = fid + modified[u(path)] = fid for oldpath, newpath, fid, kind, mod, _ in changes.renamed: - removed[oldpath] = None - modified[newpath] = fid + removed[u(oldpath)] = None + if kind == 'directory': + lst = cur.list_files(from_dir=newpath, recursive=True) + for path, file_class, kind, fid, entry in lst: + if kind != 'directory': + modified[u(newpath + '/' + path)] = fid + else: + modified[u(newpath)] = fid return modified, removed @@ -239,7 +249,7 @@ def export_files(tree, files): return final def export_branch(branch, name): - global prefix, dirname + global prefix ref = '%s/heads/%s' % (prefix, name) tip = marks.get_tip(name) @@ -260,7 +270,12 @@ def export_branch(branch, name): tz = rev.timezone committer = rev.committer.encode('utf-8') committer = "%s %u %s" % (fixup_user(committer), time, gittz(tz)) - author = committer + authors = rev.get_apparent_authors() + if authors: + author = authors[0].encode('utf-8') + author = "%s %u %s" % (fixup_user(author), time, gittz(tz)) + else: + author = committer msg = rev.message.encode('utf-8') msg += '\n' @@ -297,10 +312,10 @@ def export_branch(branch, name): else: print "merge :%s" % m + for f in removed: + print "D %s" % (f,) for f in modified_final: print "M %s :%u %s" % f - for f in removed: - print "D %s" % (f) print count += 1 @@ -320,13 +335,12 @@ def export_branch(branch, name): marks.set_tip(name, revid) def export_tag(repo, name): - global tags - try: - print "reset refs/tags/%s" % name - print "from :%u" % rev_to_mark(tags[name]) - print - except KeyError: - warn("TODO: fetch tag '%s'" % name) + global tags, prefix + + ref = '%s/tags/%s' % (prefix, name) + print "reset %s" % ref + print "from :%u" % rev_to_mark(tags[name]) + print def do_import(parser): global dirname @@ -501,6 +515,11 @@ class CustomTree(): def get_symlink_target(self, file_id): return self.updates[file_id]['data'] +def c_style_unescape(string): + if string[0] == string[-1] == '"': + return string.decode('string-escape')[1:-1] + return string + def parse_commit(parser): global marks, blob_marks, bmarks, parsed_refs global mode @@ -540,6 +559,7 @@ def parse_commit(parser): f = { 'deleted' : True } else: die('Unknown file command: %s' % line) + path = c_style_unescape(path).decode('utf-8') files[path] = f repo = parser.repo @@ -619,10 +639,9 @@ def do_export(parser): peer.import_last_revision_info_and_tags(repo, revno, revid) else: peer.import_last_revision_info(repo.repository, revno, revid) - wt = peer.bzrdir.open_workingtree() else: wt = repo.bzrdir.open_workingtree() - wt.update() + wt.update() print "ok %s" % ref print @@ -632,6 +651,7 @@ def do_capabilities(parser): print "import" print "export" print "refspec refs/heads/*:%s/heads/*" % prefix + print "refspec refs/tags/*:%s/tags/*" % prefix path = os.path.join(dirname, 'marks-git') @@ -641,12 +661,25 @@ def do_capabilities(parser): print +def ref_is_valid(name): + return not True in [c in name for c in '~^: \\'] + def do_list(parser): global tags print "? refs/heads/%s" % 'master' - for tag, revid in parser.repo.tags.get_tag_dict().items(): + + branch = parser.repo + branch.lock_read() + for tag, revid in branch.tags.get_tag_dict().items(): + try: + branch.revision_id_to_dotted_revno(revid) + except bzrlib.errors.NoSuchRevision: + continue + if not ref_is_valid(tag): + continue print "? refs/tags/%s" % tag tags[tag] = revid + branch.unlock() print "@refs/heads/%s HEAD" % 'master' print diff --git a/contrib/remote-helpers/git-remote-hg b/contrib/remote-helpers/git-remote-hg index 328c2dc76d..548133121d 100755 --- a/contrib/remote-helpers/git-remote-hg +++ b/contrib/remote-helpers/git-remote-hg @@ -8,8 +8,11 @@ # Just copy to your ~/bin, or anywhere in your $PATH. # Then you can clone with: # git clone hg::/path/to/mercurial/repo/ +# +# For remote repositories a local clone is stored in +# "$GIT_DIR/hg/origin/clone/.hg/". -from mercurial import hg, ui, bookmarks, context, util, encoding +from mercurial import hg, ui, bookmarks, context, util, encoding, node, error import re import sys @@ -18,11 +21,22 @@ import json import shutil import subprocess import urllib +import atexit # # If you want to switch to hg-git compatibility mode: # git config --global remote-hg.hg-git-compat true # +# If you are not in hg-git-compat mode and want to disable the tracking of +# named branches: +# git config --global remote-hg.track-branches false +# +# If you don't want to force pushes (and thus risk creating new remote heads): +# git config --global remote-hg.force-push false +# +# If you want the equivalent of hg's clone/pull--insecure option: +# git config remote-hg.insecure true +# # git: # Sensible defaults for git. # hg bookmarks are exported as git branches, hg branches are prefixed @@ -56,6 +70,9 @@ def hgmode(mode): m = { '100755': 'x', '120000': 'l' } return m.get(mode, '') +def hghex(node): + return hg.node.hex(node) + def get_config(config): cmd = ['git', 'config', '--get', config] process = subprocess.Popen(cmd, stdout=subprocess.PIPE) @@ -188,9 +205,15 @@ class Parser: tz = ((tz / 100) * 3600) + ((tz % 100) * 60) return (user, int(date), -tz) +def fix_file_path(path): + if not os.path.isabs(path): + return path + return os.path.relpath(path, '/') + def export_file(fc): d = fc.data() - print "M %s inline %s" % (gitmode(fc.flags()), fc.path()) + path = fix_file_path(fc.path()) + print "M %s inline %s" % (gitmode(fc.flags()), path) print "data %d" % len(d) print d @@ -267,17 +290,30 @@ def get_repo(url, alias): myui = ui.ui() myui.setconfig('ui', 'interactive', 'off') + myui.fout = sys.stderr + + try: + if get_config('remote-hg.insecure') == 'true\n': + myui.setconfig('web', 'cacerts', '') + except subprocess.CalledProcessError: + pass if hg.islocal(url): repo = hg.repository(myui, url) else: local_path = os.path.join(dirname, 'clone') if not os.path.exists(local_path): - peer, dstpeer = hg.clone(myui, {}, url, local_path, update=False, pull=True) + try: + peer, dstpeer = hg.clone(myui, {}, url, local_path, update=True, pull=True) + except: + die('Repository error') repo = dstpeer.local() else: repo = hg.repository(myui, local_path) - peer = hg.peer(myui, {}, url) + try: + peer = hg.peer(myui, {}, url) + except: + die('Repository error') repo.pull(peer, heads=None, force=True) return repo @@ -326,6 +362,8 @@ def export_ref(repo, name, kind, head): else: modified, removed = get_filechanges(repo, c, parents[0]) + desc += '\n' + if mode == 'hg': extra_msg = '' @@ -349,7 +387,6 @@ def export_ref(repo, name, kind, head): else: extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value)) - desc += '\n' if extra_msg: desc += '\n--HG--\n' + extra_msg @@ -371,7 +408,7 @@ def export_ref(repo, name, kind, head): for f in modified: export_file(c.filectx(f)) for f in removed: - print "D %s" % (f) + print "D %s" % (fix_file_path(f)) print count += 1 @@ -531,7 +568,6 @@ def parse_blob(parser): data = parser.get_data() blob_marks[mark] = data parser.next() - return def get_merge_files(repo, p1, p2, files): for e in repo[p1].files(): @@ -542,7 +578,7 @@ def get_merge_files(repo, p1, p2, files): files[e] = f def parse_commit(parser): - global marks, blob_marks, bmarks, parsed_refs + global marks, blob_marks, parsed_refs global mode from_mark = merge_mark = None @@ -575,7 +611,7 @@ def parse_commit(parser): mark = int(mark_ref[1:]) f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] } elif parser.check('D'): - t, path = line.split(' ') + t, path = line.split(' ', 1) f = { 'deleted' : True } else: die('Unknown file command: %s' % line) @@ -618,11 +654,15 @@ def parse_commit(parser): if merge_mark: get_merge_files(repo, p1, p2, files) + # Check if the ref is supposed to be a named branch + if ref.startswith('refs/heads/branches/'): + extra['branch'] = ref[len('refs/heads/branches/'):] + if mode == 'hg': i = data.find('\n--HG--\n') if i >= 0: tmp = data[i + len('\n--HG--\n'):].strip() - for k, v in [e.split(' : ') for e in tmp.split('\n')]: + for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]: if k == 'rename': old, new = v.split(' => ', 1) files[new]['rename'] = old @@ -647,10 +687,11 @@ def parse_commit(parser): rev = repo[node].rev() parsed_refs[ref] = node - marks.new_mark(rev, commit_mark) def parse_reset(parser): + global parsed_refs + ref = parser[1] parser.next() # ugh @@ -680,6 +721,8 @@ def parse_tag(parser): def do_export(parser): global parsed_refs, bmarks, peer + p_bmarks = [] + parser.next() for line in parser.each_block('done'): @@ -698,28 +741,55 @@ def do_export(parser): for ref, node in parsed_refs.iteritems(): if ref.startswith('refs/heads/branches'): - pass + print "ok %s" % ref elif ref.startswith('refs/heads/'): bmark = ref[len('refs/heads/'):] - if bmark in bmarks: - old = bmarks[bmark].hex() - else: - old = '' - if not bookmarks.pushbookmark(parser.repo, bmark, old, node): - continue + p_bmarks.append((bmark, node)) + continue elif ref.startswith('refs/tags/'): tag = ref[len('refs/tags/'):] - parser.repo.tag([tag], node, None, True, None, {}) + if mode == 'git': + msg = 'Added tag %s for changeset %s' % (tag, hghex(node[:6])); + parser.repo.tag([tag], node, msg, False, None, {}) + else: + parser.repo.tag([tag], node, None, True, None, {}) + print "ok %s" % ref else: # transport-helper/fast-export bugs continue + + if peer: + parser.repo.push(peer, force=force_push) + + # handle bookmarks + for bmark, node in p_bmarks: + ref = 'refs/heads/' + bmark + new = hghex(node) + + if bmark in bmarks: + old = bmarks[bmark].hex() + else: + old = '' + + if bmark == 'master' and 'master' not in parser.repo._bookmarks: + # fake bookmark + pass + elif bookmarks.pushbookmark(parser.repo, bmark, old, new): + # updated locally + pass + else: + print "error %s" % ref + continue + + if peer: + if not peer.pushkey('bookmarks', bmark, old, new): + print "error %s" % ref + continue + print "ok %s" % ref print - if peer: - parser.repo.push(peer, force=False) - def fix_path(alias, repo, orig_url): repo_url = util.url(repo.url()) url = util.url(orig_url) @@ -732,7 +802,7 @@ def main(args): global prefix, dirname, branches, bmarks global marks, blob_marks, parsed_refs global peer, mode, bad_mail, bad_name - global track_branches + global track_branches, force_push, is_tmp alias = args[1] url = args[2] @@ -740,12 +810,16 @@ def main(args): hg_git_compat = False track_branches = True + force_push = True + try: if get_config('remote-hg.hg-git-compat') == 'true\n': hg_git_compat = True track_branches = False if get_config('remote-hg.track-branches') == 'false\n': track_branches = False + if get_config('remote-hg.force-push') == 'false\n': + force_push = False except subprocess.CalledProcessError: pass @@ -770,6 +844,7 @@ def main(args): bmarks = {} blob_marks = {} parsed_refs = {} + marks = None repo = get_repo(url, alias) prefix = 'refs/hg/%s' % alias @@ -797,9 +872,13 @@ def main(args): die('unhandled command: %s' % line) sys.stdout.flush() +def bye(): + if not marks: + return if not is_tmp: marks.store() else: shutil.rmtree(dirname) +atexit.register(bye) sys.exit(main(sys.argv)) diff --git a/contrib/remote-helpers/test-bzr.sh b/contrib/remote-helpers/test-bzr.sh index 70aa8a010a..34666e1d0f 100755 --- a/contrib/remote-helpers/test-bzr.sh +++ b/contrib/remote-helpers/test-bzr.sh @@ -17,20 +17,6 @@ if ! "$PYTHON_PATH" -c 'import bzrlib'; then test_done fi -cmd=' -import bzrlib -bzrlib.initialize() -import bzrlib.plugin -bzrlib.plugin.load_plugins() -import bzrlib.plugins.fastimport -' - -if ! "$PYTHON_PATH" -c "$cmd"; then - echo "consider setting BZR_PLUGIN_PATH=$HOME/.bazaar/plugins" 1>&2 - skip_all='skipping remote-bzr tests; bzr-fastimport not available' - test_done -fi - check () { (cd $1 && git log --format='%s' -1 && @@ -136,7 +122,109 @@ test_expect_success 'special modes' ' (cd gitrepo && git cat-file -p HEAD:link > ../actual) && - echo -n content > expected && + printf content > expected && + test_cmp expected actual +' + +cat > expected <<EOF +100644 blob 54f9d6da5c91d556e6b54340b1327573073030af content +100755 blob 68769579c3eaadbe555379b9c3538e6628bae1eb executable +120000 blob 6b584e8ece562ebffc15d38808cd6b98fc3d97ea link +040000 tree 35c0caa46693cef62247ac89a680f0c5ce32b37b movedir-new +EOF + +test_expect_success 'moving directory' ' + (cd bzrrepo && + mkdir movedir && + echo one > movedir/one && + echo two > movedir/two && + bzr add movedir && + bzr commit -m movedir && + bzr mv movedir movedir-new && + bzr commit -m movedir-new) && + + (cd gitrepo && + git pull && + git ls-tree HEAD > ../actual) && + + test_cmp expected actual +' + +test_expect_success 'different authors' ' + (cd bzrrepo && + echo john >> content && + bzr commit -m john \ + --author "Jane Rey <jrey@example.com>" \ + --author "John Doe <jdoe@example.com>") && + + (cd gitrepo && + git pull && + git show --format="%an <%ae>, %cn <%ce>" --quiet > ../actual) && + + echo "Jane Rey <jrey@example.com>, A U Thor <author@example.com>" > expected && + test_cmp expected actual +' + +test_expect_success 'fetch utf-8 filenames' ' + mkdir -p tmp && cd tmp && + test_when_finished "cd .. && rm -rf tmp && LC_ALL=C" && + + LC_ALL=en_US.UTF-8 + export LC_ALL + ( + bzr init bzrrepo && + cd bzrrepo && + + echo test >> "ærø" && + bzr add "ærø" && + echo test >> "ø~?" && + bzr add "ø~?" && + bzr commit -m add-utf-8 && + echo test >> "ærø" && + bzr commit -m test-utf-8 && + bzr rm "ø~?" && + bzr mv "ærø" "ø~?" && + bzr commit -m bzr-mv-utf-8 + ) && + + ( + git clone "bzr::$PWD/bzrrepo" gitrepo && + cd gitrepo && + git -c core.quotepath=false ls-files > ../actual + ) && + echo "ø~?" > expected && + test_cmp expected actual +' + +test_expect_success 'push utf-8 filenames' ' + mkdir -p tmp && cd tmp && + test_when_finished "cd .. && rm -rf tmp && LC_ALL=C" && + + LC_ALL=en_US.UTF-8 + export LC_ALL + + ( + bzr init bzrrepo && + cd bzrrepo && + + echo one >> content && + bzr add content && + bzr commit -m one + ) && + + ( + git clone "bzr::$PWD/bzrrepo" gitrepo && + cd gitrepo && + + echo test >> "ærø" && + git add "ærø" && + git commit -m utf-8 && + + git push + ) && + + (cd bzrrepo && bzr ls > ../actual) && + printf "content\nærø\n" > expected && test_cmp expected actual ' diff --git a/contrib/remote-helpers/test-hg-bidi.sh b/contrib/remote-helpers/test-hg-bidi.sh index 1d61982436..f36895311e 100755 --- a/contrib/remote-helpers/test-hg-bidi.sh +++ b/contrib/remote-helpers/test-hg-bidi.sh @@ -22,7 +22,6 @@ fi # clone to a git repo git_clone () { - hg -R $1 bookmark -f -r tip master && git clone -q "hg::$PWD/$1" $2 } @@ -30,6 +29,7 @@ git_clone () { hg_clone () { ( hg init $2 && + hg -R $2 bookmark -i master && cd $1 && git push -q "hg::$PWD/../$2" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' ) && @@ -50,7 +50,8 @@ hg_push () { } hg_log () { - hg -R $1 log --graph --debug | grep -v 'tag: *default/' + hg -R $1 log --graph --debug >log && + grep -v 'tag: *default/' log } setup () { @@ -62,6 +63,8 @@ setup () { echo "commit = -d \"0 0\"" echo "debugrawcommit = -d \"0 0\"" echo "tag = -d \"0 0\"" + echo "[extensions]" + echo "graphlog =" ) >> "$HOME"/.hgrc && git config --global remote-hg.hg-git-compat true @@ -200,8 +203,8 @@ test_expect_success 'hg branch' ' hg_push hgrepo gitrepo && hg_clone gitrepo hgrepo2 && - : TODO, avoid "master" bookmark && - (cd hgrepo2 && hg checkout gamma) && + : Back to the common revision && + (cd hgrepo && hg checkout default) && hg_log hgrepo > expected && hg_log hgrepo2 > actual && diff --git a/contrib/remote-helpers/test-hg-hg-git.sh b/contrib/remote-helpers/test-hg-hg-git.sh index 7e3967f5b6..253e65aaa8 100755 --- a/contrib/remote-helpers/test-hg-hg-git.sh +++ b/contrib/remote-helpers/test-hg-hg-git.sh @@ -27,7 +27,6 @@ fi # clone to a git repo with git git_clone_git () { - hg -R $1 bookmark -f -r tip master && git clone -q "hg::$PWD/$1" $2 } @@ -35,6 +34,7 @@ git_clone_git () { hg_clone_git () { ( hg init $2 && + hg -R $2 bookmark -i master && cd $1 && git push -q "hg::$PWD/../$2" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' ) && @@ -47,7 +47,7 @@ git_clone_hg () { ( git init -q $2 && cd $1 && - hg bookmark -f -r tip master && + hg bookmark -i -f -r tip master && hg -q push -r master ../$2 || true ) } @@ -78,7 +78,8 @@ hg_push_hg () { } hg_log () { - hg -R $1 log --graph --debug | grep -v 'tag: *default/' + hg -R $1 log --graph --debug >log && + grep -v 'tag: *default/' log } git_log () { @@ -97,6 +98,7 @@ setup () { echo "[extensions]" echo "hgext.bookmarks =" echo "hggit =" + echo "graphlog =" ) >> "$HOME"/.hgrc && git config --global receive.denycurrentbranch warn git config --global remote-hg.hg-git-compat true @@ -140,7 +142,6 @@ test_expect_success 'executable bit' ' git_clone_$x hgrepo-$x gitrepo2-$x && git_log gitrepo2-$x > log-$x done && - cp -r log-* output-* /tmp/foo/ && test_cmp output-hg output-git && test_cmp log-hg log-git diff --git a/contrib/remote-helpers/test-hg.sh b/contrib/remote-helpers/test-hg.sh index 7bb81f2f8e..d5b873051f 100755 --- a/contrib/remote-helpers/test-hg.sh +++ b/contrib/remote-helpers/test-hg.sh @@ -118,4 +118,40 @@ test_expect_success 'update bookmark' ' hg -R hgrepo bookmarks | egrep "devel[ ]+3:" ' +author_test () { + echo $1 >> content && + hg commit -u "$2" -m "add $1" && + echo "$3" >> ../expected +} + +test_expect_success 'authors' ' + mkdir -p tmp && cd tmp && + test_when_finished "cd .. && rm -rf tmp" && + + ( + hg init hgrepo && + cd hgrepo && + + touch content && + hg add content && + + author_test alpha "" "H G Wells <wells@example.com>" && + author_test beta "test" "test <unknown>" && + author_test beta "test <test@example.com> (comment)" "test <unknown>" && + author_test gamma "<test@example.com>" "Unknown <test@example.com>" && + author_test delta "name<test@example.com>" "name <test@example.com>" && + author_test epsilon "name <test@example.com" "name <unknown>" && + author_test zeta " test " "test <unknown>" && + author_test eta "test < test@example.com >" "test <test@example.com>" && + author_test theta "test >test@example.com>" "test <unknown>" && + author_test iota "test < test <at> example <dot> com>" "test <unknown>" && + author_test kappa "test@example.com" "test@example.com <unknown>" + ) && + + git clone "hg::$PWD/hgrepo" gitrepo && + git --git-dir=gitrepo/.git log --reverse --format="%an <%ae>" > actual && + + test_cmp expected actual +' + test_done diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh index 80d339960b..b0f8536fca 100755 --- a/contrib/subtree/t/t7900-subtree.sh +++ b/contrib/subtree/t/t7900-subtree.sh @@ -419,7 +419,7 @@ test_expect_success 'add main-sub5' ' test_expect_success 'split for main-sub5 without --onto' ' # also test that we still can split out an entirely new subtree # if the parent of the first commit in the tree is not empty, - # then the new subtree has accidently been attached to something + # then the new subtree has accidentally been attached to something git subtree split --prefix subdir2 --branch mainsub5 && check_equal ''"$(git log --pretty=format:%P -1 mainsub5)"'' "" ' @@ -153,36 +153,13 @@ static void check_safe_crlf(const char *path, enum crlf_action crlf_action, static int has_cr_in_index(const char *path) { - int pos, len; unsigned long sz; - enum object_type type; void *data; int has_cr; - struct index_state *istate = &the_index; - len = strlen(path); - pos = index_name_pos(istate, path, len); - if (pos < 0) { - /* - * We might be in the middle of a merge, in which - * case we would read stage #2 (ours). - */ - int i; - for (i = -pos - 1; - (pos < 0 && i < istate->cache_nr && - !strcmp(istate->cache[i]->name, path)); - i++) - if (ce_stage(istate->cache[i]) == 2) - pos = i; - } - if (pos < 0) + data = read_blob_data_from_cache(path, &sz); + if (!data) return 0; - data = read_sha1_file(istate->cache[pos]->sha1, &type, &sz); - if (!data || type != OBJ_BLOB) { - free(data); - return 0; - } - has_cr = memchr(data, '\r', sz) != NULL; free(data); return has_cr; @@ -600,7 +600,7 @@ static void parse_host_arg(char *extra_args, int buflen) static int execute(void) { - static char line[1000]; + char *line = packet_buffer; int pktlen, len, i; char *addr = getenv("REMOTE_ADDR"), *port = getenv("REMOTE_PORT"); @@ -608,7 +608,7 @@ static int execute(void) loginfo("Connection from %s:%s", addr, port); alarm(init_timeout ? init_timeout : timeout); - pktlen = packet_read_line(0, line, sizeof(line)); + pktlen = packet_read(0, NULL, NULL, packet_buffer, sizeof(packet_buffer), 0); alarm(0); len = strlen(line); @@ -383,7 +383,7 @@ static int is_date(int year, int month, int day, struct tm *now_tm, time_t now, * sense to specify timestamp way into the future. Make * sure it is not later than ten days from now... */ - if (now + 10*24*3600 < specified) + if ((specified != -1) && (now + 10*24*3600 < specified)) return 0; tm->tm_mon = r->tm_mon; tm->tm_mday = r->tm_mday; @@ -694,8 +694,14 @@ int parse_date_basic(const char *date, unsigned long *timestamp, int *offset) /* mktime uses local timezone */ *timestamp = tm_to_time_t(&tm); - if (*offset == -1) - *offset = ((time_t)*timestamp - mktime(&tm)) / 60; + if (*offset == -1) { + time_t temp_time = mktime(&tm); + if ((time_t)*timestamp > temp_time) { + *offset = ((time_t)*timestamp - temp_time) / 60; + } else { + *offset = -(int)((temp_time - (time_t)*timestamp) / 60); + } + } if (*timestamp == -1) return -1; @@ -1565,7 +1565,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) * Binary files are displayed with "Bin XXX -> YYY bytes" * instead of the change count and graph. This part is treated * similarly to the graph part, except that it is not - * "scaled". If total width is too small to accomodate the + * "scaled". If total width is too small to accommodate the * guaranteed minimum width of the filename part and the * separators and this message, this message will "overflow" * making the line longer than the maximum width. @@ -2255,6 +2255,7 @@ static void builtin_diff(const char *name_a, const char *del = diff_get_color_opt(o, DIFF_FILE_OLD); const char *add = diff_get_color_opt(o, DIFF_FILE_NEW); show_submodule_summary(o->file, one ? one->path : two->path, + line_prefix, one->sha1, two->sha1, two->dirty_submodule, meta, del, add, reset); return; @@ -3596,8 +3597,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF); else if (!strcmp(arg, "--histogram")) options->xdl_opts = DIFF_WITH_ALG(options, HISTOGRAM_DIFF); - else if (!prefixcmp(arg, "--diff-algorithm=")) { - long value = parse_algorithm_value(arg+17); + else if ((argcount = parse_long_opt("diff-algorithm", av, &optarg))) { + long value = parse_algorithm_value(optarg); if (value < 0) return error("option diff-algorithm accepts \"myers\", " "\"minimal\", \"patience\" and \"histogram\""); @@ -3605,6 +3606,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) DIFF_XDL_CLR(options, NEED_MINIMAL); options->xdl_opts &= ~XDF_DIFF_ALGORITHM_MASK; options->xdl_opts |= value; + return argcount; } /* flags options */ diff --git a/diffcore-break.c b/diffcore-break.c index 44f8678d22..1d9e530a84 100644 --- a/diffcore-break.c +++ b/diffcore-break.c @@ -68,6 +68,9 @@ static int should_break(struct diff_filespec *src, if (max_size < MINIMUM_BREAK_SIZE) return 0; /* we do not break too small filepair */ + if (!src->size) + return 0; /* we do not let empty files get renamed */ + if (diffcore_count_changes(src, dst, &src->cnt_data, &dst->cnt_data, 0, diff --git a/diffcore-pickaxe.c b/diffcore-pickaxe.c index b097fa7661..63722f86dc 100644 --- a/diffcore-pickaxe.c +++ b/diffcore-pickaxe.c @@ -8,7 +8,12 @@ #include "xdiff-interface.h" #include "kwset.h" -typedef int (*pickaxe_fn)(struct diff_filepair *p, struct diff_options *o, regex_t *regexp, kwset_t kws); +typedef int (*pickaxe_fn)(mmfile_t *one, mmfile_t *two, + struct diff_options *o, + regex_t *regexp, kwset_t kws); + +static int pickaxe_match(struct diff_filepair *p, struct diff_options *o, + regex_t *regexp, kwset_t kws, pickaxe_fn fn); static void pickaxe(struct diff_queue_struct *q, struct diff_options *o, regex_t *regexp, kwset_t kws, pickaxe_fn fn) @@ -22,7 +27,7 @@ static void pickaxe(struct diff_queue_struct *q, struct diff_options *o, /* Showing the whole changeset if needle exists */ for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; - if (fn(p, o, regexp, kws)) + if (pickaxe_match(p, o, regexp, kws, fn)) return; /* do not munge the queue */ } @@ -37,7 +42,7 @@ static void pickaxe(struct diff_queue_struct *q, struct diff_options *o, /* Showing only the filepairs that has the needle */ for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; - if (fn(p, o, regexp, kws)) + if (pickaxe_match(p, o, regexp, kws, fn)) diff_q(&outq, p); else diff_free_filepair(p); @@ -74,64 +79,33 @@ static void diffgrep_consume(void *priv, char *line, unsigned long len) line[len] = hold; } -static void fill_one(struct diff_filespec *one, - mmfile_t *mf, struct userdiff_driver **textconv) -{ - if (DIFF_FILE_VALID(one)) { - *textconv = get_textconv(one); - mf->size = fill_textconv(*textconv, one, &mf->ptr); - } else { - memset(mf, 0, sizeof(*mf)); - } -} - -static int diff_grep(struct diff_filepair *p, struct diff_options *o, +static int diff_grep(mmfile_t *one, mmfile_t *two, + struct diff_options *o, regex_t *regexp, kwset_t kws) { regmatch_t regmatch; - struct userdiff_driver *textconv_one = NULL; - struct userdiff_driver *textconv_two = NULL; - mmfile_t mf1, mf2; - int hit; + struct diffgrep_cb ecbdata; + xpparam_t xpp; + xdemitconf_t xecfg; - if (diff_unmodified_pair(p)) - return 0; + if (!one) + return !regexec(regexp, two->ptr, 1, ®match, 0); + if (!two) + return !regexec(regexp, one->ptr, 1, ®match, 0); - fill_one(p->one, &mf1, &textconv_one); - fill_one(p->two, &mf2, &textconv_two); - - if (!mf1.ptr) { - if (!mf2.ptr) - return 0; /* ignore unmerged */ - /* created "two" -- does it have what we are looking for? */ - hit = !regexec(regexp, mf2.ptr, 1, ®match, 0); - } else if (!mf2.ptr) { - /* removed "one" -- did it have what we are looking for? */ - hit = !regexec(regexp, mf1.ptr, 1, ®match, 0); - } else { - /* - * We have both sides; need to run textual diff and see if - * the pattern appears on added/deleted lines. - */ - struct diffgrep_cb ecbdata; - xpparam_t xpp; - xdemitconf_t xecfg; - - memset(&xpp, 0, sizeof(xpp)); - memset(&xecfg, 0, sizeof(xecfg)); - ecbdata.regexp = regexp; - ecbdata.hit = 0; - xecfg.ctxlen = o->context; - xecfg.interhunkctxlen = o->interhunkcontext; - xdi_diff_outf(&mf1, &mf2, diffgrep_consume, &ecbdata, - &xpp, &xecfg); - hit = ecbdata.hit; - } - if (textconv_one) - free(mf1.ptr); - if (textconv_two) - free(mf2.ptr); - return hit; + /* + * We have both sides; need to run textual diff and see if + * the pattern appears on added/deleted lines. + */ + memset(&xpp, 0, sizeof(xpp)); + memset(&xecfg, 0, sizeof(xecfg)); + ecbdata.regexp = regexp; + ecbdata.hit = 0; + xecfg.ctxlen = o->context; + xecfg.interhunkctxlen = o->interhunkcontext; + xdi_diff_outf(one, two, diffgrep_consume, &ecbdata, + &xpp, &xecfg); + return ecbdata.hit; } static void diffcore_pickaxe_grep(struct diff_options *o) @@ -198,17 +172,37 @@ static unsigned int contains(mmfile_t *mf, struct diff_options *o, return cnt; } -static int has_changes(struct diff_filepair *p, struct diff_options *o, +static int has_changes(mmfile_t *one, mmfile_t *two, + struct diff_options *o, regex_t *regexp, kwset_t kws) { - struct userdiff_driver *textconv_one = get_textconv(p->one); - struct userdiff_driver *textconv_two = get_textconv(p->two); + if (!one) + return contains(two, o, regexp, kws) != 0; + if (!two) + return contains(one, o, regexp, kws) != 0; + return contains(one, o, regexp, kws) != contains(two, o, regexp, kws); +} + +static int pickaxe_match(struct diff_filepair *p, struct diff_options *o, + regex_t *regexp, kwset_t kws, pickaxe_fn fn) +{ + struct userdiff_driver *textconv_one = NULL; + struct userdiff_driver *textconv_two = NULL; mmfile_t mf1, mf2; int ret; if (!o->pickaxe[0]) return 0; + /* ignore unmerged */ + if (!DIFF_FILE_VALID(p->one) && !DIFF_FILE_VALID(p->two)) + return 0; + + if (DIFF_OPT_TST(o, ALLOW_TEXTCONV)) { + textconv_one = get_textconv(p->one); + textconv_two = get_textconv(p->two); + } + /* * If we have an unmodified pair, we know that the count will be the * same and don't even have to load the blobs. Unless textconv is in @@ -219,20 +213,12 @@ static int has_changes(struct diff_filepair *p, struct diff_options *o, if (textconv_one == textconv_two && diff_unmodified_pair(p)) return 0; - fill_one(p->one, &mf1, &textconv_one); - fill_one(p->two, &mf2, &textconv_two); + mf1.size = fill_textconv(textconv_one, p->one, &mf1.ptr); + mf2.size = fill_textconv(textconv_two, p->two, &mf2.ptr); - if (!mf1.ptr) { - if (!mf2.ptr) - ret = 0; /* ignore unmerged */ - /* created */ - ret = contains(&mf2, o, regexp, kws) != 0; - } - else if (!mf2.ptr) /* removed */ - ret = contains(&mf1, o, regexp, kws) != 0; - else - ret = contains(&mf1, o, regexp, kws) != - contains(&mf2, o, regexp, kws); + ret = fn(DIFF_FILE_VALID(p->one) ? &mf1 : NULL, + DIFF_FILE_VALID(p->two) ? &mf2 : NULL, + o, regexp, kws); if (textconv_one) free(mf1.ptr); diff --git a/diffcore-rename.c b/diffcore-rename.c index 512d0ac5fd..6c7a72fbe7 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -389,6 +389,7 @@ static int find_exact_renames(struct diff_options *options) struct hash_table file_table; init_hash(&file_table); + preallocate_hash(&file_table, rename_src_nr + rename_dst_nr); for (i = 0; i < rename_src_nr; i++) insert_file_table(&file_table, -1, i, rename_src[i].p->one); @@ -1647,7 +1647,7 @@ int remove_path(const char *name) { char *slash; - if (unlink(name) && errno != ENOENT) + if (unlink(name) && errno != ENOENT && errno != ENOTDIR) return -1; slash = strrchr(name, '/'); @@ -120,16 +120,18 @@ static int streaming_write_entry(struct cache_entry *ce, char *path, const struct checkout *state, int to_tempfile, int *fstat_done, struct stat *statbuf) { - int result = -1; + int result = 0; int fd; fd = open_output_fd(path, ce, to_tempfile); - if (0 <= fd) { - result = stream_blob_to_fd(fd, ce->sha1, filter, 1); - *fstat_done = fstat_output(fd, state, statbuf); - result = close(fd); - } - if (result && 0 <= fd) + if (fd < 0) + return -1; + + result |= stream_blob_to_fd(fd, ce->sha1, filter, 1); + *fstat_done = fstat_output(fd, state, statbuf); + result |= close(fd); + + if (result) unlink(path); return result; } diff --git a/fetch-pack.c b/fetch-pack.c index 6d8926a550..f156dd4fac 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -36,7 +36,7 @@ static int marked; #define MAX_IN_VAIN 256 static struct commit_list *rev_list; -static int non_common_revs, multi_ack, use_sideband; +static int non_common_revs, multi_ack, use_sideband, allow_tip_sha1_in_want; static void rev_list_push(struct commit *commit, int mark) { @@ -172,8 +172,8 @@ static void consume_shallow_list(struct fetch_pack_args *args, int fd) * shallow and unshallow commands every time there * is a block of have lines exchanged. */ - char line[1000]; - while (packet_read_line(fd, line, sizeof(line))) { + char *line; + while ((line = packet_read_line(fd, NULL))) { if (!prefixcmp(line, "shallow ")) continue; if (!prefixcmp(line, "unshallow ")) @@ -215,17 +215,17 @@ static int write_shallow_commits(struct strbuf *out, int use_pack_protocol) static enum ack_type get_ack(int fd, unsigned char *result_sha1) { - static char line[1000]; - int len = packet_read_line(fd, line, sizeof(line)); + int len; + char *line = packet_read_line(fd, &len); if (!len) die("git fetch-pack: expected ACK/NAK, got EOF"); - if (line[len-1] == '\n') - line[--len] = 0; if (!strcmp(line, "NAK")) return NAK; if (!prefixcmp(line, "ACK ")) { if (!get_sha1_hex(line+4, result_sha1)) { + if (len < 45) + return ACK; if (strstr(line+45, "continue")) return ACK_continue; if (strstr(line+45, "common")) @@ -245,7 +245,7 @@ static void send_request(struct fetch_pack_args *args, send_sideband(fd, -1, buf->buf, buf->len, LARGE_PACKET_MAX); packet_flush(fd); } else - safe_write(fd, buf->buf, buf->len); + write_or_die(fd, buf->buf, buf->len); } static void insert_one_alternate_ref(const struct ref *ref, void *unused) @@ -346,11 +346,11 @@ static int find_common(struct fetch_pack_args *args, state_len = req_buf.len; if (args->depth > 0) { - char line[1024]; + char *line; unsigned char sha1[20]; send_request(args, fd[1], &req_buf); - while (packet_read_line(fd[0], line, sizeof(line))) { + while ((line = packet_read_line(fd[0], NULL))) { if (!prefixcmp(line, "shallow ")) { if (get_sha1_hex(line + 8, sha1)) die("invalid shallow line: %s", line); @@ -520,47 +520,37 @@ static void mark_recent_complete_commits(struct fetch_pack_args *args, } } -static int non_matching_ref(struct string_list_item *item, void *unused) -{ - if (item->util) { - item->util = NULL; - return 0; - } - else - return 1; -} - static void filter_refs(struct fetch_pack_args *args, - struct ref **refs, struct string_list *sought) + struct ref **refs, + struct ref **sought, int nr_sought) { struct ref *newlist = NULL; struct ref **newtail = &newlist; struct ref *ref, *next; - int sought_pos; + int i; - sought_pos = 0; + i = 0; for (ref = *refs; ref; ref = next) { int keep = 0; next = ref->next; + if (!memcmp(ref->name, "refs/", 5) && check_refname_format(ref->name + 5, 0)) ; /* trash */ else { - while (sought_pos < sought->nr) { - int cmp = strcmp(ref->name, sought->items[sought_pos].string); + while (i < nr_sought) { + int cmp = strcmp(ref->name, sought[i]->name); if (cmp < 0) break; /* definitely do not have it */ else if (cmp == 0) { keep = 1; /* definitely have it */ - sought->items[sought_pos++].util = "matched"; - break; + sought[i]->matched = 1; } - else - sought_pos++; /* might have it; keep looking */ + i++; } } - if (! keep && args->fetch_all && + if (!keep && args->fetch_all && (!args->depth || prefixcmp(ref->name, "refs/tags/"))) keep = 1; @@ -573,7 +563,21 @@ static void filter_refs(struct fetch_pack_args *args, } } - filter_string_list(sought, 0, non_matching_ref, NULL); + /* Append unmatched requests to the list */ + if (allow_tip_sha1_in_want) { + for (i = 0; i < nr_sought; i++) { + ref = sought[i]; + if (ref->matched) + continue; + if (get_sha1_hex(ref->name, ref->old_sha1)) + continue; + + ref->matched = 1; + *newtail = ref; + ref->next = NULL; + newtail = &ref->next; + } + } *refs = newlist; } @@ -583,7 +587,8 @@ static void mark_alternate_complete(const struct ref *ref, void *unused) } static int everything_local(struct fetch_pack_args *args, - struct ref **refs, struct string_list *sought) + struct ref **refs, + struct ref **sought, int nr_sought) { struct ref *ref; int retval; @@ -637,7 +642,7 @@ static int everything_local(struct fetch_pack_args *args, } } - filter_refs(args, refs, sought); + filter_refs(args, refs, sought, nr_sought); for (retval = 1, ref = *refs; ref ; ref = ref->next) { const unsigned char *remote = ref->old_sha1; @@ -767,10 +772,17 @@ static int get_pack(struct fetch_pack_args *args, return 0; } +static int cmp_ref_by_name(const void *a_, const void *b_) +{ + const struct ref *a = *((const struct ref **)a_); + const struct ref *b = *((const struct ref **)b_); + return strcmp(a->name, b->name); +} + static struct ref *do_fetch_pack(struct fetch_pack_args *args, int fd[2], const struct ref *orig_ref, - struct string_list *sought, + struct ref **sought, int nr_sought, char **pack_lockfile) { struct ref *ref = copy_ref_list(orig_ref); @@ -779,6 +791,7 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args, int agent_len; sort_ref_list(&ref, ref_compare_name); + qsort(sought, nr_sought, sizeof(*sought), cmp_ref_by_name); if (is_repository_shallow() && !server_supports("shallow")) die("Server does not support shallow clients"); @@ -808,6 +821,11 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args, fprintf(stderr, "Server supports side-band\n"); use_sideband = 1; } + if (server_supports("allow-tip-sha1-in-want")) { + if (args->verbose) + fprintf(stderr, "Server supports allow-tip-sha1-in-want\n"); + allow_tip_sha1_in_want = 1; + } if (!server_supports("thin-pack")) args->use_thin_pack = 0; if (!server_supports("no-progress")) @@ -827,7 +845,7 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args, agent_len, agent_feature); } - if (everything_local(args, &ref, sought)) { + if (everything_local(args, &ref, sought, nr_sought)) { packet_flush(fd[1]); goto all_done; } @@ -890,11 +908,32 @@ static void fetch_pack_setup(void) did_setup = 1; } +static int remove_duplicates_in_refs(struct ref **ref, int nr) +{ + struct string_list names = STRING_LIST_INIT_NODUP; + int src, dst; + + for (src = dst = 0; src < nr; src++) { + struct string_list_item *item; + item = string_list_insert(&names, ref[src]->name); + if (item->util) + continue; /* already have it */ + item->util = ref[src]; + if (src != dst) + ref[dst] = ref[src]; + dst++; + } + for (src = dst; src < nr; src++) + ref[src] = NULL; + string_list_clear(&names, 0); + return dst; +} + struct ref *fetch_pack(struct fetch_pack_args *args, int fd[], struct child_process *conn, const struct ref *ref, const char *dest, - struct string_list *sought, + struct ref **sought, int nr_sought, char **pack_lockfile) { struct stat st; @@ -906,16 +945,14 @@ struct ref *fetch_pack(struct fetch_pack_args *args, st.st_mtime = 0; } - if (sought->nr) { - sort_string_list(sought); - string_list_remove_duplicates(sought, 0); - } + if (nr_sought) + nr_sought = remove_duplicates_in_refs(sought, nr_sought); if (!ref) { packet_flush(fd[1]); die("no matching remote head"); } - ref_cpy = do_fetch_pack(args, fd, ref, sought, pack_lockfile); + ref_cpy = do_fetch_pack(args, fd, ref, sought, nr_sought, pack_lockfile); if (args->depth > 0) { static struct lock_file lock; diff --git a/fetch-pack.h b/fetch-pack.h index cb148719bf..dc5266c970 100644 --- a/fetch-pack.h +++ b/fetch-pack.h @@ -20,17 +20,16 @@ struct fetch_pack_args { }; /* - * sought contains the full names of remote references that should be - * updated from. On return, the names that were found on the remote - * will have been removed from the list. The util members of the - * string_list_items are used internally; they must be NULL on entry - * (and will be NULL on exit). + * sought represents remote references that should be updated from. + * On return, the names that were found on the remote will have been + * marked as such. */ struct ref *fetch_pack(struct fetch_pack_args *args, int fd[], struct child_process *conn, const struct ref *ref, const char *dest, - struct string_list *sought, + struct ref **sought, + int nr_sought, char **pack_lockfile); #endif diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 710764abb1..d2c4ce6e1e 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -1247,7 +1247,7 @@ sub summarize_hunk { # Print a one-line summary of each hunk in the array ref in -# the first argument, starting wih the index in the 2nd. +# the first argument, starting with the index in the 2nd. sub display_hunks { my ($hunks, $i) = @_; my $ctr = 0; @@ -778,13 +778,6 @@ To restore the original branch and stop patching run \"\$cmdline --abort\"." action=yes fi - if test -f "$dotest/final-commit" - then - FIRSTLINE=$(sed 1q "$dotest/final-commit") - else - FIRSTLINE="" - fi - if test $action = skip then go_next @@ -797,6 +790,13 @@ To restore the original branch and stop patching run \"\$cmdline --abort\"." stop_here $this fi + if test -f "$dotest/final-commit" + then + FIRSTLINE=$(sed 1q "$dotest/final-commit") + else + FIRSTLINE="" + fi + say "$(eval_gettext "Applying: \$FIRSTLINE")" case "$resolved" in diff --git a/git-compat-util.h b/git-compat-util.h index e715285e7c..e955bb5e8b 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -86,6 +86,9 @@ #define _SGI_SOURCE 1 #ifdef WIN32 /* Both MinGW and MSVC */ +# if defined (_MSC_VER) +# define _WIN32_WINNT 0x0502 +# endif #define WIN32_LEAN_AND_MEAN /* stops windows.h including winsock.h */ #include <winsock2.h> #include <windows.h> @@ -160,6 +163,7 @@ typedef long intptr_t; typedef unsigned long uintptr_t; #endif +int get_st_mode_bits(const char *path, int *mode); #if defined(__CYGWIN__) #undef _XOPEN_SOURCE #include <grp.h> @@ -327,6 +331,7 @@ extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2))) extern void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list params)); extern void set_error_routine(void (*routine)(const char *err, va_list params)); +extern void set_die_is_recursing_routine(int (*routine)(void)); extern int prefixcmp(const char *str, const char *prefix); extern int suffixcmp(const char *str, const char *suffix); diff --git a/git-cvsserver.perl b/git-cvsserver.perl index f1c3f49a83..a0d796e570 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -2911,7 +2911,7 @@ sub filenamesplit } # Cleanup various junk in filename (try to canonicalize it), and -# add prependdir to accomodate running CVS client from a +# add prependdir to accommodate running CVS client from a # subdirectory (so the output is relative to top directory of the project). sub filecleanup { @@ -4583,7 +4583,7 @@ sub getmeta # the numerical value of the corresponding byte plus # 100. # - "plus 100" avoids "0"s, and also reduces the - # likelyhood of a collision in the case that someone someday + # likelihood of a collision in the case that someone someday # writes an import tool that tries to preserve original # CVS revision numbers, and the original CVS data had done # lots of branches off of branches and other strangeness to diff --git a/git-difftool.perl b/git-difftool.perl index 0a90de4146..67802922cc 100755 --- a/git-difftool.perl +++ b/git-difftool.perl @@ -13,9 +13,9 @@ use 5.008; use strict; use warnings; +use Error qw(:try); use File::Basename qw(dirname); use File::Copy; -use File::Compare; use File::Find; use File::stat; use File::Path qw(mkpath rmtree); @@ -83,6 +83,52 @@ sub exit_cleanup exit($status | ($status >> 8)); } +sub use_wt_file +{ + my ($repo, $workdir, $file, $sha1, $symlinks) = @_; + my $null_sha1 = '0' x 40; + + if ($sha1 ne $null_sha1 and not $symlinks) { + return 0; + } + + my $wt_sha1 = $repo->command_oneline('hash-object', "$workdir/$file"); + my $use = ($sha1 eq $null_sha1) || ($sha1 eq $wt_sha1); + return ($use, $wt_sha1); +} + +sub changed_files +{ + my ($repo_path, $index, $worktree) = @_; + $ENV{GIT_INDEX_FILE} = $index; + $ENV{GIT_WORK_TREE} = $worktree; + my $must_unset_git_dir = 0; + if (not defined($ENV{GIT_DIR})) { + $must_unset_git_dir = 1; + $ENV{GIT_DIR} = $repo_path; + } + + my @refreshargs = qw/update-index --really-refresh -q --unmerged/; + my @gitargs = qw/diff-files --name-only -z/; + try { + Git::command_oneline(@refreshargs); + } catch Git::Error::Command with {}; + + my $line = Git::command_oneline(@gitargs); + my @files; + if (defined $line) { + @files = split('\0', $line); + } else { + @files = (); + } + + delete($ENV{GIT_INDEX_FILE}); + delete($ENV{GIT_WORK_TREE}); + delete($ENV{GIT_DIR}) if ($must_unset_git_dir); + + return map { $_ => 1 } @files; +} + sub setup_dir_diff { my ($repo, $workdir, $symlinks) = @_; @@ -106,6 +152,7 @@ sub setup_dir_diff my $null_sha1 = '0' x 40; my $lindex = ''; my $rindex = ''; + my $wtindex = ''; my %submodule; my %symlink; my @working_tree = (); @@ -159,10 +206,14 @@ EOF } if ($rmode ne $null_mode) { - if ($rsha1 ne $null_sha1) { - $rindex .= "$rmode $rsha1\t$dst_path\0"; + my ($use, $wt_sha1) = use_wt_file($repo, $workdir, + $dst_path, $rsha1, + $symlinks); + if ($use) { + push @working_tree, $dst_path; + $wtindex .= "$rmode $wt_sha1\t$dst_path\0"; } else { - push(@working_tree, $dst_path); + $rindex .= "$rmode $rsha1\t$dst_path\0"; } } } @@ -203,13 +254,21 @@ EOF $rc = system('git', 'checkout-index', '--all', "--prefix=$rdir/"); exit_cleanup($tmpdir, $rc) if $rc != 0; + $ENV{GIT_INDEX_FILE} = "$tmpdir/wtindex"; + ($inpipe, $ctx) = + $repo->command_input_pipe(qw(update-index --info-only -z --index-info)); + print($inpipe $wtindex); + $repo->command_close_pipe($inpipe, $ctx); + # If $GIT_DIR was explicitly set just for the update/checkout # commands, then it should be unset before continuing. delete($ENV{GIT_DIR}) if ($must_unset_git_dir); delete($ENV{GIT_INDEX_FILE}); # Changes in the working tree need special treatment since they are - # not part of the index + # not part of the index. Remove any trailing slash from $workdir + # before starting to avoid double slashes in symlink targets. + $workdir =~ s|/$||; for my $file (@working_tree) { my $dir = dirname($file); unless (-d "$rdir/$dir") { @@ -336,7 +395,7 @@ sub main } if ($opts{gui}) { my $guitool = Git::config('diff.guitool'); - if (length($guitool) > 0) { + if (defined($guitool) && length($guitool) > 0) { $ENV{GIT_DIFF_TOOL} = $guitool; } } @@ -373,19 +432,34 @@ sub dir_diff # should be copied back to the working tree. # Do not copy back files when symlinks are used and the # external tool did not replace the original link with a file. + # + # These hashes are loaded lazily since they aren't needed + # in the common case of --symlinks and the difftool updating + # files through the symlink. + my %wt_modified; + my %tmp_modified; + my $indices_loaded = 0; + for my $file (@worktree) { next if $symlinks && -l "$b/$file"; next if ! -f "$b/$file"; - my $diff = compare("$b/$file", "$workdir/$file"); - if ($diff == 0) { - next; - } elsif ($diff == -1) { - my $errmsg = "warning: Could not compare "; - $errmsg += "'$b/$file' with '$workdir/$file'\n"; + if (!$indices_loaded) { + %wt_modified = changed_files($repo->repo_path(), + "$tmpdir/wtindex", "$workdir"); + %tmp_modified = changed_files($repo->repo_path(), + "$tmpdir/wtindex", "$b"); + $indices_loaded = 1; + } + + if (exists $wt_modified{$file} and exists $tmp_modified{$file}) { + my $errmsg = "warning: Both files modified: "; + $errmsg .= "'$workdir/$file' and '$b/$file'.\n"; + $errmsg .= "warning: Working tree file has been left.\n"; + $errmsg .= "warning:\n"; warn $errmsg; $error = 1; - } elsif ($diff == 1) { + } elsif (exists $tmp_modified{$file}) { my $mode = stat("$b/$file")->mode; copy("$b/$file", "$workdir/$file") or exit_cleanup($tmpdir, 1); diff --git a/git-filter-branch.sh b/git-filter-branch.sh index 53142492af..ac2a005fdb 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -199,6 +199,7 @@ t) test -d "$tempdir" && die "$tempdir already exists, please remove it" esac +orig_dir=$(pwd) mkdir -p "$tempdir/t" && tempdir="$(cd "$tempdir"; pwd)" && cd "$tempdir/t" && @@ -206,7 +207,7 @@ workdir="$(pwd)" || die "" # Remove tempdir on exit -trap 'cd ../..; rm -rf "$tempdir"' 0 +trap 'cd "$orig_dir"; rm -rf "$tempdir"' 0 ORIG_GIT_DIR="$GIT_DIR" ORIG_GIT_WORK_TREE="$GIT_WORK_TREE" @@ -469,7 +470,7 @@ if [ "$filter_tag_name" ]; then done fi -cd ../.. +cd "$orig_dir" rm -rf "$tempdir" trap - 0 diff --git a/git-merge-one-file.sh b/git-merge-one-file.sh index 3373c040d4..07dfeb8df4 100755 --- a/git-merge-one-file.sh +++ b/git-merge-one-file.sh @@ -27,7 +27,7 @@ SUBDIRECTORY_OK=Yes cd_to_toplevel require_work_tree -if ! test "$#" -eq 7 +if test $# != 7 then echo "$LONG_USAGE" exit 1 @@ -38,7 +38,8 @@ case "${1:-.}${2:-.}${3:-.}" in # Deleted in both or deleted in one and unchanged in the other # "$1.." | "$1.$1" | "$1$1.") - if [ "$2" ]; then + if test -n "$2" + then echo "Removing $4" else # read-tree checked that index matches HEAD already, @@ -48,7 +49,8 @@ case "${1:-.}${2:-.}${3:-.}" in # we do not have it in the index, though. exec git update-index --remove -- "$4" fi - if test -f "$4"; then + if test -f "$4" + then rm -f -- "$4" && rmdir -p "$(expr "z$4" : 'z\(.*\)/')" 2>/dev/null || : fi && @@ -67,7 +69,7 @@ case "${1:-.}${2:-.}${3:-.}" in echo "Adding $4" if test -f "$4" then - echo "ERROR: untracked $4 is overwritten by the merge." + echo "ERROR: untracked $4 is overwritten by the merge." >&2 exit 1 fi git update-index --add --cacheinfo "$7" "$3" "$4" && @@ -78,9 +80,10 @@ case "${1:-.}${2:-.}${3:-.}" in # Added in both, identically (check for same permissions). # ".$3$2") - if [ "$6" != "$7" ]; then - echo "ERROR: File $4 added identically in both branches," - echo "ERROR: but permissions conflict $6->$7." + if test "$6" != "$7" + then + echo "ERROR: File $4 added identically in both branches," >&2 + echo "ERROR: but permissions conflict $6->$7." >&2 exit 1 fi echo "Adding $4" @@ -95,44 +98,36 @@ case "${1:-.}${2:-.}${3:-.}" in case ",$6,$7," in *,120000,*) - echo "ERROR: $4: Not merging symbolic link changes." + echo "ERROR: $4: Not merging symbolic link changes." >&2 exit 1 ;; *,160000,*) - echo "ERROR: $4: Not merging conflicting submodule changes." + echo "ERROR: $4: Not merging conflicting submodule changes." >&2 exit 1 ;; esac - src2=`git-unpack-file $3` + src1=$(git-unpack-file $2) + src2=$(git-unpack-file $3) case "$1" in '') echo "Added $4 in both, but differently." - # This extracts OUR file in $orig, and uses git apply to - # remove lines that are unique to ours. - orig=`git-unpack-file $2` - sz0=`wc -c <"$orig"` - @@DIFF@@ -u -La/$orig -Lb/$orig $orig $src2 | git apply --no-add - sz1=`wc -c <"$orig"` - - # If we do not have enough common material, it is not - # worth trying two-file merge using common subsections. - expr $sz0 \< $sz1 \* 2 >/dev/null || : >$orig + orig=$(git-unpack-file $2) + create_virtual_base "$orig" "$src2" ;; *) echo "Auto-merging $4" - orig=`git-unpack-file $1` + orig=$(git-unpack-file $1) ;; esac - # Be careful for funny filename such as "-L" in "$4", which - # would confuse "merge" greatly. - src1=`git-unpack-file $2` git merge-file "$src1" "$orig" "$src2" ret=$? msg= - if [ $ret -ne 0 ]; then + if test $ret != 0 || test -z "$1" + then msg='content conflict' + ret=1 fi # Create the working tree file, using "our tree" version from the @@ -140,26 +135,26 @@ case "${1:-.}${2:-.}${3:-.}" in git checkout-index -f --stage=2 -- "$4" && cat "$src1" >"$4" || exit 1 rm -f -- "$orig" "$src1" "$src2" - if [ "$6" != "$7" ]; then - if [ -n "$msg" ]; then + if test "$6" != "$7" + then + if test -n "$msg" + then msg="$msg, " fi msg="${msg}permissions conflict: $5->$6,$7" ret=1 fi - if [ "$1" = '' ]; then - ret=1 - fi - if [ $ret -ne 0 ]; then - echo "ERROR: $msg in $4" + if test $ret != 0 + then + echo "ERROR: $msg in $4" >&2 exit 1 fi exec git update-index -- "$4" ;; *) - echo "ERROR: $4: Not handling case $1 -> $2 -> $3" + echo "ERROR: $4: Not handling case $1 -> $2 -> $3" >&2 ;; esac exit 1 @@ -79,12 +79,27 @@ def p4_build_cmd(cmd): real_cmd += cmd return real_cmd -def chdir(dir): - # P4 uses the PWD environment variable rather than getcwd(). Since we're - # not using the shell, we have to set it ourselves. This path could - # be relative, so go there first, then figure out where we ended up. - os.chdir(dir) - os.environ['PWD'] = os.getcwd() +def chdir(path, is_client_path=False): + """Do chdir to the given path, and set the PWD environment + variable for use by P4. It does not look at getcwd() output. + Since we're not using the shell, it is necessary to set the + PWD environment variable explicitly. + + Normally, expand the path to force it to be absolute. This + addresses the use of relative path names inside P4 settings, + e.g. P4CONFIG=.p4config. P4 does not simply open the filename + as given; it looks for .p4config using PWD. + + If is_client_path, the path was handed to us directly by p4, + and may be a symbolic link. Do not call os.getcwd() in this + case, because it will cause p4 to think that PWD is not inside + the client path. + """ + + os.chdir(path) + if not is_client_path: + path = os.getcwd() + os.environ['PWD'] = path def die(msg): if verbose: @@ -1624,7 +1639,7 @@ class P4Submit(Command, P4UserMap): new_client_dir = True os.makedirs(self.clientPath) - chdir(self.clientPath) + chdir(self.clientPath, is_client_path=True) if self.dry_run: print "Would synchronize p4 checkout in %s" % self.clientPath else: diff --git a/git-pull.sh b/git-pull.sh index 266e682f6c..638aabb7b3 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -39,7 +39,7 @@ test -z "$(git ls-files -u)" || die_conflict test -f "$GIT_DIR/MERGE_HEAD" && die_merge strategy_args= diffstat= no_commit= squash= no_ff= ff_only= -log_arg= verbosity= progress= recurse_submodules= +log_arg= verbosity= progress= recurse_submodules= verify_signatures= merge_args= edit= curr_branch=$(git symbolic-ref -q HEAD) curr_branch_short="${curr_branch#refs/heads/}" @@ -125,6 +125,12 @@ do --no-recurse-submodules) recurse_submodules=--no-recurse-submodules ;; + --verify-signatures) + verify_signatures=--verify-signatures + ;; + --no-verify-signatures) + verify_signatures=--no-verify-signatures + ;; --d|--dr|--dry|--dry-|--dry-r|--dry-ru|--dry-run) dry_run=--dry-run ;; @@ -279,11 +285,11 @@ fi merge_name=$(git fmt-merge-msg $log_arg <"$GIT_DIR/FETCH_HEAD") || exit case "$rebase" in true) - eval="git-rebase $diffstat $strategy_args $merge_args" + eval="git-rebase $diffstat $strategy_args $merge_args $verbosity" eval="$eval --onto $merge_head ${oldremoteref:-$merge_head}" ;; *) - eval="git-merge $diffstat $no_commit $edit $squash $no_ff $ff_only" + eval="git-merge $diffstat $no_commit $verify_signatures $edit $squash $no_ff $ff_only" eval="$eval $log_arg $strategy_args $merge_args $verbosity $progress" eval="$eval \"\$merge_name\" HEAD $merge_head" ;; diff --git a/git-quiltimport.sh b/git-quiltimport.sh index 9a6ba2b987..8e17525dd8 100755 --- a/git-quiltimport.sh +++ b/git-quiltimport.sh @@ -59,7 +59,7 @@ tmp_patch="$tmp_dir/patch" tmp_info="$tmp_dir/info" -# Find the intial commit +# Find the initial commit commit=$(git rev-parse HEAD) mkdir $tmp_dir || exit 2 diff --git a/git-rebase--am.sh b/git-rebase--am.sh index 97f31dc7af..f84854f09a 100644 --- a/git-rebase--am.sh +++ b/git-rebase--am.sh @@ -31,8 +31,8 @@ else rm -f "$GIT_DIR/rebased-patches" git format-patch -k --stdout --full-index --ignore-if-in-upstream \ - --src-prefix=a/ --dst-prefix=b/ \ - --no-renames $root_flag "$revisions" >"$GIT_DIR/rebased-patches" + --src-prefix=a/ --dst-prefix=b/ --no-renames --no-cover-letter \ + $root_flag "$revisions" >"$GIT_DIR/rebased-patches" ret=$? if test 0 != $ret diff --git a/git-send-email.perl b/git-send-email.perl index be809e5b59..bd13cc812d 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -54,7 +54,7 @@ git send-email [options] <file | directory | rev-list options > --[no-]bcc <str> * Email Bcc: --subject <str> * Email "Subject:" --in-reply-to <str> * Email "In-Reply-To:" - --annotate * Review each patch that will be sent in an editor. + --[no-]annotate * Review each patch that will be sent in an editor. --compose * Open an editor for introduction. --compose-encoding <str> * Encoding to assume for introduction. --8bit-encoding <str> * Encoding to assume 8bit mails if undeclared @@ -212,7 +212,8 @@ my %config_bool_settings = ( "signedoffbycc" => [\$signed_off_by_cc, undef], "signedoffcc" => [\$signed_off_by_cc, undef], # Deprecated "validate" => [\$validate, 1], - "multiedit" => [\$multiedit, undef] + "multiedit" => [\$multiedit, undef], + "annotate" => [\$annotate, undef] ); my %config_settings = ( @@ -304,7 +305,7 @@ my $rc = GetOptions("h" => \$help, "smtp-debug:i" => \$debug_net_smtp, "smtp-domain:s" => \$smtp_domain, "identity=s" => \$identity, - "annotate" => \$annotate, + "annotate!" => \$annotate, "compose" => \$compose, "quiet" => \$quiet, "cc-cmd=s" => \$cc_cmd, @@ -512,8 +513,9 @@ if (@alias_files and $aliasfiletype and defined $parse_alias{$aliasfiletype}) { ($sender) = expand_aliases($sender) if defined $sender; -# returns 1 if the conflict must be solved using it as a format-patch argument -sub check_file_rev_conflict($) { +# is_format_patch_arg($f) returns 0 if $f names a patch, or 1 if +# $f is a revision list specification to be passed to format-patch. +sub is_format_patch_arg { return unless $repo; my $f = shift; try { @@ -529,6 +531,7 @@ to produce patches for. Please disambiguate by... * Giving --format-patch option if you mean a range. EOF } catch Git::Error::Command with { + # Not a valid revision. Treat it as a filename. return 0; } } @@ -540,14 +543,14 @@ while (defined(my $f = shift @ARGV)) { if ($f eq "--") { push @rev_list_opts, "--", @ARGV; @ARGV = (); - } elsif (-d $f and !check_file_rev_conflict($f)) { + } elsif (-d $f and !is_format_patch_arg($f)) { opendir my $dh, $f or die "Failed to opendir $f: $!"; push @files, grep { -f $_ } map { catfile($f, $_) } sort readdir $dh; closedir $dh; - } elsif ((-f $f or -p $f) and !check_file_rev_conflict($f)) { + } elsif ((-f $f or -p $f) and !is_format_patch_arg($f)) { push @files, $f; } else { push @rev_list_opts, $f; @@ -711,7 +714,7 @@ sub ask { } } } - return undef; + return; } my %broken_encoding; @@ -833,7 +836,7 @@ sub extract_valid_address { # less robust/correct than the monster regexp in Email::Valid, # but still does a 99% job, and one less dependency return $1 if $address =~ /($local_part_regexp\@$domain_regexp)/; - return undef; + return; } sub extract_valid_address_or_die { @@ -1045,6 +1048,47 @@ sub maildomain { return maildomain_net() || maildomain_mta() || 'localhost.localdomain'; } +sub smtp_host_string { + if (defined $smtp_server_port) { + return "$smtp_server:$smtp_server_port"; + } else { + return $smtp_server; + } +} + +# Returns 1 if authentication succeeded or was not necessary +# (smtp_user was not specified), and 0 otherwise. + +sub smtp_auth_maybe { + if (!defined $smtp_authuser || $auth) { + return 1; + } + + # Workaround AUTH PLAIN/LOGIN interaction defect + # with Authen::SASL::Cyrus + eval { + require Authen::SASL; + Authen::SASL->import(qw(Perl)); + }; + + # TODO: Authentication may fail not because credentials were + # invalid but due to other reasons, in which we should not + # reject credentials. + $auth = Git::credential({ + 'protocol' => 'smtp', + 'host' => smtp_host_string(), + 'username' => $smtp_authuser, + # if there's no password, "git credential fill" will + # give us one, otherwise it'll just pass this one. + 'password' => $smtp_authpass + }, sub { + my $cred = shift; + return !!$smtp->auth($cred->{'username'}, $cred->{'password'}); + }); + + return $auth; +} + # Returns 1 if the message was sent, and 0 otherwise. # In actuality, the whole program dies when there # is an error sending a message. @@ -1155,9 +1199,7 @@ X-Mailer: git-send-email $gitversion else { require Net::SMTP; $smtp_domain ||= maildomain(); - $smtp ||= Net::SMTP->new((defined $smtp_server_port) - ? "$smtp_server:$smtp_server_port" - : $smtp_server, + $smtp ||= Net::SMTP->new(smtp_host_string(), Hello => $smtp_domain, Debug => $debug_net_smtp); if ($smtp_encryption eq 'tls' && $smtp) { @@ -1185,31 +1227,7 @@ X-Mailer: git-send-email $gitversion defined $smtp_server_port ? " port=$smtp_server_port" : ""; } - if (defined $smtp_authuser) { - # Workaround AUTH PLAIN/LOGIN interaction defect - # with Authen::SASL::Cyrus - eval { - require Authen::SASL; - Authen::SASL->import(qw(Perl)); - }; - - if (!defined $smtp_authpass) { - - system "stty -echo"; - - do { - print "Password: "; - $_ = <STDIN>; - print "\n"; - } while (!defined $_); - - chomp($smtp_authpass = $_); - - system "stty echo"; - } - - $auth ||= $smtp->auth( $smtp_authuser, $smtp_authpass ) or die $smtp->message; - } + smtp_auth_maybe or die $smtp->message; $smtp->mail( $raw_from ) or die $smtp->message; $smtp->to( @recipients ) or die $smtp->message; @@ -1438,7 +1456,7 @@ sub recipients_cmd { my $sanitized_sender = sanitize_address($sender); my @addresses = (); - open my $fh, "$cmd \Q$file\E |" + open my $fh, "-|", "$cmd \Q$file\E" or die "($prefix) Could not execute '$cmd'"; while (my $address = <$fh>) { $address =~ s/^\s*//g; @@ -1484,7 +1502,7 @@ sub validate_patch { return "$.: patch contains a line longer than 998 characters"; } } - return undef; + return; } sub file_has_nonascii { diff --git a/git-sh-setup.sh b/git-sh-setup.sh index 9cfbe7f143..2f7835941e 100644 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -249,6 +249,18 @@ clear_local_git_env() { unset $(git rev-parse --local-env-vars) } +# Generate a virtual base file for a two-file merge. Uses git apply to +# remove lines from $1 that are not in $2, leaving only common lines. +create_virtual_base() { + sz0=$(wc -c <"$1") + @@DIFF@@ -u -La/"$1" -Lb/"$1" "$1" "$2" | git apply --no-add + sz1=$(wc -c <"$1") + + # If we do not have enough common material, it is not + # worth trying two-file merge using common subsections. + expr $sz0 \< $sz1 \* 2 >/dev/null || : >"$1" +} + # Platform specific tweaks to work around some commands case $(uname -s) in diff --git a/git-submodule.sh b/git-submodule.sh index b3108b8ea9..79bfaac9d4 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -8,6 +8,7 @@ dashless=$(basename "$0" | sed -e 's/-/ /') USAGE="[--quiet] add [-b <branch>] [-f|--force] [--name <name>] [--reference <repository>] [--] <repository> [<path>] or: $dashless [--quiet] status [--cached] [--recursive] [--] [<path>...] or: $dashless [--quiet] init [--] [<path>...] + or: $dashless [--quiet] deinit [-f|--force] [--] <path>... or: $dashless [--quiet] update [--init] [--remote] [-N|--no-fetch] [-f|--force] [--rebase] [--reference <repository>] [--merge] [--recursive] [--] [<path>...] or: $dashless [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...] or: $dashless [--quiet] foreach [--recursive] <command> @@ -552,6 +553,82 @@ cmd_init() } # +# Unregister submodules from .git/config and remove their work tree +# +# $@ = requested paths (use '.' to deinit all submodules) +# +cmd_deinit() +{ + # parse $args after "submodule ... deinit". + while test $# -ne 0 + do + case "$1" in + -f|--force) + force=$1 + ;; + -q|--quiet) + GIT_QUIET=1 + ;; + --) + shift + break + ;; + -*) + usage + ;; + *) + break + ;; + esac + shift + done + + if test $# = 0 + then + die "$(eval_gettext "Use '.' if you really want to deinitialize all submodules")" + fi + + module_list "$@" | + while read mode sha1 stage sm_path + do + die_if_unmatched "$mode" + name=$(module_name "$sm_path") || exit + + # Remove the submodule work tree (unless the user already did it) + if test -d "$sm_path" + then + # Protect submodules containing a .git directory + if test -d "$sm_path/.git" + then + echo >&2 "$(eval_gettext "Submodule work tree '\$sm_path' contains a .git directory")" + die "$(eval_gettext "(use 'rm -rf' if you really want to remove it including all of its history)")" + fi + + if test -z "$force" + then + git rm -qn "$sm_path" || + die "$(eval_gettext "Submodule work tree '\$sm_path' contains local modifications; use '-f' to discard them")" + fi + rm -rf "$sm_path" && + say "$(eval_gettext "Cleared directory '\$sm_path'")" || + say "$(eval_gettext "Could not remove submodule work tree '\$sm_path'")" + fi + + mkdir "$sm_path" || say "$(eval_gettext "Could not create empty submodule directory '\$sm_path'")" + + # Remove the .git/config entries (unless the user already did it) + if test -n "$(git config --get-regexp submodule."$name\.")" + then + # Remove the whole section so we have a clean state when + # the user later decides to init this submodule again + url=$(git config submodule."$name".url) + git config --remove-section submodule."$name" 2>/dev/null && + say "$(eval_gettext "Submodule '\$name' (\$url) unregistered for path '\$sm_path'")" + fi + done +} + +# # Update each submodule path to correct revision, using clone and checkout as needed # # $@ = requested paths (default to all) @@ -1167,7 +1244,7 @@ cmd_sync() while test $# != 0 && test -z "$command" do case "$1" in - add | foreach | init | update | status | summary | sync) + add | foreach | init | deinit | update | status | summary | sync) command=$1 ;; -q|--quiet) diff --git a/git-web--browse.sh b/git-web--browse.sh index 1e827264b4..9f446798d4 100755 --- a/git-web--browse.sh +++ b/git-web--browse.sh @@ -119,8 +119,8 @@ if test -z "$browser" ; then browser_candidates="w3m elinks links lynx" fi # SECURITYSESSIONID indicates an OS X GUI login session - if test -n "$SECURITYSESSIONID" \ - -o "$TERM_PROGRAM" = "Apple_Terminal" ; then + if test -n "$SECURITYSESSIONID" || test -n "$TERM_PROGRAM" + then browser_candidates="open $browser_candidates" fi # /bin/start indicates MinGW @@ -13,7 +13,9 @@ const char git_usage_string[] = " <command> [<args>]"; const char git_more_info_string[] = - N_("See 'git help <command>' for more information on a specific command."); + N_("'git help -a' and 'git help -g' lists available subcommands and some\n" + "concept guides. See 'git help <command>' or 'git help <concept>'\n" + "to read about a specific subcommand or concept."); static struct startup_info git_startup_info; static int use_pager = -1; diff --git a/gitweb/INSTALL b/gitweb/INSTALL index 6d45406797..08f3dda02d 100644 --- a/gitweb/INSTALL +++ b/gitweb/INSTALL @@ -244,7 +244,7 @@ for gitweb (in gitweb/README), and gitweb.conf(5) manpage. through the GITWEB_CONFIG_SYSTEM environment variable. Note that if per-instance configuration file exists, then system-wide - configuration is _not used at all_. This is quite untypical and suprising + configuration is _not used at all_. This is quite untypical and surprising behavior. On the other hand changing current behavior would break backwards compatibility and can lead to unexpected changes in gitweb behavior. Therefore gitweb also looks for common system-wide configuration file, diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 1309196d27..80950c018d 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -683,7 +683,7 @@ sub evaluate_gitweb_config { our $GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++"; our $GITWEB_CONFIG_COMMON = $ENV{'GITWEB_CONFIG_COMMON'} || "++GITWEB_CONFIG_COMMON++"; - # Protect agains duplications of file names, to not read config twice. + # Protect against duplications of file names, to not read config twice. # Only one of $GITWEB_CONFIG and $GITWEB_CONFIG_SYSTEM is used, so # there possibility of duplication of filename there doesn't matter. $GITWEB_CONFIG = "" if ($GITWEB_CONFIG eq $GITWEB_CONFIG_COMMON); @@ -1136,7 +1136,7 @@ sub handle_errors_html { # to avoid infinite loop where error occurs in die_error, # change handler to default handler, disabling handle_errors_html - set_message("Error occured when inside die_error:\n$msg"); + set_message("Error occurred when inside die_error:\n$msg"); # you cannot jump out of die_error when called as error handler; # the subroutine set via CGI::Carp::set_message is called _after_ @@ -7485,7 +7485,7 @@ sub git_object { system(git_cmd(), "cat-file", '-e', $hash_base) == 0 or die_error(404, "Base object does not exist"); - # here errors should not hapen + # here errors should not happen open my $fd, "-|", git_cmd(), "ls-tree", $hash_base, "--", $file_name or die_error(500, "Open git-ls-tree failed"); my $line = <$fd>; diff --git a/gpg-interface.h b/gpg-interface.h index cf99021842..a85cb5bc97 100644 --- a/gpg-interface.h +++ b/gpg-interface.h @@ -1,6 +1,18 @@ #ifndef GPG_INTERFACE_H #define GPG_INTERFACE_H +struct signature_check { + char *gpg_output; + char *gpg_status; + char result; /* 0 (not checked), + * N (checked but no further result), + * U (untrusted good), + * G (good) + * B (bad) */ + char *signer; + char *key; +}; + extern int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key); extern int verify_signed_buffer(const char *payload, size_t payload_size, const char *signature, size_t signature_size, struct strbuf *gpg_output, struct strbuf *gpg_status); extern int git_gpg_config(const char *, const char *, void *); @@ -625,7 +625,8 @@ static struct grep_expr *prep_header_patterns(struct grep_opt *opt) for (p = opt->header_list; p; p = p->next) { if (p->token != GREP_PATTERN_HEAD) die("bug: a non-header pattern in grep header list."); - if (p->field < 0 || GREP_HEADER_FIELD_MAX <= p->field) + if (p->field < GREP_HEADER_FIELD_MIN || + GREP_HEADER_FIELD_MAX <= p->field) die("bug: unknown header field %d", p->field); compile_regexp(p, opt); } @@ -28,7 +28,8 @@ enum grep_context { }; enum grep_header_field { - GREP_HEADER_AUTHOR = 0, + GREP_HEADER_FIELD_MIN = 0, + GREP_HEADER_AUTHOR = GREP_HEADER_FIELD_MIN, GREP_HEADER_COMMITTER, GREP_HEADER_REFLOG, @@ -40,4 +40,11 @@ static inline void init_hash(struct hash_table *table) table->array = NULL; } +static inline void preallocate_hash(struct hash_table *table, unsigned int elts) +{ + assert(table->size == 0 && table->nr == 0 && table->array == NULL); + table->size = elts * 2; + table->array = xcalloc(sizeof(struct hash_table_entry), table->size); +} + #endif @@ -397,6 +397,10 @@ const char *help_unknown_cmd(const char *cmd) int cmd_version(int argc, const char **argv, const char *prefix) { + /* + * The format of this string should be kept stable for compatibility + * with external projects that rely on the output of "git version". + */ printf("git version %s\n", git_version_string); return 0; } diff --git a/http-backend.c b/http-backend.c index f50e77fb28..6b85ffac27 100644 --- a/http-backend.c +++ b/http-backend.c @@ -70,7 +70,7 @@ static void format_write(int fd, const char *fmt, ...) if (n >= sizeof(buffer)) die("protocol error: impossibly long line"); - safe_write(fd, buffer, n); + write_or_die(fd, buffer, n); } static void http_status(unsigned code, const char *msg) @@ -111,7 +111,7 @@ static void hdr_cache_forever(void) static void end_headers(void) { - safe_write(1, "\r\n", 2); + write_or_die(1, "\r\n", 2); } __attribute__((format (printf, 1, 2))) @@ -157,7 +157,7 @@ static void send_strbuf(const char *type, struct strbuf *buf) hdr_int(content_length, buf->len); hdr_str(content_type, type); end_headers(); - safe_write(1, buf->buf, buf->len); + write_or_die(1, buf->buf, buf->len); } static void send_local_file(const char *the_type, const char *name) @@ -185,7 +185,7 @@ static void send_local_file(const char *the_type, const char *name) die_errno("Cannot read '%s'", p); if (!n) break; - safe_write(1, buf, n); + write_or_die(1, buf, n); } close(fd); free(buf); @@ -361,17 +361,19 @@ static void run_service(const char **argv) static int show_text_ref(const char *name, const unsigned char *sha1, int flag, void *cb_data) { + const char *name_nons = strip_namespace(name); struct strbuf *buf = cb_data; struct object *o = parse_object(sha1); if (!o) return 0; - strbuf_addf(buf, "%s\t%s\n", sha1_to_hex(sha1), name); + strbuf_addf(buf, "%s\t%s\n", sha1_to_hex(sha1), name_nons); if (o->type == OBJ_TAG) { o = deref_tag(o, name, 0); if (!o) return 0; - strbuf_addf(buf, "%s\t%s^{}\n", sha1_to_hex(o->sha1), name); + strbuf_addf(buf, "%s\t%s^{}\n", sha1_to_hex(o->sha1), + name_nons); } return 0; } @@ -402,12 +404,40 @@ static void get_info_refs(char *arg) } else { select_getanyfile(); - for_each_ref(show_text_ref, &buf); + for_each_namespaced_ref(show_text_ref, &buf); send_strbuf("text/plain", &buf); } strbuf_release(&buf); } +static int show_head_ref(const char *name, const unsigned char *sha1, + int flag, void *cb_data) +{ + struct strbuf *buf = cb_data; + + if (flag & REF_ISSYMREF) { + unsigned char sha1[20]; + const char *target = resolve_ref_unsafe(name, sha1, 1, NULL); + const char *target_nons = strip_namespace(target); + + strbuf_addf(buf, "ref: %s\n", target_nons); + } else { + strbuf_addf(buf, "%s\n", sha1_to_hex(sha1)); + } + + return 0; +} + +static void get_head(char *arg) +{ + struct strbuf buf = STRBUF_INIT; + + select_getanyfile(); + head_ref_namespaced(show_head_ref, &buf); + send_strbuf("text/plain", &buf); + strbuf_release(&buf); +} + static void get_info_packs(char *arg) { size_t objdirlen = strlen(get_object_directory()); @@ -520,7 +550,7 @@ static struct service_cmd { const char *pattern; void (*imp)(char *); } services[] = { - {"GET", "/HEAD$", get_text_file}, + {"GET", "/HEAD$", get_head}, {"GET", "/info/refs$", get_info_refs}, {"GET", "/objects/info/alternates$", get_text_file}, {"GET", "/objects/info/http-alternates$", get_text_file}, diff --git a/http-push.c b/http-push.c index bd66f6ab6e..395a8cfc10 100644 --- a/http-push.c +++ b/http-push.c @@ -1551,7 +1551,7 @@ static int remote_exists(const char *path) ret = 0; break; case HTTP_ERROR: - http_error(url, HTTP_ERROR); + error("unable to access '%s': %s", url, curl_errorstr); default: ret = -1; } @@ -5,6 +5,7 @@ #include "url.h" #include "credential.h" #include "version.h" +#include "pkt-line.h" int active_requests; int http_is_verbose; @@ -30,6 +31,7 @@ static CURL *curl_default; char curl_errorstr[CURL_ERROR_SIZE]; static int curl_ssl_verify = -1; +static int curl_ssl_try; static const char *ssl_cert; #if LIBCURL_VERSION_NUM >= 0x070903 static const char *ssl_key; @@ -162,6 +164,10 @@ static int http_options(const char *var, const char *value, void *cb) ssl_cert_password_required = 1; return 0; } + if (!strcmp("http.ssltry", var)) { + curl_ssl_try = git_config_bool(var, value); + return 0; + } if (!strcmp("http.minsessions", var)) { min_curl_sessions = git_config_int(var, value); #ifndef USE_CURL_MULTI @@ -281,7 +287,6 @@ static CURL *get_curl_handle(void) #endif if (ssl_cainfo != NULL) curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo); - curl_easy_setopt(result, CURLOPT_FAILONERROR, 1); if (curl_low_speed_limit > 0 && curl_low_speed_time > 0) { curl_easy_setopt(result, CURLOPT_LOW_SPEED_LIMIT, @@ -306,6 +311,11 @@ static CURL *get_curl_handle(void) if (curl_ftp_no_epsv) curl_easy_setopt(result, CURLOPT_FTP_USE_EPSV, 0); +#ifdef CURLOPT_USE_SSL + if (curl_ssl_try) + curl_easy_setopt(result, CURLOPT_USE_SSL, CURLUSESSL_TRY); +#endif + if (curl_http_proxy) { curl_easy_setopt(result, CURLOPT_PROXY, curl_http_proxy); curl_easy_setopt(result, CURLOPT_PROXYAUTH, CURLAUTH_ANY); @@ -505,6 +515,7 @@ struct active_request_slot *get_active_slot(void) curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, NULL); curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 0); curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); + curl_easy_setopt(slot->curl, CURLOPT_FAILONERROR, 1); if (http_auth.password) init_curl_http_auth(slot->curl); @@ -760,6 +771,25 @@ char *get_remote_object_url(const char *url, const char *hex, int handle_curl_result(struct slot_results *results) { + /* + * If we see a failing http code with CURLE_OK, we have turned off + * FAILONERROR (to keep the server's custom error response), and should + * translate the code into failure here. + */ + if (results->curl_result == CURLE_OK && + results->http_code >= 400) { + results->curl_result = CURLE_HTTP_RETURNED_ERROR; + /* + * Normally curl will already have put the "reason phrase" + * from the server into curl_errorstr; unfortunately without + * FAILONERROR it is lost, so we can give only the numeric + * status code. + */ + snprintf(curl_errorstr, sizeof(curl_errorstr), + "The requested URL returned error: %ld", + results->http_code); + } + if (results->curl_result == CURLE_OK) { credential_approve(&http_auth); return HTTP_OK; @@ -824,6 +854,8 @@ static int http_request(const char *url, struct strbuf *type, strbuf_addstr(&buf, "Pragma:"); if (options & HTTP_NO_CACHE) strbuf_addstr(&buf, " no-cache"); + if (options & HTTP_KEEP_ERROR) + curl_easy_setopt(slot->curl, CURLOPT_FAILONERROR, 0); headers = curl_slist_append(headers, buf.buf); @@ -835,7 +867,8 @@ static int http_request(const char *url, struct strbuf *type, run_active_slot(slot); ret = handle_curl_result(&results); } else { - error("Unable to start HTTP request for %s", url); + snprintf(curl_errorstr, sizeof(curl_errorstr), + "failed to start HTTP request"); ret = HTTP_START_FAILED; } @@ -861,6 +894,22 @@ static int http_request_reauth(const char *url, int ret = http_request(url, type, result, target, options); if (ret != HTTP_REAUTH) return ret; + + /* + * If we are using KEEP_ERROR, the previous request may have + * put cruft into our output stream; we should clear it out before + * making our next request. We only know how to do this for + * the strbuf case, but that is enough to satisfy current callers. + */ + if (options & HTTP_KEEP_ERROR) { + switch (target) { + case HTTP_REQUEST_STRBUF: + strbuf_reset(result); + break; + default: + die("BUG: HTTP_KEEP_ERROR is only supported with strbufs"); + } + } return http_request(url, type, result, target, options); } @@ -902,15 +951,6 @@ cleanup: return ret; } -int http_error(const char *url, int ret) -{ - /* http_request has already handled HTTP_START_FAILED. */ - if (ret != HTTP_START_FAILED) - error("%s while accessing %s", curl_errorstr, url); - - return ret; -} - int http_fetch_ref(const char *base, struct ref *ref) { char *url; @@ -42,6 +42,15 @@ #define NO_CURL_IOCTL #endif +/* + * CURLOPT_USE_SSL was known as CURLOPT_FTP_SSL up to 7.16.4, + * and the constants were known as CURLFTPSSL_* +*/ +#if !defined(CURLOPT_USE_SSL) && defined(CURLOPT_FTP_SSL) +#define CURLOPT_USE_SSL CURLOPT_FTP_SSL +#define CURLUSESSL_TRY CURLFTPSSL_TRY +#endif + struct slot_results { CURLcode curl_result; long http_code; @@ -118,6 +127,7 @@ extern char *get_remote_object_url(const char *url, const char *hex, /* Options for http_request_*() */ #define HTTP_NO_CACHE 1 +#define HTTP_KEEP_ERROR 2 /* Return values for http_request_*() */ #define HTTP_OK 0 @@ -134,12 +144,6 @@ extern char *get_remote_object_url(const char *url, const char *hex, */ int http_get_strbuf(const char *url, struct strbuf *content_type, struct strbuf *result, int options); -/* - * Prints an error message using error() containing url and curl_errorstr, - * and returns ret. - */ -int http_error(const char *url, int ret); - extern int http_fetch_ref(const char *base, struct ref *ref); /* Helpers for fetching packs */ @@ -26,7 +26,7 @@ The author may be reached (Email) at the address mike@ai.mit.edu, or (US mail) as Mike Haertel c/o Free Software Foundation. */ -/* The algorithm implemented by these routines bears a startling resemblence +/* The algorithm implemented by these routines bears a startling resemblance to one discovered by Beate Commentz-Walter, although it is not identical. See "A String Matching Algorithm Fast on the Average," Technical Report, IBM-Germany, Scientific Center Heidelberg, Tiergartenstrasse 15, D-6900 @@ -435,7 +435,7 @@ kwsprep (kwset_t kws) /* Update the delta table for the descendents of this node. */ treedelta(curr->links, curr->depth, delta); - /* Compute the failure function for the decendents of this node. */ + /* Compute the failure function for the descendants of this node. */ treefails(curr->links, curr->fail, kwset->trie); /* Update the shifts at each node in the current node's chain diff --git a/log-tree.c b/log-tree.c index 3d88823871..7cc7d598e7 100644 --- a/log-tree.c +++ b/log-tree.c @@ -9,6 +9,7 @@ #include "string-list.h" #include "color.h" #include "gpg-interface.h" +#include "sequencer.h" struct decoration name_decoration = { "object names" }; @@ -206,89 +207,6 @@ void show_decorations(struct rev_info *opt, struct commit *commit) putchar(')'); } -/* - * Search for "^[-A-Za-z]+: [^@]+@" pattern. It usually matches - * Signed-off-by: and Acked-by: lines. - */ -static int detect_any_signoff(char *letter, int size) -{ - char *cp; - int seen_colon = 0; - int seen_at = 0; - int seen_name = 0; - int seen_head = 0; - - cp = letter + size; - while (letter <= --cp && *cp == '\n') - continue; - - while (letter <= cp) { - char ch = *cp--; - if (ch == '\n') - break; - - if (!seen_at) { - if (ch == '@') - seen_at = 1; - continue; - } - if (!seen_colon) { - if (ch == '@') - return 0; - else if (ch == ':') - seen_colon = 1; - else - seen_name = 1; - continue; - } - if (('A' <= ch && ch <= 'Z') || - ('a' <= ch && ch <= 'z') || - ch == '-') { - seen_head = 1; - continue; - } - /* no empty last line doesn't match */ - return 0; - } - return seen_head && seen_name; -} - -static void append_signoff(struct strbuf *sb, const char *signoff) -{ - static const char signed_off_by[] = "Signed-off-by: "; - size_t signoff_len = strlen(signoff); - int has_signoff = 0; - char *cp; - - cp = sb->buf; - - /* First see if we already have the sign-off by the signer */ - while ((cp = strstr(cp, signed_off_by))) { - - has_signoff = 1; - - cp += strlen(signed_off_by); - if (cp + signoff_len >= sb->buf + sb->len) - break; - if (strncmp(cp, signoff, signoff_len)) - continue; - if (!isspace(cp[signoff_len])) - continue; - /* we already have him */ - return; - } - - if (!has_signoff) - has_signoff = detect_any_signoff(sb->buf, sb->len); - - if (!has_signoff) - strbuf_addch(sb, '\n'); - - strbuf_addstr(sb, signed_off_by); - strbuf_add(sb, signoff, signoff_len); - strbuf_addch(sb, '\n'); -} - static unsigned int digits_in_number(unsigned int number) { unsigned int i = 10, result = 1; @@ -669,8 +587,10 @@ void show_log(struct rev_info *opt) /* * And then the pretty-printed message itself */ - if (ctx.need_8bit_cte >= 0) - ctx.need_8bit_cte = has_non_ascii(opt->add_signoff); + if (ctx.need_8bit_cte >= 0 && opt->add_signoff) + ctx.need_8bit_cte = + has_non_ascii(fmt_name(getenv("GIT_COMMITTER_NAME"), + getenv("GIT_COMMITTER_EMAIL"))); ctx.date_mode = opt->date_mode; ctx.date_mode_explicit = opt->date_mode_explicit; ctx.abbrev = opt->diffopt.abbrev; @@ -683,7 +603,7 @@ void show_log(struct rev_info *opt) pretty_print_commit(&ctx, commit, &msgbuf); if (opt->add_signoff) - append_signoff(&msgbuf, opt->add_signoff); + append_signoff(&msgbuf, 0, APPEND_SIGNOFF_DEDUP); if ((ctx.fmt != CMIT_FMT_USERFORMAT) && ctx.notes_message && *ctx.notes_message) { @@ -789,11 +709,14 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log { int showed_log; struct commit_list *parents; - unsigned const char *sha1 = commit->object.sha1; + unsigned const char *sha1; if (!opt->diff && !DIFF_OPT_TST(&opt->diffopt, EXIT_WITH_STATUS)) return 0; + parse_commit(commit); + sha1 = commit->tree->object.sha1; + /* Root commit? */ parents = commit->parents; if (!parents) { @@ -816,7 +739,9 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log * parent, showing summary diff of the others * we merged _in_. */ - diff_tree_sha1(parents->item->object.sha1, sha1, "", &opt->diffopt); + parse_commit(parents->item); + diff_tree_sha1(parents->item->tree->object.sha1, + sha1, "", &opt->diffopt); log_tree_diff_flush(opt); return !opt->loginfo; } @@ -829,7 +754,9 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log for (;;) { struct commit *parent = parents->item; - diff_tree_sha1(parent->object.sha1, sha1, "", &opt->diffopt); + parse_commit(parent); + diff_tree_sha1(parent->tree->object.sha1, + sha1, "", &opt->diffopt); log_tree_diff_flush(opt); showed_log |= !opt->loginfo; diff --git a/match-trees.c b/match-trees.c index 26f7ed143e..2bb734d51c 100644 --- a/match-trees.c +++ b/match-trees.c @@ -47,6 +47,13 @@ static int score_matches(unsigned mode1, unsigned mode2, const char *path) return score; } +static int base_name_entries_compare(const struct name_entry *a, + const struct name_entry *b) +{ + return base_name_compare(a->path, tree_entry_len(a), a->mode, + b->path, tree_entry_len(b), b->mode); +} + /* * Inspect two trees, and give a score that tells how similar they are. */ @@ -71,54 +78,35 @@ static int score_trees(const unsigned char *hash1, const unsigned char *hash2) if (type != OBJ_TREE) die("%s is not a tree", sha1_to_hex(hash2)); init_tree_desc(&two, two_buf, size); - while (one.size | two.size) { - const unsigned char *elem1 = elem1; - const unsigned char *elem2 = elem2; - const char *path1 = path1; - const char *path2 = path2; - unsigned mode1 = mode1; - unsigned mode2 = mode2; + for (;;) { + struct name_entry e1, e2; + int got_entry_from_one = tree_entry(&one, &e1); + int got_entry_from_two = tree_entry(&two, &e2); int cmp; - if (one.size) - elem1 = tree_entry_extract(&one, &path1, &mode1); - if (two.size) - elem2 = tree_entry_extract(&two, &path2, &mode2); - - if (!one.size) { - /* two has more entries */ - score += score_missing(mode2, path2); - update_tree_entry(&two); - continue; - } - if (!two.size) { + if (got_entry_from_one && got_entry_from_two) + cmp = base_name_entries_compare(&e1, &e2); + else if (got_entry_from_one) /* two lacks this entry */ - score += score_missing(mode1, path1); - update_tree_entry(&one); - continue; - } - cmp = base_name_compare(path1, strlen(path1), mode1, - path2, strlen(path2), mode2); - if (cmp < 0) { + cmp = -1; + else if (got_entry_from_two) + /* two has more entries */ + cmp = 1; + else + break; + + if (cmp < 0) /* path1 does not appear in two */ - score += score_missing(mode1, path1); - update_tree_entry(&one); - continue; - } - else if (cmp > 0) { + score += score_missing(e1.mode, e1.path); + else if (cmp > 0) /* path2 does not appear in one */ - score += score_missing(mode2, path2); - update_tree_entry(&two); - continue; - } - else if (hashcmp(elem1, elem2)) + score += score_missing(e2.mode, e2.path); + else if (hashcmp(e1.sha1, e2.sha1)) /* they are different */ - score += score_differs(mode1, mode2, path1); + score += score_differs(e1.mode, e2.mode, e1.path); else /* same subtree or blob */ - score += score_matches(mode1, mode2, path1); - update_tree_entry(&one); - update_tree_entry(&two); + score += score_matches(e1.mode, e2.mode, e1.path); } free(one_buf); free(two_buf); diff --git a/mergetools/p4merge b/mergetools/p4merge index 8a36916567..5a608abf9c 100644 --- a/mergetools/p4merge +++ b/mergetools/p4merge @@ -21,8 +21,12 @@ diff_cmd () { merge_cmd () { touch "$BACKUP" - $base_present || >"$BASE" - "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" "$MERGED" + if ! $base_present + then + cp -- "$LOCAL" "$BASE" + create_virtual_base "$BASE" "$REMOTE" + fi + "$merge_tool_path" "$BASE" "$REMOTE" "$LOCAL" "$MERGED" check_unchanged } diff --git a/name-hash.c b/name-hash.c index 6d7e1980c6..617c86c537 100644 --- a/name-hash.c +++ b/name-hash.c @@ -150,6 +150,8 @@ static void lazy_init_name_hash(struct index_state *istate) if (istate->name_hash_initialized) return; + if (istate->cache_nr) + preallocate_hash(&istate->name_hash, istate->cache_nr); for (nr = 0; nr < istate->cache_nr; nr++) hash_index_entry(istate, istate->cache[nr]); istate->name_hash_initialized = 1; @@ -1,19 +1,26 @@ /* - * I'm tired of doing "vsnprintf()" etc just to open a - * file, so here's a "return static buffer with printf" - * interface for paths. - * - * It's obviously not thread-safe. Sue me. But it's quite - * useful for doing things like - * - * f = open(mkpath("%s/%s.git", base, name), O_RDONLY); - * - * which is what it's designed for. + * Utilities for paths and pathnames */ #include "cache.h" #include "strbuf.h" #include "string-list.h" +#ifndef get_st_mode_bits +/* + * The replacement lstat(2) we use on Cygwin is incomplete and + * may return wrong permission bits. Most of the time we do not care, + * but the callsites of this wrapper do care. + */ +int get_st_mode_bits(const char *path, int *mode) +{ + struct stat st; + if (lstat(path, &st) < 0) + return -1; + *mode = st.st_mode; + return 0; +} +#endif + static char bad_path[] = "/bad-path/"; static char *get_pathname(void) @@ -389,28 +396,14 @@ const char *enter_repo(const char *path, int strict) return NULL; } -int set_shared_perm(const char *path, int mode) +static int calc_shared_perm(int mode) { - struct stat st; - int tweak, shared, orig_mode; + int tweak; - if (!shared_repository) { - if (mode) - return chmod(path, mode & ~S_IFMT); - return 0; - } - if (!mode) { - if (lstat(path, &st) < 0) - return -1; - mode = st.st_mode; - orig_mode = mode; - } else - orig_mode = 0; if (shared_repository < 0) - shared = -shared_repository; + tweak = -shared_repository; else - shared = shared_repository; - tweak = shared; + tweak = shared_repository; if (!(mode & S_IWUSR)) tweak &= ~0222; @@ -422,16 +415,28 @@ int set_shared_perm(const char *path, int mode) else mode |= tweak; - if (S_ISDIR(mode)) { + return mode; +} + + +int adjust_shared_perm(const char *path) +{ + int old_mode, new_mode; + + if (!shared_repository) + return 0; + if (get_st_mode_bits(path, &old_mode) < 0) + return -1; + + new_mode = calc_shared_perm(old_mode); + if (S_ISDIR(old_mode)) { /* Copy read bits to execute bits */ - mode |= (shared & 0444) >> 2; - mode |= FORCE_DIR_SET_GID; + new_mode |= (new_mode & 0444) >> 2; + new_mode |= FORCE_DIR_SET_GID; } - if (((shared_repository < 0 - ? (orig_mode & (FORCE_DIR_SET_GID | 0777)) - : (orig_mode & mode)) != mode) && - chmod(path, (mode & ~S_IFMT)) < 0) + if (((old_mode ^ new_mode) & ~S_IFMT) && + chmod(path, (new_mode & ~S_IFMT)) < 0) return -2; return 0; } diff --git a/perl/Git.pm b/perl/Git.pm index 57a17160f9..dc48159cca 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -60,6 +60,7 @@ require Exporter; version exec_path html_path hash_object git_cmd_try remote_refs prompt get_tz_offset + credential credential_read credential_write temp_acquire temp_release temp_reset temp_path); @@ -269,13 +270,13 @@ sub command { if (not defined wantarray) { # Nothing to pepper the possible exception with. - _cmd_close($fh, $ctx); + _cmd_close($ctx, $fh); } elsif (not wantarray) { local $/; my $text = <$fh>; try { - _cmd_close($fh, $ctx); + _cmd_close($ctx, $fh); } catch Git::Error::Command with { # Pepper with the output: my $E = shift; @@ -288,7 +289,7 @@ sub command { my @lines = <$fh>; defined and chomp for @lines; try { - _cmd_close($fh, $ctx); + _cmd_close($ctx, $fh); } catch Git::Error::Command with { my $E = shift; $E->{'-outputref'} = \@lines; @@ -315,7 +316,7 @@ sub command_oneline { my $line = <$fh>; defined $line and chomp $line; try { - _cmd_close($fh, $ctx); + _cmd_close($ctx, $fh); } catch Git::Error::Command with { # Pepper with the output: my $E = shift; @@ -383,7 +384,7 @@ have more complicated structure. sub command_close_pipe { my ($self, $fh, $ctx) = _maybe_self(@_); $ctx ||= '<unknown>'; - _cmd_close($fh, $ctx); + _cmd_close($ctx, $fh); } =item command_bidi_pipe ( COMMAND [, ARGUMENTS... ] ) @@ -420,7 +421,7 @@ and it is the fourth value returned by C<command_bidi_pipe()>. The call idiom is: my ($pid, $in, $out, $ctx) = $r->command_bidi_pipe('cat-file --batch-check'); - print "000000000\n" $out; + print $out "000000000\n"; while (<$in>) { ... } $r->command_close_bidi_pipe($pid, $in, $out, $ctx); @@ -428,23 +429,26 @@ Note that you should not rely on whatever actually is in C<CTX>; currently it is simply the command name but in future the context might have more complicated structure. +C<PIPE_IN> and C<PIPE_OUT> may be C<undef> if they have been closed prior to +calling this function. This may be useful in a query-response type of +commands where caller first writes a query and later reads response, eg: + + my ($pid, $in, $out, $ctx) = $r->command_bidi_pipe('cat-file --batch-check'); + print $out "000000000\n"; + close $out; + while (<$in>) { ... } + $r->command_close_bidi_pipe($pid, $in, undef, $ctx); + +This idiom may prevent potential dead locks caused by data sent to the output +pipe not being flushed and thus not reaching the executed command. + =cut sub command_close_bidi_pipe { local $?; - my ($pid, $in, $out, $ctx) = @_; - foreach my $fh ($in, $out) { - unless (close $fh) { - if ($!) { - carp "error closing pipe: $!"; - } elsif ($? >> 8) { - throw Git::Error::Command($ctx, $? >>8); - } - } - } - + my ($self, $pid, $in, $out, $ctx) = _maybe_self(@_); + _cmd_close($ctx, (grep { defined } ($in, $out))); waitpid $pid, 0; - if ($? >> 8) { throw Git::Error::Command($ctx, $? >>8); } @@ -1020,13 +1024,163 @@ sub _close_cat_blob { } +=item credential_read( FILEHANDLE ) + +Reads credential key-value pairs from C<FILEHANDLE>. Reading stops at EOF or +when an empty line is encountered. Each line must be of the form C<key=value> +with a non-empty key. Function returns hash with all read values. Any white +space (other than new-line character) is preserved. + +=cut + +sub credential_read { + my ($self, $reader) = _maybe_self(@_); + my %credential; + while (<$reader>) { + chomp; + if ($_ eq '') { + last; + } elsif (!/^([^=]+)=(.*)$/) { + throw Error::Simple("unable to parse git credential data:\n$_"); + } + $credential{$1} = $2; + } + return %credential; +} + +=item credential_write( FILEHANDLE, CREDENTIAL_HASHREF ) + +Writes credential key-value pairs from hash referenced by +C<CREDENTIAL_HASHREF> to C<FILEHANDLE>. Keys and values cannot contain +new-lines or NUL bytes characters, and key cannot contain equal signs nor be +empty (if they do Error::Simple is thrown). Any white space is preserved. If +value for a key is C<undef>, it will be skipped. + +If C<'url'> key exists it will be written first. (All the other key-value +pairs are written in sorted order but you should not depend on that). Once +all lines are written, an empty line is printed. + +=cut + +sub credential_write { + my ($self, $writer, $credential) = _maybe_self(@_); + my ($key, $value); + + # Check if $credential is valid prior to writing anything + while (($key, $value) = each %$credential) { + if (!defined $key || !length $key) { + throw Error::Simple("credential key empty or undefined"); + } elsif ($key =~ /[=\n\0]/) { + throw Error::Simple("credential key contains invalid characters: $key"); + } elsif (defined $value && $value =~ /[\n\0]/) { + throw Error::Simple("credential value for key=$key contains invalid characters: $value"); + } + } + + for $key (sort { + # url overwrites other fields, so it must come first + return -1 if $a eq 'url'; + return 1 if $b eq 'url'; + return $a cmp $b; + } keys %$credential) { + if (defined $credential->{$key}) { + print $writer $key, '=', $credential->{$key}, "\n"; + } + } + print $writer "\n"; +} + +sub _credential_run { + my ($self, $credential, $op) = _maybe_self(@_); + my ($pid, $reader, $writer, $ctx) = command_bidi_pipe('credential', $op); + + credential_write $writer, $credential; + close $writer; + + if ($op eq "fill") { + %$credential = credential_read $reader; + } + if (<$reader>) { + throw Error::Simple("unexpected output from git credential $op response:\n$_\n"); + } + + command_close_bidi_pipe($pid, $reader, undef, $ctx); +} + +=item credential( CREDENTIAL_HASHREF [, OPERATION ] ) + +=item credential( CREDENTIAL_HASHREF, CODE ) + +Executes C<git credential> for a given set of credentials and specified +operation. In both forms C<CREDENTIAL_HASHREF> needs to be a reference to +a hash which stores credentials. Under certain conditions the hash can +change. + +In the first form, C<OPERATION> can be C<'fill'>, C<'approve'> or C<'reject'>, +and function will execute corresponding C<git credential> sub-command. If +it's omitted C<'fill'> is assumed. In case of C<'fill'> the values stored in +C<CREDENTIAL_HASHREF> will be changed to the ones returned by the C<git +credential fill> command. The usual usage would look something like: + + my %cred = ( + 'protocol' => 'https', + 'host' => 'example.com', + 'username' => 'bob' + ); + Git::credential \%cred; + if (try_to_authenticate($cred{'username'}, $cred{'password'})) { + Git::credential \%cred, 'approve'; + ... do more stuff ... + } else { + Git::credential \%cred, 'reject'; + } + +In the second form, C<CODE> needs to be a reference to a subroutine. The +function will execute C<git credential fill> to fill the provided credential +hash, then call C<CODE> with C<CREDENTIAL_HASHREF> as the sole argument. If +C<CODE>'s return value is defined, the function will execute C<git credential +approve> (if return value yields true) or C<git credential reject> (if return +value is false). If the return value is undef, nothing at all is executed; +this is useful, for example, if the credential could neither be verified nor +rejected due to an unrelated network error. The return value is the same as +what C<CODE> returns. With this form, the usage might look as follows: + + if (Git::credential { + 'protocol' => 'https', + 'host' => 'example.com', + 'username' => 'bob' + }, sub { + my $cred = shift; + return !!try_to_authenticate($cred->{'username'}, + $cred->{'password'}); + }) { + ... do more stuff ... + } + +=cut + +sub credential { + my ($self, $credential, $op_or_code) = (_maybe_self(@_), 'fill'); + + if ('CODE' eq ref $op_or_code) { + _credential_run $credential, 'fill'; + my $ret = $op_or_code->($credential); + if (defined $ret) { + _credential_run $credential, $ret ? 'approve' : 'reject'; + } + return $ret; + } else { + _credential_run $credential, $op_or_code; + } +} + { # %TEMP_* Lexical Context my (%TEMP_FILEMAP, %TEMP_FILES); =item temp_acquire ( NAME ) -Attempts to retreive the temporary file mapped to the string C<NAME>. If an +Attempts to retrieve the temporary file mapped to the string C<NAME>. If an associated temp file has not been created this session or was closed, it is created, cached, and set for autoflush and binmode. @@ -1335,12 +1489,12 @@ sub _command_common_pipe { if (not defined $pid) { throw Error::Simple("open failed: $!"); } elsif ($pid == 0) { - if (defined $opts{STDERR}) { - close STDERR; - } if ($opts{STDERR}) { open (STDERR, '>&', $opts{STDERR}) or die "dup failed: $!"; + } elsif (defined $opts{STDERR}) { + open (STDERR, '>', '/dev/null') + or die "opening /dev/null failed: $!"; } _cmd_exec($self, $cmd, @args); } @@ -1375,9 +1529,11 @@ sub _execv_git_cmd { exec('git', @_); } # Close pipe to a subprocess. sub _cmd_close { - my ($fh, $ctx) = @_; - if (not close $fh) { - if ($!) { + my $ctx = shift @_; + foreach my $fh (@_) { + if (close $fh) { + # nop + } elsif ($!) { # It's just close, no point in fatalities carp "error closing pipe: $!"; } elsif ($? >> 8) { diff --git a/perl/Git/I18N.pm b/perl/Git/I18N.pm index 40dd897191..f889fd6da9 100644 --- a/perl/Git/I18N.pm +++ b/perl/Git/I18N.pm @@ -68,7 +68,7 @@ Git::I18N - Perl interface to Git's Gettext localizations print __("Welcome to Git!\n"); - printf __("The following error occured: %s\n"), $error; + printf __("The following error occurred: %s\n"), $error; =head1 DESCRIPTION diff --git a/perl/Git/SVN/Ra.pm b/perl/Git/SVN/Ra.pm index 049c97bfaf..6a212eb7a8 100644 --- a/perl/Git/SVN/Ra.pm +++ b/perl/Git/SVN/Ra.pm @@ -295,7 +295,7 @@ sub gs_do_switch { my $full_url = add_path_to_url( $self->url, $path ); my ($ra, $reparented); - if ($old_url =~ m#^svn(\+ssh)?://# || + if ($old_url =~ m#^svn(\+\w+)?://# || ($full_url =~ m#^https?://# && canonicalize_url($full_url) ne $full_url)) { $_[0] = undef; diff --git a/perl/private-Error.pm b/perl/private-Error.pm index 11e9cd9a02..6098135ae2 100644 --- a/perl/private-Error.pm +++ b/perl/private-Error.pm @@ -630,7 +630,7 @@ Only one finally block may be specified per try block =head2 CONSTRUCTORS The C<Error> object is implemented as a HASH. This HASH is initialized -with the arguments that are passed to it's constructor. The elements +with the arguments that are passed to its constructor. The elements that are used by, or are retrievable by the C<Error> class are listed below, other classes may add to these. @@ -763,13 +763,13 @@ to the constructor. =item Error::Simple -This class can be used to hold simple error strings and values. It's +This class can be used to hold simple error strings and values. Its constructor takes two arguments. The first is a text value, the second is a numeric value. These values are what will be returned by the overload methods. If the text value ends with C<at file line 1> as $@ strings do, then -this infomation will be used to set the C<-file> and C<-line> arguments +this information will be used to set the C<-file> and C<-line> arguments of the error object. This class is used internally if an eval'd block die's with an error diff --git a/pkt-line.c b/pkt-line.c index eaba15f124..70f19501d0 100644 --- a/pkt-line.c +++ b/pkt-line.c @@ -1,6 +1,7 @@ #include "cache.h" #include "pkt-line.h" +char packet_buffer[LARGE_PACKET_MAX]; static const char *packet_trace_prefix = "git"; static const char trace_key[] = "GIT_TRACE_PACKET"; @@ -47,45 +48,13 @@ static void packet_trace(const char *buf, unsigned int len, int write) } /* - * Write a packetized stream, where each line is preceded by - * its length (including the header) as a 4-byte hex number. - * A length of 'zero' means end of stream (and a length of 1-3 - * would be an error). - * - * This is all pretty stupid, but we use this packetized line - * format to make a streaming format possible without ever - * over-running the read buffers. That way we'll never read - * into what might be the pack data (which should go to another - * process entirely). - * - * The writing side could use stdio, but since the reading - * side can't, we stay with pure read/write interfaces. - */ -ssize_t safe_write(int fd, const void *buf, ssize_t n) -{ - ssize_t nn = n; - while (n) { - int ret = xwrite(fd, buf, n); - if (ret > 0) { - buf = (char *) buf + ret; - n -= ret; - continue; - } - if (!ret) - die("write error (disk full?)"); - die_errno("write error"); - } - return nn; -} - -/* * If we buffered things up above (we don't, but we should), * we'd flush it here */ void packet_flush(int fd) { packet_trace("0000", 4, 1); - safe_write(fd, "0000", 4); + write_or_die(fd, "0000", 4); } void packet_buf_flush(struct strbuf *buf) @@ -121,7 +90,7 @@ void packet_write(int fd, const char *fmt, ...) va_start(args, fmt); n = format_packet(fmt, args); va_end(args); - safe_write(fd, buffer, n); + write_or_die(fd, buffer, n); } void packet_buf_write(struct strbuf *buf, const char *fmt, ...) @@ -135,13 +104,29 @@ void packet_buf_write(struct strbuf *buf, const char *fmt, ...) strbuf_add(buf, buffer, n); } -static int safe_read(int fd, void *buffer, unsigned size, int return_line_fail) +static int get_packet_data(int fd, char **src_buf, size_t *src_size, + void *dst, unsigned size, int options) { - ssize_t ret = read_in_full(fd, buffer, size); - if (ret < 0) - die_errno("read error"); - else if (ret < size) { - if (return_line_fail) + ssize_t ret; + + if (fd >= 0 && src_buf && *src_buf) + die("BUG: multiple sources given to packet_read"); + + /* Read up to "size" bytes from our source, whatever it is. */ + if (src_buf && *src_buf) { + ret = size < *src_size ? size : *src_size; + memcpy(dst, *src_buf, ret); + *src_buf += ret; + *src_size -= ret; + } else { + ret = read_in_full(fd, dst, size); + if (ret < 0) + die_errno("read error"); + } + + /* And complain if we didn't get enough bytes to satisfy the read. */ + if (ret < size) { + if (options & PACKET_READ_GENTLE_ON_EOF) return -1; die("The remote end hung up unexpectedly"); @@ -175,13 +160,14 @@ static int packet_length(const char *linelen) return len; } -static int packet_read_internal(int fd, char *buffer, unsigned size, int return_line_fail) +int packet_read(int fd, char **src_buf, size_t *src_len, + char *buffer, unsigned size, int options) { int len, ret; char linelen[4]; - ret = safe_read(fd, linelen, 4, return_line_fail); - if (return_line_fail && ret < 0) + ret = get_packet_data(fd, src_buf, src_len, linelen, 4, options); + if (ret < 0) return ret; len = packet_length(linelen); if (len < 0) @@ -193,50 +179,37 @@ static int packet_read_internal(int fd, char *buffer, unsigned size, int return_ len -= 4; if (len >= size) die("protocol error: bad line length %d", len); - ret = safe_read(fd, buffer, len, return_line_fail); - if (return_line_fail && ret < 0) + ret = get_packet_data(fd, src_buf, src_len, buffer, len, options); + if (ret < 0) return ret; + + if ((options & PACKET_READ_CHOMP_NEWLINE) && + len && buffer[len-1] == '\n') + len--; + buffer[len] = 0; packet_trace(buffer, len, 0); return len; } -int packet_read(int fd, char *buffer, unsigned size) +static char *packet_read_line_generic(int fd, + char **src, size_t *src_len, + int *dst_len) { - return packet_read_internal(fd, buffer, size, 1); + int len = packet_read(fd, src, src_len, + packet_buffer, sizeof(packet_buffer), + PACKET_READ_CHOMP_NEWLINE); + if (dst_len) + *dst_len = len; + return len ? packet_buffer : NULL; } -int packet_read_line(int fd, char *buffer, unsigned size) +char *packet_read_line(int fd, int *len_p) { - return packet_read_internal(fd, buffer, size, 0); + return packet_read_line_generic(fd, NULL, NULL, len_p); } -int packet_get_line(struct strbuf *out, - char **src_buf, size_t *src_len) +char *packet_read_line_buf(char **src, size_t *src_len, int *dst_len) { - int len; - - if (*src_len < 4) - return -1; - len = packet_length(*src_buf); - if (len < 0) - return -1; - if (!len) { - *src_buf += 4; - *src_len -= 4; - packet_trace("0000", 4, 0); - return 0; - } - if (*src_len < len) - return -2; - - *src_buf += 4; - *src_len -= 4; - len -= 4; - - strbuf_add(out, *src_buf, len); - *src_buf += len; - *src_len -= len; - packet_trace(out->buf, out->len, 0); - return len; + return packet_read_line_generic(-1, src, src_len, dst_len); } diff --git a/pkt-line.h b/pkt-line.h index 8cfeb0c31c..0a838d1656 100644 --- a/pkt-line.h +++ b/pkt-line.h @@ -5,16 +5,78 @@ #include "strbuf.h" /* - * Silly packetized line writing interface + * Write a packetized stream, where each line is preceded by + * its length (including the header) as a 4-byte hex number. + * A length of 'zero' means end of stream (and a length of 1-3 + * would be an error). + * + * This is all pretty stupid, but we use this packetized line + * format to make a streaming format possible without ever + * over-running the read buffers. That way we'll never read + * into what might be the pack data (which should go to another + * process entirely). + * + * The writing side could use stdio, but since the reading + * side can't, we stay with pure read/write interfaces. */ void packet_flush(int fd); void packet_write(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3))); void packet_buf_flush(struct strbuf *buf); void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((format (printf, 2, 3))); -int packet_read_line(int fd, char *buffer, unsigned size); -int packet_read(int fd, char *buffer, unsigned size); -int packet_get_line(struct strbuf *out, char **src_buf, size_t *src_len); -ssize_t safe_write(int, const void *, ssize_t); +/* + * Read a packetized line into the buffer, which must be at least size bytes + * long. The return value specifies the number of bytes read into the buffer. + * + * If src_buffer is not NULL (and nor is *src_buffer), it should point to a + * buffer containing the packet data to parse, of at least *src_len bytes. + * After the function returns, src_buf will be incremented and src_len + * decremented by the number of bytes consumed. + * + * If src_buffer (or *src_buffer) is NULL, then data is read from the + * descriptor "fd". + * + * If options does not contain PACKET_READ_GENTLE_ON_EOF, we will die under any + * of the following conditions: + * + * 1. Read error from descriptor. + * + * 2. Protocol error from the remote (e.g., bogus length characters). + * + * 3. Receiving a packet larger than "size" bytes. + * + * 4. Truncated output from the remote (e.g., we expected a packet but got + * EOF, or we got a partial packet followed by EOF). + * + * If options does contain PACKET_READ_GENTLE_ON_EOF, we will not die on + * condition 4 (truncated input), but instead return -1. However, we will still + * die for the other 3 conditions. + * + * If options contains PACKET_READ_CHOMP_NEWLINE, a trailing newline (if + * present) is removed from the buffer before returning. + */ +#define PACKET_READ_GENTLE_ON_EOF (1u<<0) +#define PACKET_READ_CHOMP_NEWLINE (1u<<1) +int packet_read(int fd, char **src_buffer, size_t *src_len, char + *buffer, unsigned size, int options); + +/* + * Convenience wrapper for packet_read that is not gentle, and sets the + * CHOMP_NEWLINE option. The return value is NULL for a flush packet, + * and otherwise points to a static buffer (that may be overwritten by + * subsequent calls). If the size parameter is not NULL, the length of the + * packet is written to it. + */ +char *packet_read_line(int fd, int *size); + +/* + * Same as packet_read_line, but read from a buf rather than a descriptor; + * see packet_read for details on how src_* is used. + */ +char *packet_read_line_buf(char **src_buf, size_t *src_len, int *size); + +#define DEFAULT_PACKET_MAX 1000 +#define LARGE_PACKET_MAX 65520 +extern char packet_buffer[LARGE_PACKET_MAX]; #endif @@ -232,7 +232,7 @@ Shell: # To interpolate variables: details="oh noes" - eval_gettext "An error occured: \$details"; echo + eval_gettext "An error occurred: \$details"; echo In addition we have wrappers for messages that end with a trailing newline. I.e. you could write the above as: @@ -242,7 +242,7 @@ Shell: # To interpolate variables: details="oh noes" - eval_gettextln "An error occured: \$details" + eval_gettextln "An error occurred: \$details" More documentation about the interface is available in the GNU info page: `info '(gettext)sh'`. Looking at git-am.sh (the first shell @@ -257,7 +257,7 @@ Perl: use Git::I18N; print __("Welcome to Git!\n"); - printf __("The following error occured: %s\n"), $error; + printf __("The following error occurred: %s\n"), $error; Run `perldoc perl/Git/I18N.pm` for more info. @@ -766,14 +766,7 @@ struct format_commit_context { const struct pretty_print_context *pretty_ctx; unsigned commit_header_parsed:1; unsigned commit_message_parsed:1; - unsigned commit_signature_parsed:1; - struct { - char *gpg_output; - char *gpg_status; - char good_bad; - char *signer; - char *key; - } signature; + struct signature_check signature_check; char *message; size_t width, indent1, indent2; @@ -956,64 +949,6 @@ static void rewrap_message_tail(struct strbuf *sb, c->indent2 = new_indent2; } -static struct { - char result; - const char *check; -} signature_check[] = { - { 'G', "\n[GNUPG:] GOODSIG " }, - { 'B', "\n[GNUPG:] BADSIG " }, -}; - -static void parse_signature_lines(struct format_commit_context *ctx) -{ - const char *buf = ctx->signature.gpg_status; - int i; - - for (i = 0; i < ARRAY_SIZE(signature_check); i++) { - const char *found = strstr(buf, signature_check[i].check); - const char *next; - if (!found) - continue; - ctx->signature.good_bad = signature_check[i].result; - found += strlen(signature_check[i].check); - ctx->signature.key = xmemdupz(found, 16); - found += 17; - next = strchrnul(found, '\n'); - ctx->signature.signer = xmemdupz(found, next - found); - break; - } -} - -static void parse_commit_signature(struct format_commit_context *ctx) -{ - struct strbuf payload = STRBUF_INIT; - struct strbuf signature = STRBUF_INIT; - struct strbuf gpg_output = STRBUF_INIT; - struct strbuf gpg_status = STRBUF_INIT; - int status; - - ctx->commit_signature_parsed = 1; - - if (parse_signed_commit(ctx->commit->object.sha1, - &payload, &signature) <= 0) - goto out; - status = verify_signed_buffer(payload.buf, payload.len, - signature.buf, signature.len, - &gpg_output, &gpg_status); - if (status && !gpg_output.len) - goto out; - ctx->signature.gpg_output = strbuf_detach(&gpg_output, NULL); - ctx->signature.gpg_status = strbuf_detach(&gpg_status, NULL); - parse_signature_lines(ctx); - - out: - strbuf_release(&gpg_status); - strbuf_release(&gpg_output); - strbuf_release(&payload); - strbuf_release(&signature); -} - - static int format_reflog_person(struct strbuf *sb, char part, struct reflog_walk_info *log, @@ -1199,27 +1134,29 @@ static size_t format_commit_one(struct strbuf *sb, const char *placeholder, } if (placeholder[0] == 'G') { - if (!c->commit_signature_parsed) - parse_commit_signature(c); + if (!c->signature_check.result) + check_commit_signature(c->commit, &(c->signature_check)); switch (placeholder[1]) { case 'G': - if (c->signature.gpg_output) - strbuf_addstr(sb, c->signature.gpg_output); + if (c->signature_check.gpg_output) + strbuf_addstr(sb, c->signature_check.gpg_output); break; case '?': - switch (c->signature.good_bad) { + switch (c->signature_check.result) { case 'G': case 'B': - strbuf_addch(sb, c->signature.good_bad); + case 'U': + case 'N': + strbuf_addch(sb, c->signature_check.result); } break; case 'S': - if (c->signature.signer) - strbuf_addstr(sb, c->signature.signer); + if (c->signature_check.signer) + strbuf_addstr(sb, c->signature_check.signer); break; case 'K': - if (c->signature.key) - strbuf_addstr(sb, c->signature.key); + if (c->signature_check.key) + strbuf_addstr(sb, c->signature_check.key); break; } return 2; @@ -1357,8 +1294,8 @@ void format_commit_message(const struct commit *commit, rewrap_message_tail(sb, &context, 0, 0, 0); logmsg_free(context.message, commit); - free(context.signature.gpg_output); - free(context.signature.signer); + free(context.signature_check.gpg_output); + free(context.signature_check.signer); } static void pp_header(const struct pretty_print_context *pp, diff --git a/progress.c b/progress.c index 3971f49f4d..10652b174d 100644 --- a/progress.c +++ b/progress.c @@ -10,6 +10,7 @@ #include "git-compat-util.h" #include "progress.h" +#include "strbuf.h" #define TP_IDX_MAX 8 @@ -112,34 +113,14 @@ static int display(struct progress *progress, unsigned n, const char *done) return 0; } -static void throughput_string(struct throughput *tp, off_t total, +static void throughput_string(struct strbuf *buf, off_t total, unsigned int rate) { - int l = sizeof(tp->display); - if (total > 1 << 30) { - l -= snprintf(tp->display, l, ", %u.%2.2u GiB", - (int)(total >> 30), - (int)(total & ((1 << 30) - 1)) / 10737419); - } else if (total > 1 << 20) { - int x = total + 5243; /* for rounding */ - l -= snprintf(tp->display, l, ", %u.%2.2u MiB", - x >> 20, ((x & ((1 << 20) - 1)) * 100) >> 20); - } else if (total > 1 << 10) { - int x = total + 5; /* for rounding */ - l -= snprintf(tp->display, l, ", %u.%2.2u KiB", - x >> 10, ((x & ((1 << 10) - 1)) * 100) >> 10); - } else { - l -= snprintf(tp->display, l, ", %u bytes", (int)total); - } - - if (rate > 1 << 10) { - int x = rate + 5; /* for rounding */ - snprintf(tp->display + sizeof(tp->display) - l, l, - " | %u.%2.2u MiB/s", - x >> 10, ((x & ((1 << 10) - 1)) * 100) >> 10); - } else if (rate) - snprintf(tp->display + sizeof(tp->display) - l, l, - " | %u KiB/s", rate); + strbuf_addstr(buf, ", "); + strbuf_humanise_bytes(buf, total); + strbuf_addstr(buf, " | "); + strbuf_humanise_bytes(buf, rate * 1024); + strbuf_addstr(buf, "/s"); } void display_throughput(struct progress *progress, off_t total) @@ -183,6 +164,7 @@ void display_throughput(struct progress *progress, off_t total) misecs += (int)(tv.tv_usec - tp->prev_tv.tv_usec) / 977; if (misecs > 512) { + struct strbuf buf = STRBUF_INIT; unsigned int count, rate; count = total - tp->prev_total; @@ -197,7 +179,9 @@ void display_throughput(struct progress *progress, off_t total) tp->last_misecs[tp->idx] = misecs; tp->idx = (tp->idx + 1) % TP_IDX_MAX; - throughput_string(tp, total, rate); + throughput_string(&buf, total, rate); + strncpy(tp->display, buf.buf, sizeof(tp->display)); + strbuf_release(&buf); if (progress->last_value != -1 && progress_update) display(progress, progress->last_value, NULL); } @@ -253,9 +237,12 @@ void stop_progress_msg(struct progress **p_progress, const char *msg) bufp = (len < sizeof(buf)) ? buf : xmalloc(len + 1); if (tp) { + struct strbuf strbuf = STRBUF_INIT; unsigned int rate = !tp->avg_misecs ? 0 : tp->avg_bytes / tp->avg_misecs; - throughput_string(tp, tp->curr_total, rate); + throughput_string(&strbuf, tp->curr_total, rate); + strncpy(tp->display, strbuf.buf, sizeof(tp->display)); + strbuf_release(&strbuf); } progress_update = 1; sprintf(bufp, ", %s.\n", msg); diff --git a/read-cache.c b/read-cache.c index 5a9704f4e5..04ed561bfe 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1899,3 +1899,37 @@ int index_name_is_other(const struct index_state *istate, const char *name, } return 1; } + +void *read_blob_data_from_index(struct index_state *istate, const char *path, unsigned long *size) +{ + int pos, len; + unsigned long sz; + enum object_type type; + void *data; + + len = strlen(path); + pos = index_name_pos(istate, path, len); + if (pos < 0) { + /* + * We might be in the middle of a merge, in which + * case we would read stage #2 (ours). + */ + int i; + for (i = -pos - 1; + (pos < 0 && i < istate->cache_nr && + !strcmp(istate->cache[i]->name, path)); + i++) + if (ce_stage(istate->cache[i]) == 2) + pos = i; + } + if (pos < 0) + return NULL; + data = read_sha1_file(istate->cache[pos]->sha1, &type, &sz); + if (!data || type != OBJ_BLOB) { + free(data); + return NULL; + } + if (size) + *size = sz; + return data; +} @@ -2332,59 +2332,117 @@ int read_ref_at(const char *refname, unsigned long at_time, int cnt, return 1; } -int for_each_recent_reflog_ent(const char *refname, each_reflog_ent_fn fn, long ofs, void *cb_data) +static int show_one_reflog_ent(struct strbuf *sb, each_reflog_ent_fn fn, void *cb_data) +{ + unsigned char osha1[20], nsha1[20]; + char *email_end, *message; + unsigned long timestamp; + int tz; + + /* old SP new SP name <email> SP time TAB msg LF */ + if (sb->len < 83 || sb->buf[sb->len - 1] != '\n' || + get_sha1_hex(sb->buf, osha1) || sb->buf[40] != ' ' || + get_sha1_hex(sb->buf + 41, nsha1) || sb->buf[81] != ' ' || + !(email_end = strchr(sb->buf + 82, '>')) || + email_end[1] != ' ' || + !(timestamp = strtoul(email_end + 2, &message, 10)) || + !message || message[0] != ' ' || + (message[1] != '+' && message[1] != '-') || + !isdigit(message[2]) || !isdigit(message[3]) || + !isdigit(message[4]) || !isdigit(message[5])) + return 0; /* corrupt? */ + email_end[1] = '\0'; + tz = strtol(message + 1, NULL, 10); + if (message[6] != '\t') + message += 6; + else + message += 7; + return fn(osha1, nsha1, sb->buf + 82, timestamp, tz, message, cb_data); +} + +static char *find_beginning_of_line(char *bob, char *scan) +{ + while (bob < scan && *(--scan) != '\n') + ; /* keep scanning backwards */ + /* + * Return either beginning of the buffer, or LF at the end of + * the previous line. + */ + return scan; +} + +int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void *cb_data) { - const char *logfile; - FILE *logfp; struct strbuf sb = STRBUF_INIT; - int ret = 0; + FILE *logfp; + long pos; + int ret = 0, at_tail = 1; - logfile = git_path("logs/%s", refname); - logfp = fopen(logfile, "r"); + logfp = fopen(git_path("logs/%s", refname), "r"); if (!logfp) return -1; - if (ofs) { - struct stat statbuf; - if (fstat(fileno(logfp), &statbuf) || - statbuf.st_size < ofs || - fseek(logfp, -ofs, SEEK_END) || - strbuf_getwholeline(&sb, logfp, '\n')) { - fclose(logfp); - strbuf_release(&sb); - return -1; + /* Jump to the end */ + if (fseek(logfp, 0, SEEK_END) < 0) + return error("cannot seek back reflog for %s: %s", + refname, strerror(errno)); + pos = ftell(logfp); + while (!ret && 0 < pos) { + int cnt; + size_t nread; + char buf[BUFSIZ]; + char *endp, *scanp; + + /* Fill next block from the end */ + cnt = (sizeof(buf) < pos) ? sizeof(buf) : pos; + if (fseek(logfp, pos - cnt, SEEK_SET)) + return error("cannot seek back reflog for %s: %s", + refname, strerror(errno)); + nread = fread(buf, cnt, 1, logfp); + if (nread != 1) + return error("cannot read %d bytes from reflog for %s: %s", + cnt, refname, strerror(errno)); + pos -= cnt; + + scanp = endp = buf + cnt; + if (at_tail && scanp[-1] == '\n') + /* Looking at the final LF at the end of the file */ + scanp--; + at_tail = 0; + + while (buf < scanp) { + /* + * terminating LF of the previous line, or the beginning + * of the buffer. + */ + char *bp; + + bp = find_beginning_of_line(buf, scanp); + + if (*bp != '\n') { + strbuf_splice(&sb, 0, 0, buf, endp - buf); + if (pos) + break; /* need to fill another block */ + scanp = buf - 1; /* leave loop */ + } else { + /* + * (bp + 1) thru endp is the beginning of the + * current line we have in sb + */ + strbuf_splice(&sb, 0, 0, bp + 1, endp - (bp + 1)); + scanp = bp; + endp = bp + 1; + } + ret = show_one_reflog_ent(&sb, fn, cb_data); + strbuf_reset(&sb); + if (ret) + break; } - } - while (!strbuf_getwholeline(&sb, logfp, '\n')) { - unsigned char osha1[20], nsha1[20]; - char *email_end, *message; - unsigned long timestamp; - int tz; - - /* old SP new SP name <email> SP time TAB msg LF */ - if (sb.len < 83 || sb.buf[sb.len - 1] != '\n' || - get_sha1_hex(sb.buf, osha1) || sb.buf[40] != ' ' || - get_sha1_hex(sb.buf + 41, nsha1) || sb.buf[81] != ' ' || - !(email_end = strchr(sb.buf + 82, '>')) || - email_end[1] != ' ' || - !(timestamp = strtoul(email_end + 2, &message, 10)) || - !message || message[0] != ' ' || - (message[1] != '+' && message[1] != '-') || - !isdigit(message[2]) || !isdigit(message[3]) || - !isdigit(message[4]) || !isdigit(message[5])) - continue; /* corrupt? */ - email_end[1] = '\0'; - tz = strtol(message + 1, NULL, 10); - if (message[6] != '\t') - message += 6; - else - message += 7; - ret = fn(osha1, nsha1, sb.buf + 82, timestamp, tz, message, - cb_data); - if (ret) - break; } + if (!ret && sb.len) + ret = show_one_reflog_ent(&sb, fn, cb_data); + fclose(logfp); strbuf_release(&sb); return ret; @@ -2392,9 +2450,20 @@ int for_each_recent_reflog_ent(const char *refname, each_reflog_ent_fn fn, long int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data) { - return for_each_recent_reflog_ent(refname, fn, 0, cb_data); -} + FILE *logfp; + struct strbuf sb = STRBUF_INIT; + int ret = 0; + + logfp = fopen(git_path("logs/%s", refname), "r"); + if (!logfp) + return -1; + while (!ret && !strbuf_getwholeline(&sb, logfp, '\n')) + ret = show_one_reflog_ent(&sb, fn, cb_data); + fclose(logfp); + strbuf_release(&sb); + return ret; +} /* * Call fn for each reflog in the namespace indicated by name. name * must be empty or end with '/'. Name will be used as a scratch @@ -103,7 +103,7 @@ extern int read_ref_at(const char *refname, unsigned long at_time, int cnt, /* iterate over reflog entries */ typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *); int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data); -int for_each_recent_reflog_ent(const char *refname, each_reflog_ent_fn fn, long, void *cb_data); +int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void *cb_data); /* * Calls the specified function for each reflog file until it returns nonzero, diff --git a/remote-curl.c b/remote-curl.c index 933c69ac26..60eda63081 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -76,21 +76,109 @@ struct discovery { char *buf_alloc; char *buf; size_t len; + struct ref *refs; unsigned proto_git : 1; }; static struct discovery *last_discovery; +static struct ref *parse_git_refs(struct discovery *heads, int for_push) +{ + struct ref *list = NULL; + get_remote_heads(-1, heads->buf, heads->len, &list, + for_push ? REF_NORMAL : 0, NULL); + return list; +} + +static struct ref *parse_info_refs(struct discovery *heads) +{ + char *data, *start, *mid; + char *ref_name; + int i = 0; + + struct ref *refs = NULL; + struct ref *ref = NULL; + struct ref *last_ref = NULL; + + data = heads->buf; + start = NULL; + mid = data; + while (i < heads->len) { + if (!start) { + start = &data[i]; + } + if (data[i] == '\t') + mid = &data[i]; + if (data[i] == '\n') { + if (mid - start != 40) + die("%sinfo/refs not valid: is this a git repository?", url); + data[i] = 0; + ref_name = mid + 1; + ref = xmalloc(sizeof(struct ref) + + strlen(ref_name) + 1); + memset(ref, 0, sizeof(struct ref)); + strcpy(ref->name, ref_name); + get_sha1_hex(start, ref->old_sha1); + if (!refs) + refs = ref; + if (last_ref) + last_ref->next = ref; + last_ref = ref; + start = NULL; + } + i++; + } + + ref = alloc_ref("HEAD"); + if (!http_fetch_ref(url, ref) && + !resolve_remote_symref(ref, refs)) { + ref->next = refs; + refs = ref; + } else { + free(ref); + } + + return refs; +} + static void free_discovery(struct discovery *d) { if (d) { if (d == last_discovery) last_discovery = NULL; free(d->buf_alloc); + free_refs(d->refs); free(d); } } -static struct discovery* discover_refs(const char *service) +static int show_http_message(struct strbuf *type, struct strbuf *msg) +{ + const char *p, *eol; + + /* + * We only show text/plain parts, as other types are likely + * to be ugly to look at on the user's terminal. + * + * TODO should handle "; charset=XXX", and re-encode into + * logoutputencoding + */ + if (strcasecmp(type->buf, "text/plain")) + return -1; + + strbuf_trim(msg); + if (!msg->len) + return -1; + + p = msg->buf; + do { + eol = strchrnul(p, '\n'); + fprintf(stderr, "remote: %.*s\n", (int)(eol - p), p); + p = eol + 1; + } while(*eol); + return 0; +} + +static struct discovery* discover_refs(const char *service, int for_push) { struct strbuf exp = STRBUF_INIT; struct strbuf type = STRBUF_INIT; @@ -115,18 +203,20 @@ static struct discovery* discover_refs(const char *service) } refs_url = strbuf_detach(&buffer, NULL); - http_ret = http_get_strbuf(refs_url, &type, &buffer, HTTP_NO_CACHE); + http_ret = http_get_strbuf(refs_url, &type, &buffer, + HTTP_NO_CACHE | HTTP_KEEP_ERROR); switch (http_ret) { case HTTP_OK: break; case HTTP_MISSING_TARGET: - die("%s not found: did you run git update-server-info on the" - " server?", refs_url); + show_http_message(&type, &buffer); + die("repository '%s' not found", url); case HTTP_NOAUTH: - die("Authentication failed"); + show_http_message(&type, &buffer); + die("Authentication failed for '%s'", url); default: - http_error(refs_url, http_ret); - die("HTTP request failed"); + show_http_message(&type, &buffer); + die("unable to access '%s': %s", url, curl_errorstr); } last= xcalloc(1, sizeof(*last_discovery)); @@ -138,32 +228,35 @@ static struct discovery* discover_refs(const char *service) if (maybe_smart && (5 <= last->len && last->buf[4] == '#') && !strbuf_cmp(&exp, &type)) { + char *line; + /* * smart HTTP response; validate that the service * pkt-line matches our request. */ - if (packet_get_line(&buffer, &last->buf, &last->len) <= 0) - die("%s has invalid packet header", refs_url); - if (buffer.len && buffer.buf[buffer.len - 1] == '\n') - strbuf_setlen(&buffer, buffer.len - 1); + line = packet_read_line_buf(&last->buf, &last->len, NULL); strbuf_reset(&exp); strbuf_addf(&exp, "# service=%s", service); - if (strbuf_cmp(&exp, &buffer)) - die("invalid server response; got '%s'", buffer.buf); + if (strcmp(line, exp.buf)) + die("invalid server response; got '%s'", line); strbuf_release(&exp); /* The header can include additional metadata lines, up * until a packet flush marker. Ignore these now, but * in the future we might start to scan them. */ - strbuf_reset(&buffer); - while (packet_get_line(&buffer, &last->buf, &last->len) > 0) - strbuf_reset(&buffer); + while (packet_read_line_buf(&last->buf, &last->len, NULL)) + ; last->proto_git = 1; } + if (last->proto_git) + last->refs = parse_git_refs(last, for_push); + else + last->refs = parse_info_refs(last); + free(refs_url); strbuf_release(&exp); strbuf_release(&type); @@ -172,99 +265,16 @@ static struct discovery* discover_refs(const char *service) return last; } -static int write_discovery(int in, int out, void *data) -{ - struct discovery *heads = data; - int err = 0; - if (write_in_full(out, heads->buf, heads->len) != heads->len) - err = 1; - close(out); - return err; -} - -static struct ref *parse_git_refs(struct discovery *heads, int for_push) -{ - struct ref *list = NULL; - struct async async; - - memset(&async, 0, sizeof(async)); - async.proc = write_discovery; - async.data = heads; - async.out = -1; - - if (start_async(&async)) - die("cannot start thread to parse advertised refs"); - get_remote_heads(async.out, &list, - for_push ? REF_NORMAL : 0, NULL); - close(async.out); - if (finish_async(&async)) - die("ref parsing thread failed"); - return list; -} - -static struct ref *parse_info_refs(struct discovery *heads) -{ - char *data, *start, *mid; - char *ref_name; - int i = 0; - - struct ref *refs = NULL; - struct ref *ref = NULL; - struct ref *last_ref = NULL; - - data = heads->buf; - start = NULL; - mid = data; - while (i < heads->len) { - if (!start) { - start = &data[i]; - } - if (data[i] == '\t') - mid = &data[i]; - if (data[i] == '\n') { - if (mid - start != 40) - die("%sinfo/refs not valid: is this a git repository?", url); - data[i] = 0; - ref_name = mid + 1; - ref = xmalloc(sizeof(struct ref) + - strlen(ref_name) + 1); - memset(ref, 0, sizeof(struct ref)); - strcpy(ref->name, ref_name); - get_sha1_hex(start, ref->old_sha1); - if (!refs) - refs = ref; - if (last_ref) - last_ref->next = ref; - last_ref = ref; - start = NULL; - } - i++; - } - - ref = alloc_ref("HEAD"); - if (!http_fetch_ref(url, ref) && - !resolve_remote_symref(ref, refs)) { - ref->next = refs; - refs = ref; - } else { - free(ref); - } - - return refs; -} - static struct ref *get_refs(int for_push) { struct discovery *heads; if (for_push) - heads = discover_refs("git-receive-pack"); + heads = discover_refs("git-receive-pack", for_push); else - heads = discover_refs("git-upload-pack"); + heads = discover_refs("git-upload-pack", for_push); - if (heads->proto_git) - return parse_git_refs(heads, for_push); - return parse_info_refs(heads); + return heads->refs; } static void output_refs(struct ref *refs) @@ -278,7 +288,6 @@ static void output_refs(struct ref *refs) } printf("\n"); fflush(stdout); - free_refs(refs); } struct rpc_state { @@ -308,7 +317,7 @@ static size_t rpc_out(void *ptr, size_t eltsize, if (!avail) { rpc->initial_buffer = 0; - avail = packet_read_line(rpc->out, rpc->buf, rpc->alloc); + avail = packet_read(rpc->out, NULL, NULL, rpc->buf, rpc->alloc, 0); if (!avail) return 0; rpc->pos = 0; @@ -425,7 +434,7 @@ static int post_rpc(struct rpc_state *rpc) break; } - n = packet_read_line(rpc->out, buf, left); + n = packet_read(rpc->out, NULL, NULL, buf, left, 0); if (!n) break; rpc->len += n; @@ -579,7 +588,7 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads) rpc->hdr_accept = strbuf_detach(&buf, NULL); while (!err) { - int n = packet_read_line(rpc->out, rpc->buf, rpc->alloc); + int n = packet_read(rpc->out, NULL, NULL, rpc->buf, rpc->alloc, 0); if (!n) break; rpc->pos = 0; @@ -685,7 +694,7 @@ static int fetch_git(struct discovery *heads, err = rpc_service(&rpc, heads); if (rpc.result.len) - safe_write(1, rpc.result.buf, rpc.result.len); + write_or_die(1, rpc.result.buf, rpc.result.len); strbuf_release(&rpc.result); strbuf_release(&preamble); free(depth_arg); @@ -694,7 +703,7 @@ static int fetch_git(struct discovery *heads, static int fetch(int nr_heads, struct ref **to_fetch) { - struct discovery *d = discover_refs("git-upload-pack"); + struct discovery *d = discover_refs("git-upload-pack", 0); if (d->proto_git) return fetch_git(d, nr_heads, to_fetch); else @@ -805,7 +814,7 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs) err = rpc_service(&rpc, heads); if (rpc.result.len) - safe_write(1, rpc.result.buf, rpc.result.len); + write_or_die(1, rpc.result.buf, rpc.result.len); strbuf_release(&rpc.result); free(argv); return err; @@ -813,7 +822,7 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs) static int push(int nr_spec, char **specs) { - struct discovery *heads = discover_refs("git-receive-pack"); + struct discovery *heads = discover_refs("git-receive-pack", 1); int ret; if (heads->proto_git) @@ -15,6 +15,7 @@ static struct refspec s_tag_refspec = { 0, 1, 0, + 0, "refs/tags/*", "refs/tags/*" }; @@ -48,6 +49,7 @@ static int branches_nr; static struct branch *current_branch; static const char *default_remote_name; +static const char *pushremote_name; static int explicit_default_remote_name; static struct rewrites rewrites; @@ -356,13 +358,16 @@ static int handle_config(const char *key, const char *value, void *cb) return 0; branch = make_branch(name, subkey - name); if (!strcmp(subkey, ".remote")) { - if (!value) - return config_error_nonbool(key); - branch->remote_name = xstrdup(value); + if (git_config_string(&branch->remote_name, key, value)) + return -1; if (branch == current_branch) { default_remote_name = branch->remote_name; explicit_default_remote_name = 1; } + } else if (!strcmp(subkey, ".pushremote")) { + if (branch == current_branch) + if (git_config_string(&pushremote_name, key, value)) + return -1; } else if (!strcmp(subkey, ".merge")) { if (!value) return config_error_nonbool(key); @@ -388,9 +393,16 @@ static int handle_config(const char *key, const char *value, void *cb) add_instead_of(rewrite, xstrdup(value)); } } + if (prefixcmp(key, "remote.")) return 0; name = key + 7; + + /* Handle remote.* variables */ + if (!strcmp(name, "pushdefault")) + return git_config_string(&pushremote_name, key, value); + + /* Handle remote.<name>.* variables */ if (*name == '/') { warning("Config remote shorthand cannot begin with '/': %s", name); @@ -538,7 +550,7 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp /* * Before going on, special case ":" (or "+:") as a refspec - * for matching refs. + * for pushing matching refs. */ if (!fetch && rhs == lhs && rhs[1] == '\0') { rs[i].matching = 1; @@ -565,26 +577,25 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp flags = REFNAME_ALLOW_ONELEVEL | (is_glob ? REFNAME_REFSPEC_PATTERN : 0); if (fetch) { - /* - * LHS - * - empty is allowed; it means HEAD. - * - otherwise it must be a valid looking ref. - */ + unsigned char unused[40]; + + /* LHS */ if (!*rs[i].src) - ; /* empty is ok */ - else if (check_refname_format(rs[i].src, flags)) + ; /* empty is ok; it means "HEAD" */ + else if (llen == 40 && !get_sha1_hex(rs[i].src, unused)) + rs[i].exact_sha1 = 1; /* ok */ + else if (!check_refname_format(rs[i].src, flags)) + ; /* valid looking ref is ok */ + else goto invalid; - /* - * RHS - * - missing is ok, and is same as empty. - * - empty is ok; it means not to store. - * - otherwise it must be a valid looking ref. - */ + /* RHS */ if (!rs[i].dst) - ; /* ok */ + ; /* missing is ok; it is the same as empty */ else if (!*rs[i].dst) - ; /* ok */ - else if (check_refname_format(rs[i].dst, flags)) + ; /* empty is ok; it means "do not store" */ + else if (!check_refname_format(rs[i].dst, flags)) + ; /* valid looking ref is ok */ + else goto invalid; } else { /* @@ -671,17 +682,21 @@ static int valid_remote_nick(const char *name) return !strchr(name, '/'); /* no slash */ } -struct remote *remote_get(const char *name) +static struct remote *remote_get_1(const char *name, const char *pushremote_name) { struct remote *ret; int name_given = 0; - read_config(); if (name) name_given = 1; else { - name = default_remote_name; - name_given = explicit_default_remote_name; + if (pushremote_name) { + name = pushremote_name; + name_given = 1; + } else { + name = default_remote_name; + name_given = explicit_default_remote_name; + } } ret = make_remote(name, 0); @@ -700,6 +715,18 @@ struct remote *remote_get(const char *name) return ret; } +struct remote *remote_get(const char *name) +{ + read_config(); + return remote_get_1(name, NULL); +} + +struct remote *pushremote_get(const char *name) +{ + read_config(); + return remote_get_1(name, pushremote_name); +} + int remote_is_configured(const char *name) { int i; @@ -1195,6 +1222,101 @@ static struct ref **tail_ref(struct ref **head) return tail; } +struct tips { + struct commit **tip; + int nr, alloc; +}; + +static void add_to_tips(struct tips *tips, const unsigned char *sha1) +{ + struct commit *commit; + + if (is_null_sha1(sha1)) + return; + commit = lookup_commit_reference_gently(sha1, 1); + if (!commit || (commit->object.flags & TMP_MARK)) + return; + commit->object.flags |= TMP_MARK; + ALLOC_GROW(tips->tip, tips->nr + 1, tips->alloc); + tips->tip[tips->nr++] = commit; +} + +static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***dst_tail) +{ + struct string_list dst_tag = STRING_LIST_INIT_NODUP; + struct string_list src_tag = STRING_LIST_INIT_NODUP; + struct string_list_item *item; + struct ref *ref; + struct tips sent_tips; + + /* + * Collect everything we know they would have at the end of + * this push, and collect all tags they have. + */ + memset(&sent_tips, 0, sizeof(sent_tips)); + for (ref = *dst; ref; ref = ref->next) { + if (ref->peer_ref && + !is_null_sha1(ref->peer_ref->new_sha1)) + add_to_tips(&sent_tips, ref->peer_ref->new_sha1); + else + add_to_tips(&sent_tips, ref->old_sha1); + if (!prefixcmp(ref->name, "refs/tags/")) + string_list_append(&dst_tag, ref->name); + } + clear_commit_marks_many(sent_tips.nr, sent_tips.tip, TMP_MARK); + + sort_string_list(&dst_tag); + + /* Collect tags they do not have. */ + for (ref = src; ref; ref = ref->next) { + if (prefixcmp(ref->name, "refs/tags/")) + continue; /* not a tag */ + if (string_list_has_string(&dst_tag, ref->name)) + continue; /* they already have it */ + if (sha1_object_info(ref->new_sha1, NULL) != OBJ_TAG) + continue; /* be conservative */ + item = string_list_append(&src_tag, ref->name); + item->util = ref; + } + string_list_clear(&dst_tag, 0); + + /* + * At this point, src_tag lists tags that are missing from + * dst, and sent_tips lists the tips we are pushing or those + * that we know they already have. An element in the src_tag + * that is an ancestor of any of the sent_tips needs to be + * sent to the other side. + */ + if (sent_tips.nr) { + for_each_string_list_item(item, &src_tag) { + struct ref *ref = item->util; + struct ref *dst_ref; + struct commit *commit; + + if (is_null_sha1(ref->new_sha1)) + continue; + commit = lookup_commit_reference_gently(ref->new_sha1, 1); + if (!commit) + /* not pushing a commit, which is not an error */ + continue; + + /* + * Is this tag, which they do not have, reachable from + * any of the commits we are sending? + */ + if (!in_merge_bases_many(commit, sent_tips.nr, sent_tips.tip)) + continue; + + /* Add it in */ + dst_ref = make_linked_ref(ref->name, dst_tail); + hashcpy(dst_ref->new_sha1, ref->new_sha1); + dst_ref->peer_ref = copy_ref(ref); + } + } + string_list_clear(&src_tag, 0); + free(sent_tips.tip); +} + /* * Given the set of refs the local repository has, the set of refs the * remote repository has, and the refspec used for push, determine @@ -1227,9 +1349,6 @@ int match_push_refs(struct ref *src, struct ref **dst, const struct refspec *pat = NULL; char *dst_name; - if (ref->peer_ref) - continue; - dst_name = get_ref_match(rs, nr_refspec, ref, send_mirror, FROM_SRC, &pat); if (!dst_name) continue; @@ -1257,6 +1376,10 @@ int match_push_refs(struct ref *src, struct ref **dst, free_name: free(dst_name); } + + if (flags & MATCH_REFS_FOLLOW_TAGS) + add_missing_tags(src, dst, &dst_tail); + if (send_prune) { /* check for missing refs on the remote */ for (ref = *dst; ref; ref = ref->next) { @@ -1466,7 +1589,12 @@ int get_fetch_map(const struct ref *remote_refs, } else { const char *name = refspec->src[0] ? refspec->src : "HEAD"; - ref_map = get_remote_ref(remote_refs, name); + if (refspec->exact_sha1) { + ref_map = alloc_ref(name); + get_sha1_hex(name, ref_map->old_sha1); + } else { + ref_map = get_remote_ref(remote_refs, name); + } if (!missing_ok && !ref_map) die("Couldn't find remote ref %s", name); if (ref_map) { @@ -51,6 +51,7 @@ struct remote { }; struct remote *remote_get(const char *name); +struct remote *pushremote_get(const char *name); int remote_is_configured(const char *name); typedef int each_remote_fn(struct remote *remote, void *priv); @@ -62,6 +63,7 @@ struct refspec { unsigned force : 1; unsigned pattern : 1; unsigned matching : 1; + unsigned exact_sha1 : 1; char *src; char *dst; @@ -148,7 +150,8 @@ enum match_refs_flags { MATCH_REFS_NONE = 0, MATCH_REFS_ALL = (1 << 0), MATCH_REFS_MIRROR = (1 << 1), - MATCH_REFS_PRUNE = (1 << 2) + MATCH_REFS_PRUNE = (1 << 2), + MATCH_REFS_FOLLOW_TAGS = (1 << 3) }; /* Reporting of tracking info */ @@ -284,8 +284,10 @@ static int rerere_mem_getline(struct strbuf *sb, struct rerere_io *io_) strbuf_release(sb); if (!io->input.len) return -1; - ep = strchrnul(io->input.buf, '\n'); - if (*ep == '\n') + ep = memchr(io->input.buf, '\n', io->input.len); + if (!ep) + ep = io->input.buf + io->input.len; + else if (*ep == '\n') ep++; len = ep - io->input.buf; strbuf_add(sb, io->input.buf, len); @@ -295,7 +297,7 @@ static int rerere_mem_getline(struct strbuf *sb, struct rerere_io *io_) static int handle_cache(const char *path, unsigned char *sha1, const char *output) { - mmfile_t mmfile[3]; + mmfile_t mmfile[3] = {{NULL}}; mmbuffer_t result = {NULL, 0}; struct cache_entry *ce; int pos, len, i, hunk_no; @@ -314,17 +316,16 @@ static int handle_cache(const char *path, unsigned char *sha1, const char *outpu for (i = 0; i < 3; i++) { enum object_type type; unsigned long size; + int j; - mmfile[i].size = 0; - mmfile[i].ptr = NULL; if (active_nr <= pos) break; ce = active_cache[pos++]; - if (ce_namelen(ce) != len || memcmp(ce->name, path, len) - || ce_stage(ce) != i + 1) - break; - mmfile[i].ptr = read_sha1_file(ce->sha1, &type, &size); - mmfile[i].size = size; + if (ce_namelen(ce) != len || memcmp(ce->name, path, len)) + continue; + j = ce_stage(ce) - 1; + mmfile[j].ptr = read_sha1_file(ce->sha1, &type, &size); + mmfile[j].size = size; } for (i = 0; i < 3; i++) { if (!mmfile[i].ptr && !mmfile[i].size) diff --git a/resolve-undo.c b/resolve-undo.c index 72b46125b7..639eb9c59f 100644 --- a/resolve-undo.c +++ b/resolve-undo.c @@ -118,7 +118,7 @@ int unmerge_index_entry_at(struct index_state *istate, int pos) struct cache_entry *ce; struct string_list_item *item; struct resolve_undo_info *ru; - int i, err = 0; + int i, err = 0, matched; if (!istate->resolve_undo) return pos; @@ -137,6 +137,7 @@ int unmerge_index_entry_at(struct index_state *istate, int pos) ru = item->util; if (!ru) return pos; + matched = ce->ce_flags & CE_MATCHED; remove_index_entry_at(istate, pos); for (i = 0; i < 3; i++) { struct cache_entry *nce; @@ -144,6 +145,8 @@ int unmerge_index_entry_at(struct index_state *istate, int pos) continue; nce = make_cache_entry(ru->mode[i], ru->sha1[i], ce->name, i + 1, 0); + if (matched) + nce->ce_flags |= CE_MATCHED; if (add_index_entry(istate, nce, ADD_CACHE_OK_TO_ADD)) { err = 1; error("cannot unmerge '%s'", ce->name); @@ -156,6 +159,20 @@ int unmerge_index_entry_at(struct index_state *istate, int pos) return unmerge_index_entry_at(istate, pos); } +void unmerge_marked_index(struct index_state *istate) +{ + int i; + + if (!istate->resolve_undo) + return; + + for (i = 0; i < istate->cache_nr; i++) { + struct cache_entry *ce = istate->cache[i]; + if (ce->ce_flags & CE_MATCHED) + i = unmerge_index_entry_at(istate, i); + } +} + void unmerge_index(struct index_state *istate, const char **pathspec) { int i; diff --git a/resolve-undo.h b/resolve-undo.h index 845876911d..7a30206aad 100644 --- a/resolve-undo.h +++ b/resolve-undo.h @@ -12,5 +12,6 @@ extern struct string_list *resolve_undo_read(const char *, unsigned long); extern void resolve_undo_clear_index(struct index_state *); extern int unmerge_index_entry_at(struct index_state *, int); extern void unmerge_index(struct index_state *, const char **); +extern void unmerge_marked_index(struct index_state *); #endif diff --git a/revision.c b/revision.c index cf620c6b36..56d666d69a 100644 --- a/revision.c +++ b/revision.c @@ -1276,7 +1276,8 @@ static void read_revisions_from_stdin(struct rev_info *revs, } die("options not supported in --stdin mode"); } - if (handle_revision_arg(sb.buf, revs, 0, REVARG_CANNOT_BE_FILENAME)) + if (handle_revision_arg(xstrdup(sb.buf), revs, 0, + REVARG_CANNOT_BE_FILENAME)) die("bad revision '%s'", sb.buf); } if (seen_dashdash) @@ -2022,10 +2023,11 @@ static struct commit_list **simplify_one(struct rev_info *revs, struct commit *c if (revs->first_parent_only) break; } - if (!revs->first_parent_only) - cnt = remove_duplicate_parents(commit); - else + + if (revs->first_parent_only) cnt = 1; + else + cnt = remove_duplicate_parents(commit); /* * It is possible that we are a merge and one side branch diff --git a/revision.h b/revision.h index 5da09ee3ef..01bd2b7c07 100644 --- a/revision.h +++ b/revision.h @@ -138,7 +138,7 @@ struct rev_info { int reroll_count; char *message_id; struct string_list *ref_message_ids; - const char *add_signoff; + int add_signoff; const char *extra_headers; const char *log_reencode; const char *subject_prefix; diff --git a/run-command.c b/run-command.c index 765c2ce056..1b32a12a29 100644 --- a/run-command.c +++ b/run-command.c @@ -588,6 +588,7 @@ int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const static pthread_t main_thread; static int main_thread_set; static pthread_key_t async_key; +static pthread_key_t async_die_counter; static void *run_thread(void *data) { @@ -614,6 +615,14 @@ static NORETURN void die_async(const char *err, va_list params) exit(128); } + +static int async_die_is_recursing(void) +{ + void *ret = pthread_getspecific(async_die_counter); + pthread_setspecific(async_die_counter, (void *)1); + return ret != NULL; +} + #endif int start_async(struct async *async) @@ -695,7 +704,9 @@ int start_async(struct async *async) main_thread_set = 1; main_thread = pthread_self(); pthread_key_create(&async_key, NULL); + pthread_key_create(&async_die_counter, NULL); set_die_routine(die_async); + set_die_is_recursing_routine(async_die_is_recursing); } if (proc_in >= 0) diff --git a/send-pack.c b/send-pack.c index 97ab336097..7d172ef37f 100644 --- a/send-pack.c +++ b/send-pack.c @@ -106,15 +106,11 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext static int receive_status(int in, struct ref *refs) { struct ref *hint; - char line[1000]; int ret = 0; - int len = packet_read_line(in, line, sizeof(line)); - if (len < 10 || memcmp(line, "unpack ", 7)) + char *line = packet_read_line(in, NULL); + if (prefixcmp(line, "unpack ")) return error("did not receive remote status"); - if (memcmp(line, "unpack ok\n", 10)) { - char *p = line + strlen(line) - 1; - if (*p == '\n') - *p = '\0'; + if (strcmp(line, "unpack ok")) { error("unpack failed: %s", line + 7); ret = -1; } @@ -122,17 +118,15 @@ static int receive_status(int in, struct ref *refs) while (1) { char *refname; char *msg; - len = packet_read_line(in, line, sizeof(line)); - if (!len) + line = packet_read_line(in, NULL); + if (!line) break; - if (len < 3 || - (memcmp(line, "ok ", 3) && memcmp(line, "ng ", 3))) { - fprintf(stderr, "protocol error: %s\n", line); + if (prefixcmp(line, "ok ") && prefixcmp(line, "ng ")) { + error("invalid ref status from remote: %s", line); ret = -1; break; } - line[strlen(line)-1] = '\0'; refname = line + 3; msg = strchr(refname, ' '); if (msg) @@ -281,7 +275,7 @@ int send_pack(struct send_pack_args *args, send_sideband(out, -1, req_buf.buf, req_buf.len, LARGE_PACKET_MAX); } } else { - safe_write(out, req_buf.buf, req_buf.len); + write_or_die(out, req_buf.buf, req_buf.len); packet_flush(out); } strbuf_release(&req_buf); diff --git a/sequencer.c b/sequencer.c index aef5e8a017..cf8fbeb8d5 100644 --- a/sequencer.c +++ b/sequencer.c @@ -18,6 +18,89 @@ #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" const char sign_off_header[] = "Signed-off-by: "; +static const char cherry_picked_prefix[] = "(cherry picked from commit "; + +static int is_rfc2822_line(const char *buf, int len) +{ + int i; + + for (i = 0; i < len; i++) { + int ch = buf[i]; + if (ch == ':') + return 1; + if (!isalnum(ch) && ch != '-') + break; + } + + return 0; +} + +static int is_cherry_picked_from_line(const char *buf, int len) +{ + /* + * We only care that it looks roughly like (cherry picked from ...) + */ + return len > strlen(cherry_picked_prefix) + 1 && + !prefixcmp(buf, cherry_picked_prefix) && buf[len - 1] == ')'; +} + +/* + * Returns 0 for non-conforming footer + * Returns 1 for conforming footer + * Returns 2 when sob exists within conforming footer + * Returns 3 when sob exists within conforming footer as last entry + */ +static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob, + int ignore_footer) +{ + char prev; + int i, k; + int len = sb->len - ignore_footer; + const char *buf = sb->buf; + int found_sob = 0; + + /* footer must end with newline */ + if (!len || buf[len - 1] != '\n') + return 0; + + prev = '\0'; + for (i = len - 1; i > 0; i--) { + char ch = buf[i]; + if (prev == '\n' && ch == '\n') /* paragraph break */ + break; + prev = ch; + } + + /* require at least one blank line */ + if (prev != '\n' || buf[i] != '\n') + return 0; + + /* advance to start of last paragraph */ + while (i < len - 1 && buf[i] == '\n') + i++; + + for (; i < len; i = k) { + int found_rfc2822; + + for (k = i; k < len && buf[k] != '\n'; k++) + ; /* do nothing */ + k++; + + found_rfc2822 = is_rfc2822_line(buf + i, k - i - 1); + if (found_rfc2822 && sob && + !strncmp(buf + i, sob->buf, sob->len)) + found_sob = k; + + if (!(found_rfc2822 || + is_cherry_picked_from_line(buf + i, k - i - 1))) + return 0; + } + if (found_sob == i) + return 3; + if (found_sob) + return 2; + return 1; +} static void remove_sequencer_state(void) { @@ -133,7 +216,7 @@ static void print_advice(int show_hint, struct replay_opts *opts) if (msg) { fprintf(stderr, "%s\n", msg); /* - * A conflict has occured but the porcelain + * A conflict has occurred but the porcelain * (typically rebase --interactive) wants to take care * of the commit itself so remove CHERRY_PICK_HEAD */ @@ -237,7 +320,7 @@ static int do_recursive_merge(struct commit *base, struct commit *next, rollback_lock_file(&index_lock); if (opts->signoff) - append_signoff(msgbuf, 0); + append_signoff(msgbuf, 0, 0); if (!clean) { int i; @@ -496,7 +579,9 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts) } if (opts->record_origin) { - strbuf_addstr(&msgbuf, "(cherry picked from commit "); + if (!has_conforming_footer(&msgbuf, NULL, 0)) + strbuf_addch(&msgbuf, '\n'); + strbuf_addstr(&msgbuf, cherry_picked_prefix); strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1)); strbuf_addstr(&msgbuf, ")\n"); } @@ -962,6 +1047,7 @@ int sequencer_pick_revisions(struct replay_opts *opts) { struct commit_list *todo_list = NULL; unsigned char sha1[20]; + int i; if (opts->subcommand == REPLAY_NONE) assert(opts->revs); @@ -982,6 +1068,23 @@ int sequencer_pick_revisions(struct replay_opts *opts) if (opts->subcommand == REPLAY_CONTINUE) return sequencer_continue(opts); + for (i = 0; i < opts->revs->pending.nr; i++) { + unsigned char sha1[20]; + const char *name = opts->revs->pending.objects[i].name; + + /* This happens when using --stdin. */ + if (!strlen(name)) + continue; + + if (!get_sha1(name, sha1)) { + enum object_type type = sha1_object_info(sha1, NULL); + + if (type > 0 && type != OBJ_COMMIT) + die(_("%s: can't cherry-pick a %s"), name, typename(type)); + } else + die(_("%s: bad revision"), name); + } + /* * If we were called as "git cherry-pick <commit>", just * cherry-pick/revert it, set CHERRY_PICK_HEAD / @@ -1021,62 +1124,67 @@ int sequencer_pick_revisions(struct replay_opts *opts) return pick_commits(todo_list, opts); } -static int ends_rfc2822_footer(struct strbuf *sb, int ignore_footer) -{ - int ch; - int hit = 0; - int i, j, k; - int len = sb->len - ignore_footer; - int first = 1; - const char *buf = sb->buf; - - for (i = len - 1; i > 0; i--) { - if (hit && buf[i] == '\n') - break; - hit = (buf[i] == '\n'); - } - - while (i < len - 1 && buf[i] == '\n') - i++; - - for (; i < len; i = k) { - for (k = i; k < len && buf[k] != '\n'; k++) - ; /* do nothing */ - k++; - - if ((buf[k] == ' ' || buf[k] == '\t') && !first) - continue; - - first = 0; - - for (j = 0; i + j < len; j++) { - ch = buf[i + j]; - if (ch == ':') - break; - if (isalnum(ch) || - (ch == '-')) - continue; - return 0; - } - } - return 1; -} - -void append_signoff(struct strbuf *msgbuf, int ignore_footer) +void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag) { + unsigned no_dup_sob = flag & APPEND_SIGNOFF_DEDUP; struct strbuf sob = STRBUF_INIT; - int i; + int has_footer; strbuf_addstr(&sob, sign_off_header); strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"), getenv("GIT_COMMITTER_EMAIL"))); strbuf_addch(&sob, '\n'); - for (i = msgbuf->len - 1 - ignore_footer; i > 0 && msgbuf->buf[i - 1] != '\n'; i--) - ; /* do nothing */ - if (prefixcmp(msgbuf->buf + i, sob.buf)) { - if (!i || !ends_rfc2822_footer(msgbuf, ignore_footer)) - strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, "\n", 1); - strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, sob.buf, sob.len); + + /* + * If the whole message buffer is equal to the sob, pretend that we + * found a conforming footer with a matching sob + */ + if (msgbuf->len - ignore_footer == sob.len && + !strncmp(msgbuf->buf, sob.buf, sob.len)) + has_footer = 3; + else + has_footer = has_conforming_footer(msgbuf, &sob, ignore_footer); + + if (!has_footer) { + const char *append_newlines = NULL; + size_t len = msgbuf->len - ignore_footer; + + if (!len) { + /* + * The buffer is completely empty. Leave foom for + * the title and body to be filled in by the user. + */ + append_newlines = "\n\n"; + } else if (msgbuf->buf[len - 1] != '\n') { + /* + * Incomplete line. Complete the line and add a + * blank one so that there is an empty line between + * the message body and the sob. + */ + append_newlines = "\n\n"; + } else if (len == 1) { + /* + * Buffer contains a single newline. Add another + * so that we leave room for the title and body. + */ + append_newlines = "\n"; + } else if (msgbuf->buf[len - 2] != '\n') { + /* + * Buffer ends with a single newline. Add another + * so that there is an empty line between the message + * body and the sob. + */ + append_newlines = "\n"; + } /* else, the buffer already ends with two newlines. */ + + if (append_newlines) + strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, + append_newlines, strlen(append_newlines)); } + + if (has_footer != 3 && (!no_dup_sob || has_footer != 2)) + strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, + sob.buf, sob.len); + strbuf_release(&sob); } diff --git a/sequencer.h b/sequencer.h index 9d57d57524..1fc22dcabe 100644 --- a/sequencer.h +++ b/sequencer.h @@ -6,6 +6,8 @@ #define SEQ_TODO_FILE "sequencer/todo" #define SEQ_OPTS_FILE "sequencer/opts" +#define APPEND_SIGNOFF_DEDUP (1u << 0) + enum replay_action { REPLAY_REVERT, REPLAY_PICK @@ -48,6 +50,6 @@ int sequencer_pick_revisions(struct replay_opts *opts); extern const char sign_off_header[]; -void append_signoff(struct strbuf *msgbuf, int ignore_footer); +void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag); #endif diff --git a/sha1_file.c b/sha1_file.c index 40b23297b2..64228a26d0 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -21,6 +21,7 @@ #include "sha1-lookup.h" #include "bulk-checkin.h" #include "streaming.h" +#include "dir.h" #ifndef O_NOATIME #if defined(__linux__) && (defined(__i386__) || defined(__PPC__)) @@ -123,8 +124,13 @@ int safe_create_leading_directories(char *path) } } else if (mkdir(path, 0777)) { - *pos = '/'; - return -1; + if (errno == EEXIST && + !stat(path, &st) && S_ISDIR(st.st_mode)) { + ; /* somebody created it since we checked */ + } else { + *pos = '/'; + return -1; + } } else if (adjust_shared_perm(path)) { *pos = '/'; @@ -1000,6 +1006,63 @@ void install_packed_git(struct packed_git *pack) packed_git = pack; } +void (*report_garbage)(const char *desc, const char *path); + +static void report_helper(const struct string_list *list, + int seen_bits, int first, int last) +{ + const char *msg; + switch (seen_bits) { + case 0: + msg = "no corresponding .idx nor .pack"; + break; + case 1: + msg = "no corresponding .idx"; + break; + case 2: + msg = "no corresponding .pack"; + break; + default: + return; + } + for (; first < last; first++) + report_garbage(msg, list->items[first].string); +} + +static void report_pack_garbage(struct string_list *list) +{ + int i, baselen = -1, first = 0, seen_bits = 0; + + if (!report_garbage) + return; + + sort_string_list(list); + + for (i = 0; i < list->nr; i++) { + const char *path = list->items[i].string; + if (baselen != -1 && + strncmp(path, list->items[first].string, baselen)) { + report_helper(list, seen_bits, first, i); + baselen = -1; + seen_bits = 0; + } + if (baselen == -1) { + const char *dot = strrchr(path, '.'); + if (!dot) { + report_garbage("garbage found", path); + continue; + } + baselen = dot - path + 1; + first = i; + } + if (!strcmp(path + baselen, "pack")) + seen_bits |= 1; + else if (!strcmp(path + baselen, "idx")) + seen_bits |= 2; + } + report_helper(list, seen_bits, first, list->nr); +} + static void prepare_packed_git_one(char *objdir, int local) { /* Ensure that this buffer is large enough so that we can @@ -1009,6 +1072,7 @@ static void prepare_packed_git_one(char *objdir, int local) int len; DIR *dir; struct dirent *de; + struct string_list garbage = STRING_LIST_INIT_DUP; sprintf(path, "%s/pack", objdir); len = strlen(path); @@ -1024,29 +1088,49 @@ static void prepare_packed_git_one(char *objdir, int local) int namelen = strlen(de->d_name); struct packed_git *p; - if (!has_extension(de->d_name, ".idx")) + if (len + namelen + 1 > sizeof(path)) { + if (report_garbage) { + struct strbuf sb = STRBUF_INIT; + strbuf_addf(&sb, "%.*s/%s", len - 1, path, de->d_name); + report_garbage("path too long", sb.buf); + strbuf_release(&sb); + } continue; + } - if (len + namelen + 1 > sizeof(path)) + if (is_dot_or_dotdot(de->d_name)) continue; - /* Don't reopen a pack we already have. */ strcpy(path + len, de->d_name); - for (p = packed_git; p; p = p->next) { - if (!memcmp(path, p->pack_name, len + namelen - 4)) - break; + + if (has_extension(de->d_name, ".idx")) { + /* Don't reopen a pack we already have. */ + for (p = packed_git; p; p = p->next) { + if (!memcmp(path, p->pack_name, len + namelen - 4)) + break; + } + if (p == NULL && + /* + * See if it really is a valid .idx file with + * corresponding .pack file that we can map. + */ + (p = add_packed_git(path, len + namelen, local)) != NULL) + install_packed_git(p); } - if (p) - continue; - /* See if it really is a valid .idx file with corresponding - * .pack file that we can map. - */ - p = add_packed_git(path, len + namelen, local); - if (!p) + + if (!report_garbage) continue; - install_packed_git(p); + + if (has_extension(de->d_name, ".idx") || + has_extension(de->d_name, ".pack") || + has_extension(de->d_name, ".keep")) + string_list_append(&garbage, path); + else + report_garbage("garbage found", path); } closedir(dir); + report_pack_garbage(&garbage); + string_list_clear(&garbage, 0); } static int sort_pack(const void *a_, const void *b_) @@ -1187,6 +1271,10 @@ int check_sha1_signature(const unsigned char *sha1, void *map, char buf[1024 * 16]; ssize_t readlen = read_istream(st, buf, sizeof(buf)); + if (readlen < 0) { + close_istream(st); + return -1; + } if (!readlen) break; git_SHA1_Update(&c, buf, readlen); @@ -1560,50 +1648,6 @@ static off_t get_delta_base(struct packed_git *p, return base_offset; } -/* forward declaration for a mutually recursive function */ -static int packed_object_info(struct packed_git *p, off_t offset, - unsigned long *sizep, int *rtype); - -static int packed_delta_info(struct packed_git *p, - struct pack_window **w_curs, - off_t curpos, - enum object_type type, - off_t obj_offset, - unsigned long *sizep) -{ - off_t base_offset; - - base_offset = get_delta_base(p, w_curs, &curpos, type, obj_offset); - if (!base_offset) - return OBJ_BAD; - type = packed_object_info(p, base_offset, NULL, NULL); - if (type <= OBJ_NONE) { - struct revindex_entry *revidx; - const unsigned char *base_sha1; - revidx = find_pack_revindex(p, base_offset); - if (!revidx) - return OBJ_BAD; - base_sha1 = nth_packed_object_sha1(p, revidx->nr); - mark_bad_packed_object(p, base_sha1); - type = sha1_object_info(base_sha1, NULL); - if (type <= OBJ_NONE) - return OBJ_BAD; - } - - /* We choose to only get the type of the base object and - * ignore potentially corrupt pack file that expects the delta - * based on a base with a wrong size. This saves tons of - * inflate() calls. - */ - if (sizep) { - *sizep = get_size_from_delta(p, w_curs, curpos); - if (*sizep == 0) - type = OBJ_BAD; - } - - return type; -} - int unpack_object_header(struct packed_git *p, struct pack_window **w_curs, off_t *curpos, @@ -1630,6 +1674,25 @@ int unpack_object_header(struct packed_git *p, return type; } +static int retry_bad_packed_offset(struct packed_git *p, off_t obj_offset) +{ + int type; + struct revindex_entry *revidx; + const unsigned char *sha1; + revidx = find_pack_revindex(p, obj_offset); + if (!revidx) + return OBJ_BAD; + sha1 = nth_packed_object_sha1(p, revidx->nr); + mark_bad_packed_object(p, sha1); + type = sha1_object_info(sha1, NULL); + if (type <= OBJ_NONE) + return OBJ_BAD; + return type; +} + + +#define POI_STACK_PREALLOC 64 + static int packed_object_info(struct packed_git *p, off_t obj_offset, unsigned long *sizep, int *rtype) { @@ -1637,31 +1700,89 @@ static int packed_object_info(struct packed_git *p, off_t obj_offset, unsigned long size; off_t curpos = obj_offset; enum object_type type; + off_t small_poi_stack[POI_STACK_PREALLOC]; + off_t *poi_stack = small_poi_stack; + int poi_stack_nr = 0, poi_stack_alloc = POI_STACK_PREALLOC; type = unpack_object_header(p, &w_curs, &curpos, &size); + if (rtype) *rtype = type; /* representation type */ + if (sizep) { + if (type == OBJ_OFS_DELTA || type == OBJ_REF_DELTA) { + off_t tmp_pos = curpos; + off_t base_offset = get_delta_base(p, &w_curs, &tmp_pos, + type, obj_offset); + if (!base_offset) { + type = OBJ_BAD; + goto out; + } + *sizep = get_size_from_delta(p, &w_curs, tmp_pos); + if (*sizep == 0) { + type = OBJ_BAD; + goto out; + } + } else { + *sizep = size; + } + } + + while (type == OBJ_OFS_DELTA || type == OBJ_REF_DELTA) { + off_t base_offset; + /* Push the object we're going to leave behind */ + if (poi_stack_nr >= poi_stack_alloc && poi_stack == small_poi_stack) { + poi_stack_alloc = alloc_nr(poi_stack_nr); + poi_stack = xmalloc(sizeof(off_t)*poi_stack_alloc); + memcpy(poi_stack, small_poi_stack, sizeof(off_t)*poi_stack_nr); + } else { + ALLOC_GROW(poi_stack, poi_stack_nr+1, poi_stack_alloc); + } + poi_stack[poi_stack_nr++] = obj_offset; + /* If parsing the base offset fails, just unwind */ + base_offset = get_delta_base(p, &w_curs, &curpos, type, obj_offset); + if (!base_offset) + goto unwind; + curpos = obj_offset = base_offset; + type = unpack_object_header(p, &w_curs, &curpos, &size); + if (type <= OBJ_NONE) { + /* If getting the base itself fails, we first + * retry the base, otherwise unwind */ + type = retry_bad_packed_offset(p, base_offset); + if (type > OBJ_NONE) + goto out; + goto unwind; + } + } + switch (type) { - case OBJ_OFS_DELTA: - case OBJ_REF_DELTA: - type = packed_delta_info(p, &w_curs, curpos, - type, obj_offset, sizep); - break; + case OBJ_BAD: case OBJ_COMMIT: case OBJ_TREE: case OBJ_BLOB: case OBJ_TAG: - if (sizep) - *sizep = size; break; default: error("unknown object type %i at offset %"PRIuMAX" in %s", type, (uintmax_t)obj_offset, p->pack_name); type = OBJ_BAD; } + +out: + if (poi_stack != small_poi_stack) + free(poi_stack); unuse_pack(&w_curs); return type; + +unwind: + while (poi_stack_nr) { + obj_offset = poi_stack[--poi_stack_nr]; + type = retry_bad_packed_offset(p, obj_offset); + if (type > OBJ_NONE) + goto out; + } + type = OBJ_BAD; + goto out; } static void *unpack_compressed_entry(struct packed_git *p, @@ -1723,32 +1844,51 @@ static unsigned long pack_entry_hash(struct packed_git *p, off_t base_offset) return hash % MAX_DELTA_CACHE; } -static int in_delta_base_cache(struct packed_git *p, off_t base_offset) +static struct delta_base_cache_entry * +get_delta_base_cache_entry(struct packed_git *p, off_t base_offset) { unsigned long hash = pack_entry_hash(p, base_offset); - struct delta_base_cache_entry *ent = delta_base_cache + hash; + return delta_base_cache + hash; +} + +static int eq_delta_base_cache_entry(struct delta_base_cache_entry *ent, + struct packed_git *p, off_t base_offset) +{ return (ent->data && ent->p == p && ent->base_offset == base_offset); } +static int in_delta_base_cache(struct packed_git *p, off_t base_offset) +{ + struct delta_base_cache_entry *ent; + ent = get_delta_base_cache_entry(p, base_offset); + return eq_delta_base_cache_entry(ent, p, base_offset); +} + +static void clear_delta_base_cache_entry(struct delta_base_cache_entry *ent) +{ + ent->data = NULL; + ent->lru.next->prev = ent->lru.prev; + ent->lru.prev->next = ent->lru.next; + delta_base_cached -= ent->size; +} + static void *cache_or_unpack_entry(struct packed_git *p, off_t base_offset, unsigned long *base_size, enum object_type *type, int keep_cache) { + struct delta_base_cache_entry *ent; void *ret; - unsigned long hash = pack_entry_hash(p, base_offset); - struct delta_base_cache_entry *ent = delta_base_cache + hash; - ret = ent->data; - if (!ret || ent->p != p || ent->base_offset != base_offset) + ent = get_delta_base_cache_entry(p, base_offset); + + if (!eq_delta_base_cache_entry(ent, p, base_offset)) return unpack_entry(p, base_offset, type, base_size); - if (!keep_cache) { - ent->data = NULL; - ent->lru.next->prev = ent->lru.prev; - ent->lru.prev->next = ent->lru.next; - delta_base_cached -= ent->size; - } else { + ret = ent->data; + + if (!keep_cache) + clear_delta_base_cache_entry(ent); + else ret = xmemdupz(ent->data, ent->size); - } *type = ent->type; *base_size = ent->size; return ret; @@ -1812,68 +1952,6 @@ static void add_delta_base_cache(struct packed_git *p, off_t base_offset, static void *read_object(const unsigned char *sha1, enum object_type *type, unsigned long *size); -static void *unpack_delta_entry(struct packed_git *p, - struct pack_window **w_curs, - off_t curpos, - unsigned long delta_size, - off_t obj_offset, - enum object_type *type, - unsigned long *sizep) -{ - void *delta_data, *result, *base; - unsigned long base_size; - off_t base_offset; - - base_offset = get_delta_base(p, w_curs, &curpos, *type, obj_offset); - if (!base_offset) { - error("failed to validate delta base reference " - "at offset %"PRIuMAX" from %s", - (uintmax_t)curpos, p->pack_name); - return NULL; - } - unuse_pack(w_curs); - base = cache_or_unpack_entry(p, base_offset, &base_size, type, 0); - if (!base) { - /* - * We're probably in deep shit, but let's try to fetch - * the required base anyway from another pack or loose. - * This is costly but should happen only in the presence - * of a corrupted pack, and is better than failing outright. - */ - struct revindex_entry *revidx; - const unsigned char *base_sha1; - revidx = find_pack_revindex(p, base_offset); - if (!revidx) - return NULL; - base_sha1 = nth_packed_object_sha1(p, revidx->nr); - error("failed to read delta base object %s" - " at offset %"PRIuMAX" from %s", - sha1_to_hex(base_sha1), (uintmax_t)base_offset, - p->pack_name); - mark_bad_packed_object(p, base_sha1); - base = read_object(base_sha1, type, &base_size); - if (!base) - return NULL; - } - - delta_data = unpack_compressed_entry(p, w_curs, curpos, delta_size); - if (!delta_data) { - error("failed to unpack compressed delta " - "at offset %"PRIuMAX" from %s", - (uintmax_t)curpos, p->pack_name); - free(base); - return NULL; - } - result = patch_delta(base, base_size, - delta_data, delta_size, - sizep); - if (!result) - die("failed to apply delta"); - free(delta_data); - add_delta_base_cache(p, base_offset, base, base_size, *type); - return result; -} - static void write_pack_access_log(struct packed_git *p, off_t obj_offset) { static FILE *log_file; @@ -1894,48 +1972,179 @@ static void write_pack_access_log(struct packed_git *p, off_t obj_offset) int do_check_packed_object_crc; +#define UNPACK_ENTRY_STACK_PREALLOC 64 +struct unpack_entry_stack_ent { + off_t obj_offset; + off_t curpos; + unsigned long size; +}; + void *unpack_entry(struct packed_git *p, off_t obj_offset, - enum object_type *type, unsigned long *sizep) + enum object_type *final_type, unsigned long *final_size) { struct pack_window *w_curs = NULL; off_t curpos = obj_offset; - void *data; + void *data = NULL; + unsigned long size; + enum object_type type; + struct unpack_entry_stack_ent small_delta_stack[UNPACK_ENTRY_STACK_PREALLOC]; + struct unpack_entry_stack_ent *delta_stack = small_delta_stack; + int delta_stack_nr = 0, delta_stack_alloc = UNPACK_ENTRY_STACK_PREALLOC; + int base_from_cache = 0; if (log_pack_access) write_pack_access_log(p, obj_offset); - if (do_check_packed_object_crc && p->index_version > 1) { - struct revindex_entry *revidx = find_pack_revindex(p, obj_offset); - unsigned long len = revidx[1].offset - obj_offset; - if (check_pack_crc(p, &w_curs, obj_offset, len, revidx->nr)) { - const unsigned char *sha1 = - nth_packed_object_sha1(p, revidx->nr); - error("bad packed object CRC for %s", - sha1_to_hex(sha1)); - mark_bad_packed_object(p, sha1); - unuse_pack(&w_curs); - return NULL; + /* PHASE 1: drill down to the innermost base object */ + for (;;) { + off_t base_offset; + int i; + struct delta_base_cache_entry *ent; + + if (do_check_packed_object_crc && p->index_version > 1) { + struct revindex_entry *revidx = find_pack_revindex(p, obj_offset); + unsigned long len = revidx[1].offset - obj_offset; + if (check_pack_crc(p, &w_curs, obj_offset, len, revidx->nr)) { + const unsigned char *sha1 = + nth_packed_object_sha1(p, revidx->nr); + error("bad packed object CRC for %s", + sha1_to_hex(sha1)); + mark_bad_packed_object(p, sha1); + unuse_pack(&w_curs); + return NULL; + } } + + ent = get_delta_base_cache_entry(p, curpos); + if (eq_delta_base_cache_entry(ent, p, curpos)) { + type = ent->type; + data = ent->data; + size = ent->size; + clear_delta_base_cache_entry(ent); + base_from_cache = 1; + break; + } + + type = unpack_object_header(p, &w_curs, &curpos, &size); + if (type != OBJ_OFS_DELTA && type != OBJ_REF_DELTA) + break; + + base_offset = get_delta_base(p, &w_curs, &curpos, type, obj_offset); + if (!base_offset) { + error("failed to validate delta base reference " + "at offset %"PRIuMAX" from %s", + (uintmax_t)curpos, p->pack_name); + /* bail to phase 2, in hopes of recovery */ + data = NULL; + break; + } + + /* push object, proceed to base */ + if (delta_stack_nr >= delta_stack_alloc + && delta_stack == small_delta_stack) { + delta_stack_alloc = alloc_nr(delta_stack_nr); + delta_stack = xmalloc(sizeof(*delta_stack)*delta_stack_alloc); + memcpy(delta_stack, small_delta_stack, + sizeof(*delta_stack)*delta_stack_nr); + } else { + ALLOC_GROW(delta_stack, delta_stack_nr+1, delta_stack_alloc); + } + i = delta_stack_nr++; + delta_stack[i].obj_offset = obj_offset; + delta_stack[i].curpos = curpos; + delta_stack[i].size = size; + + curpos = obj_offset = base_offset; } - *type = unpack_object_header(p, &w_curs, &curpos, sizep); - switch (*type) { + /* PHASE 2: handle the base */ + switch (type) { case OBJ_OFS_DELTA: case OBJ_REF_DELTA: - data = unpack_delta_entry(p, &w_curs, curpos, *sizep, - obj_offset, type, sizep); + if (data) + die("BUG in unpack_entry: left loop at a valid delta"); break; case OBJ_COMMIT: case OBJ_TREE: case OBJ_BLOB: case OBJ_TAG: - data = unpack_compressed_entry(p, &w_curs, curpos, *sizep); + if (!base_from_cache) + data = unpack_compressed_entry(p, &w_curs, curpos, size); break; default: data = NULL; error("unknown object type %i at offset %"PRIuMAX" in %s", - *type, (uintmax_t)obj_offset, p->pack_name); + type, (uintmax_t)obj_offset, p->pack_name); } + + /* PHASE 3: apply deltas in order */ + + /* invariants: + * 'data' holds the base data, or NULL if there was corruption + */ + while (delta_stack_nr) { + void *delta_data; + void *base = data; + unsigned long delta_size, base_size = size; + int i; + + data = NULL; + + if (base) + add_delta_base_cache(p, obj_offset, base, base_size, type); + + if (!base) { + /* + * We're probably in deep shit, but let's try to fetch + * the required base anyway from another pack or loose. + * This is costly but should happen only in the presence + * of a corrupted pack, and is better than failing outright. + */ + struct revindex_entry *revidx; + const unsigned char *base_sha1; + revidx = find_pack_revindex(p, obj_offset); + if (revidx) { + base_sha1 = nth_packed_object_sha1(p, revidx->nr); + error("failed to read delta base object %s" + " at offset %"PRIuMAX" from %s", + sha1_to_hex(base_sha1), (uintmax_t)obj_offset, + p->pack_name); + mark_bad_packed_object(p, base_sha1); + base = read_object(base_sha1, &type, &base_size); + } + } + + i = --delta_stack_nr; + obj_offset = delta_stack[i].obj_offset; + curpos = delta_stack[i].curpos; + delta_size = delta_stack[i].size; + + if (!base) + continue; + + delta_data = unpack_compressed_entry(p, &w_curs, curpos, delta_size); + + if (!delta_data) { + error("failed to unpack compressed delta " + "at offset %"PRIuMAX" from %s", + (uintmax_t)curpos, p->pack_name); + free(base); + data = NULL; + continue; + } + + data = patch_delta(base, base_size, + delta_data, delta_size, + &size); + if (!data) + die("failed to apply delta"); + + free (delta_data); + } + + *final_type = type; + *final_size = size; + unuse_pack(&w_curs); return data; } diff --git a/sha1_name.c b/sha1_name.c index c50630a3ea..3820f28ae7 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -594,7 +594,7 @@ struct object *peel_to_type(const char *name, int namelen, while (1) { if (!o || (!o->parsed && !parse_object(o->sha1))) return NULL; - if (o->type == expected_type) + if (expected_type == OBJ_ANY || o->type == expected_type) return o; if (o->type == OBJ_TAG) o = ((struct tag*) o)->tagged; @@ -645,6 +645,8 @@ static int peel_onion(const char *name, int len, unsigned char *sha1) expected_type = OBJ_TREE; else if (!strncmp(blob_type, sp, 4) && sp[4] == '}') expected_type = OBJ_BLOB; + else if (!prefixcmp(sp, "object}")) + expected_type = OBJ_ANY; else if (sp[0] == '}') expected_type = OBJ_NONE; else if (sp[0] == '/') @@ -654,6 +656,8 @@ static int peel_onion(const char *name, int len, unsigned char *sha1) if (expected_type == OBJ_COMMIT) lookup_flags = GET_SHA1_COMMITTISH; + else if (expected_type == OBJ_TREE) + lookup_flags = GET_SHA1_TREEISH; if (get_sha1_1(name, sp - name - 2, outer, lookup_flags)) return -1; @@ -856,8 +860,8 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1, } struct grab_nth_branch_switch_cbdata { - long cnt, alloc; - struct strbuf *buf; + int remaining; + struct strbuf buf; }; static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1, @@ -867,7 +871,6 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1, struct grab_nth_branch_switch_cbdata *cb = cb_data; const char *match = NULL, *target = NULL; size_t len; - int nth; if (!prefixcmp(message, "checkout: moving from ")) { match = message + strlen("checkout: moving from "); @@ -876,11 +879,12 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1, if (!match || !target) return 0; - - len = target - match; - nth = cb->cnt++ % cb->alloc; - strbuf_reset(&cb->buf[nth]); - strbuf_add(&cb->buf[nth], match, len); + if (--(cb->remaining) == 0) { + len = target - match; + strbuf_reset(&cb->buf); + strbuf_add(&cb->buf, match, len); + return 1; /* we are done */ + } return 0; } @@ -891,7 +895,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1, static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf) { long nth; - int i, retval; + int retval; struct grab_nth_branch_switch_cbdata cb; const char *brace; char *num_end; @@ -901,34 +905,22 @@ static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf) brace = strchr(name, '}'); if (!brace) return -1; - nth = strtol(name+3, &num_end, 10); + nth = strtol(name + 3, &num_end, 10); if (num_end != brace) return -1; if (nth <= 0) return -1; - cb.alloc = nth; - cb.buf = xmalloc(nth * sizeof(struct strbuf)); - for (i = 0; i < nth; i++) - strbuf_init(&cb.buf[i], 20); - cb.cnt = 0; + cb.remaining = nth; + strbuf_init(&cb.buf, 20); + retval = 0; - for_each_recent_reflog_ent("HEAD", grab_nth_branch_switch, 40960, &cb); - if (cb.cnt < nth) { - cb.cnt = 0; - for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb); + if (0 < for_each_reflog_ent_reverse("HEAD", grab_nth_branch_switch, &cb)) { + strbuf_reset(buf); + strbuf_add(buf, cb.buf.buf, cb.buf.len); + retval = brace - name + 1; } - if (cb.cnt < nth) - goto release_return; - i = cb.cnt % nth; - strbuf_reset(buf); - strbuf_add(buf, cb.buf[i].buf, cb.buf[i].len); - retval = brace-name+1; - -release_return: - for (i = 0; i < nth; i++) - strbuf_release(&cb.buf[i]); - free(cb.buf); + strbuf_release(&cb.buf); return retval; } @@ -6,6 +6,7 @@ #define COMMAND_DIR "git-shell-commands" #define HELP_COMMAND COMMAND_DIR "/help" +#define NOLOGIN_COMMAND COMMAND_DIR "/no-interactive-login" static int do_generic_cmd(const char *me, char *arg) { @@ -65,6 +66,18 @@ static void run_shell(void) { int done = 0; static const char *help_argv[] = { HELP_COMMAND, NULL }; + + if (!access(NOLOGIN_COMMAND, F_OK)) { + /* Interactive login disabled. */ + const char *argv[] = { NOLOGIN_COMMAND, NULL }; + int status; + + status = run_command_v_opt(argv, 0); + if (status < 0) + exit(127); + exit(status); + } + /* Print help if enabled */ run_command_v_opt(help_argv, RUN_SILENT_EXEC_FAILURE); diff --git a/sideband.c b/sideband.c index d5ffa1c891..d1125f5c52 100644 --- a/sideband.c +++ b/sideband.c @@ -1,3 +1,4 @@ +#include "cache.h" #include "pkt-line.h" #include "sideband.h" @@ -37,7 +38,7 @@ int recv_sideband(const char *me, int in_stream, int out) while (1) { int band, len; - len = packet_read_line(in_stream, buf + pf, LARGE_PACKET_MAX); + len = packet_read(in_stream, NULL, NULL, buf + pf, LARGE_PACKET_MAX, 0); if (len == 0) break; if (len < 1) { @@ -108,7 +109,7 @@ int recv_sideband(const char *me, int in_stream, int out) } while (len); continue; case 1: - safe_write(out, buf + pf+1, len); + write_or_die(out, buf + pf+1, len); continue; default: fprintf(stderr, "%s: protocol error: bad band #%d\n", @@ -138,12 +139,12 @@ ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet if (0 <= band) { sprintf(hdr, "%04x", n + 5); hdr[4] = band; - safe_write(fd, hdr, 5); + write_or_die(fd, hdr, 5); } else { sprintf(hdr, "%04x", n + 4); - safe_write(fd, hdr, 4); + write_or_die(fd, hdr, 4); } - safe_write(fd, p, n); + write_or_die(fd, p, n); p += n; sz -= n; } diff --git a/sideband.h b/sideband.h index d72db35d1e..e46bed0b01 100644 --- a/sideband.h +++ b/sideband.h @@ -4,9 +4,6 @@ #define SIDEBAND_PROTOCOL_ERROR -2 #define SIDEBAND_REMOTE_ERROR -1 -#define DEFAULT_PACKET_MAX 1000 -#define LARGE_PACKET_MAX 65520 - int recv_sideband(const char *me, int in_stream, int out); ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_max); @@ -528,6 +528,25 @@ void strbuf_addstr_urlencode(struct strbuf *sb, const char *s, strbuf_add_urlencode(sb, s, strlen(s), reserved); } +void strbuf_humanise_bytes(struct strbuf *buf, off_t bytes) +{ + if (bytes > 1 << 30) { + strbuf_addf(buf, "%u.%2.2u GiB", + (int)(bytes >> 30), + (int)(bytes & ((1 << 30) - 1)) / 10737419); + } else if (bytes > 1 << 20) { + int x = bytes + 5243; /* for rounding */ + strbuf_addf(buf, "%u.%2.2u MiB", + x >> 20, ((x & ((1 << 20) - 1)) * 100) >> 20); + } else if (bytes > 1 << 10) { + int x = bytes + 5; /* for rounding */ + strbuf_addf(buf, "%u.%2.2u KiB", + x >> 10, ((x & ((1 << 10) - 1)) * 100) >> 10); + } else { + strbuf_addf(buf, "%u bytes", (int)bytes); + } +} + int printf_ln(const char *fmt, ...) { int ret; @@ -170,6 +170,7 @@ extern int strbuf_check_branch_ref(struct strbuf *sb, const char *name); extern void strbuf_addstr_urlencode(struct strbuf *, const char *, int reserved); +extern void strbuf_humanise_bytes(struct strbuf *buf, off_t bytes); __attribute__((format (printf,1,2))) extern int printf_ln(const char *fmt, ...); diff --git a/streaming.c b/streaming.c index 4d978e54e4..cabcd9d157 100644 --- a/streaming.c +++ b/streaming.c @@ -237,7 +237,7 @@ static read_method_decl(filtered) if (!fs->input_finished) { fs->i_end = read_istream(fs->upstream, fs->ibuf, FILTER_BUFFER); if (fs->i_end < 0) - break; + return -1; if (fs->i_end) continue; } @@ -309,7 +309,7 @@ static read_method_decl(loose) st->z_state = z_done; break; } - if (status != Z_OK && status != Z_BUF_ERROR) { + if (status != Z_OK && (status != Z_BUF_ERROR || total_read < sz)) { git_inflate_end(&st->z); st->z_state = z_error; return -1; @@ -514,6 +514,8 @@ int stream_blob_to_fd(int fd, unsigned const char *sha1, struct stream_filter *f ssize_t wrote, holeto; ssize_t readlen = read_istream(st, buf, sizeof(buf)); + if (readlen < 0) + goto close_and_exit; if (!readlen) break; if (can_seek && sizeof(buf) == readlen) { diff --git a/submodule.c b/submodule.c index 9ba1496543..e728025f60 100644 --- a/submodule.c +++ b/submodule.c @@ -216,6 +216,7 @@ static int prepare_submodule_summary(struct rev_info *rev, const char *path, } static void print_submodule_summary(struct rev_info *rev, FILE *f, + const char *line_prefix, const char *del, const char *add, const char *reset) { static const char format[] = " %m %s"; @@ -226,6 +227,7 @@ static void print_submodule_summary(struct rev_info *rev, FILE *f, struct pretty_print_context ctx = {0}; ctx.date_mode = rev->date_mode; strbuf_setlen(&sb, 0); + strbuf_addstr(&sb, line_prefix); if (commit->object.flags & SYMMETRIC_LEFT) { if (del) strbuf_addstr(&sb, del); @@ -256,12 +258,13 @@ int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg) } void show_submodule_summary(FILE *f, const char *path, + const char *line_prefix, unsigned char one[20], unsigned char two[20], unsigned dirty_submodule, const char *meta, const char *del, const char *add, const char *reset) { struct rev_info rev; - struct commit *left = left, *right = right; + struct commit *left = NULL, *right = NULL; const char *message = NULL; struct strbuf sb = STRBUF_INIT; int fast_forward = 0, fast_backward = 0; @@ -275,23 +278,23 @@ void show_submodule_summary(FILE *f, const char *path, else if (!(left = lookup_commit_reference(one)) || !(right = lookup_commit_reference(two))) message = "(commits not present)"; - - if (!message && - prepare_submodule_summary(&rev, path, left, right, - &fast_forward, &fast_backward)) + else if (prepare_submodule_summary(&rev, path, left, right, + &fast_forward, &fast_backward)) message = "(revision walker failed)"; if (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED) - fprintf(f, "Submodule %s contains untracked content\n", path); + fprintf(f, "%sSubmodule %s contains untracked content\n", + line_prefix, path); if (dirty_submodule & DIRTY_SUBMODULE_MODIFIED) - fprintf(f, "Submodule %s contains modified content\n", path); + fprintf(f, "%sSubmodule %s contains modified content\n", + line_prefix, path); if (!hashcmp(one, two)) { strbuf_release(&sb); return; } - strbuf_addf(&sb, "%sSubmodule %s %s..", meta, path, + strbuf_addf(&sb, "%s%sSubmodule %s %s..", line_prefix, meta, path, find_unique_abbrev(one, DEFAULT_ABBREV)); if (!fast_backward && !fast_forward) strbuf_addch(&sb, '.'); @@ -302,11 +305,12 @@ void show_submodule_summary(FILE *f, const char *path, strbuf_addf(&sb, "%s:%s\n", fast_backward ? " (rewind)" : "", reset); fwrite(sb.buf, sb.len, 1, f); - if (!message) { - print_submodule_summary(&rev, f, del, add, reset); + if (!message) /* only NULL if we succeeded in setting up the walk */ + print_submodule_summary(&rev, f, line_prefix, del, add, reset); + if (left) clear_commit_marks(left, ~0); + if (right) clear_commit_marks(right, ~0); - } strbuf_release(&sb); } diff --git a/submodule.h b/submodule.h index 3dc1b3fe89..c7ffc7c399 100644 --- a/submodule.h +++ b/submodule.h @@ -19,6 +19,7 @@ int parse_submodule_config_option(const char *var, const char *value); void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *); int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg); void show_submodule_summary(FILE *f, const char *path, + const char *line_prefix, unsigned char one[20], unsigned char two[20], unsigned dirty_submodule, const char *meta, const char *del, const char *add, const char *reset); @@ -86,23 +86,35 @@ appropriately before running "make". --immediate:: This causes the test to immediately exit upon the first - failed test. + failed test. Cleanup commands requested with + test_when_finished are not executed if the test failed, + in order to keep the state for inspection by the tester + to diagnose the bug. --long-tests:: This causes additional long-running tests to be run (where available), for more exhaustive testing. ---valgrind:: - Execute all Git binaries with valgrind and exit with status - 126 on errors (just like regular tests, this will only stop - the test script when running under -i). Valgrind errors - go to stderr, so you might want to pass the -v option, too. +--valgrind=<tool>:: + Execute all Git binaries under valgrind tool <tool> and exit + with status 126 on errors (just like regular tests, this will + only stop the test script when running under -i). Since it makes no sense to run the tests with --valgrind and not see any output, this option implies --verbose. For convenience, it also implies --tee. - Note that valgrind is run with the option --leak-check=no, + <tool> defaults to 'memcheck', just like valgrind itself. + Other particularly useful choices include 'helgrind' and + 'drd', but you may use any tool recognized by your valgrind + installation. + + As a special case, <tool> can be 'memcheck-fast', which uses + memcheck but disables --track-origins. Use this if you are + running tests in bulk, to see if there are _any_ memory + issues. + + Note that memcheck is run with the option --leak-check=no, as the git process is short-lived and some errors are not interesting. In order to run a single command under the same conditions manually, you should set GIT_VALGRIND to point to @@ -610,6 +622,11 @@ use these, and "test_set_prereq" for how to define your own. The process retains the same pid across exec(2). See fb9a2bea for details. + - PIPE + + The filesystem we're on supports creation of FIFOs (named pipes) + via mkfifo(1). + - SYMLINKS The filesystem we're on supports symbolic links. E.g. a FAT diff --git a/t/lib-gpg/pubring.gpg b/t/lib-gpg/pubring.gpg Binary files differindex 83855fa4e1..1a3c2d487c 100644 --- a/t/lib-gpg/pubring.gpg +++ b/t/lib-gpg/pubring.gpg diff --git a/t/lib-gpg/random_seed b/t/lib-gpg/random_seed Binary files differindex 8fed1339ed..95d249f15f 100644 --- a/t/lib-gpg/random_seed +++ b/t/lib-gpg/random_seed diff --git a/t/lib-gpg/secring.gpg b/t/lib-gpg/secring.gpg Binary files differindex d831cd9eb3..82dca8f80b 100644 --- a/t/lib-gpg/secring.gpg +++ b/t/lib-gpg/secring.gpg diff --git a/t/lib-gpg/trustdb.gpg b/t/lib-gpg/trustdb.gpg Binary files differindex abace962b8..4879ae9a84 100644 --- a/t/lib-gpg/trustdb.gpg +++ b/t/lib-gpg/trustdb.gpg diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index 542241b229..b5bce459b6 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -64,6 +64,11 @@ Alias /auth/dumb/ www/auth/dumb/ SetEnv GIT_COMMITTER_NAME "Custom User" SetEnv GIT_COMMITTER_EMAIL custom@example.com </LocationMatch> +<LocationMatch /smart_namespace/> + SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH} + SetEnv GIT_HTTP_EXPORT_ALL + SetEnv GIT_NAMESPACE ns +</LocationMatch> ScriptAliasMatch /smart_*[^/]*/(.*) ${GIT_EXEC_PATH}/git-http-backend/$1 ScriptAlias /broken_smart/ broken-smart-http.sh/ <Directory ${GIT_EXEC_PATH}> diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh index d8b7f2ffbc..9820f70c84 100755 --- a/t/t1006-cat-file.sh +++ b/t/t1006-cat-file.sh @@ -116,9 +116,9 @@ tree_pretty_content="100644 blob $hello_sha1 hello" run_tests 'tree' $tree_sha1 $tree_size "" "$tree_pretty_content" -commit_message="Intial commit" +commit_message="Initial commit" commit_sha1=$(echo_without_newline "$commit_message" | git commit-tree $tree_sha1) -commit_size=176 +commit_size=177 commit_content="tree $tree_sha1 author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> 0000000000 +0000 committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 0000000000 +0000 diff --git a/t/t1060-object-corruption.sh b/t/t1060-object-corruption.sh new file mode 100755 index 0000000000..3f8705139d --- /dev/null +++ b/t/t1060-object-corruption.sh @@ -0,0 +1,104 @@ +#!/bin/sh + +test_description='see how we handle various forms of corruption' +. ./test-lib.sh + +# convert "1234abcd" to ".git/objects/12/34abcd" +obj_to_file() { + echo "$(git rev-parse --git-dir)/objects/$(git rev-parse "$1" | sed 's,..,&/,')" +} + +# Convert byte at offset "$2" of object "$1" into '\0' +corrupt_byte() { + obj_file=$(obj_to_file "$1") && + chmod +w "$obj_file" && + printf '\0' | dd of="$obj_file" bs=1 seek="$2" conv=notrunc +} + +test_expect_success 'setup corrupt repo' ' + git init bit-error && + ( + cd bit-error && + test_commit content && + corrupt_byte HEAD:content.t 10 + ) +' + +test_expect_success 'setup repo with missing object' ' + git init missing && + ( + cd missing && + test_commit content && + rm -f "$(obj_to_file HEAD:content.t)" + ) +' + +test_expect_success 'setup repo with misnamed object' ' + git init misnamed && + ( + cd misnamed && + test_commit content && + good=$(obj_to_file HEAD:content.t) && + blob=$(echo corrupt | git hash-object -w --stdin) && + bad=$(obj_to_file $blob) && + rm -f "$good" && + mv "$bad" "$good" + ) +' + +test_expect_success 'streaming a corrupt blob fails' ' + ( + cd bit-error && + test_must_fail git cat-file blob HEAD:content.t + ) +' + +test_expect_success 'read-tree -u detects bit-errors in blobs' ' + ( + cd bit-error && + rm -f content.t && + test_must_fail git read-tree --reset -u HEAD + ) +' + +test_expect_success 'read-tree -u detects missing objects' ' + ( + cd missing && + rm -f content.t && + test_must_fail git read-tree --reset -u HEAD + ) +' + +# We use --bare to make sure that the transport detects it, not the checkout +# phase. +test_expect_success 'clone --no-local --bare detects corruption' ' + test_must_fail git clone --no-local --bare bit-error corrupt-transport +' + +test_expect_success 'clone --no-local --bare detects missing object' ' + test_must_fail git clone --no-local --bare missing missing-transport +' + +test_expect_success 'clone --no-local --bare detects misnamed object' ' + test_must_fail git clone --no-local --bare misnamed misnamed-transport +' + +# We do not expect --local to detect corruption at the transport layer, +# so we are really checking the checkout() code path. +test_expect_success 'clone --local detects corruption' ' + test_must_fail git clone --local bit-error corrupt-checkout +' + +test_expect_success 'error detected during checkout leaves repo intact' ' + test_path_is_dir corrupt-checkout/.git +' + +test_expect_success 'clone --local detects missing objects' ' + test_must_fail git clone --local missing missing-checkout +' + +test_expect_failure 'clone --local detects misnamed objects' ' + test_must_fail git clone --local misnamed misnamed-checkout +' + +test_done diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh index 3c96fda548..c4a7d84f46 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -1087,4 +1087,39 @@ test_expect_success 'barf on incomplete string' ' grep " line 3 " error ' +# good section hygiene +test_expect_failure 'unsetting the last key in a section removes header' ' + cat >.git/config <<-\EOF && + # some generic comment on the configuration file itself + # a comment specific to this "section" section. + [section] + # some intervening lines + # that should also be dropped + + key = value + # please be careful when you update the above variable + EOF + + cat >expect <<-\EOF && + # some generic comment on the configuration file itself + EOF + + git config --unset section.key && + test_cmp expect .git/config +' + +test_expect_failure 'adding a key into an empty section reuses header' ' + cat >.git/config <<-\EOF && + [section] + EOF + + q_to_tab >expect <<-\EOF && + [section] + Qkey = value + EOF + + git config section.key value + test_cmp expect .git/config +' + test_done diff --git a/t/t2022-checkout-paths.sh b/t/t2022-checkout-paths.sh index 56090d2eba..8e3545d868 100755 --- a/t/t2022-checkout-paths.sh +++ b/t/t2022-checkout-paths.sh @@ -39,4 +39,26 @@ test_expect_success 'checking out paths out of a tree does not clobber unrelated test_cmp expect.next2 dir/next2 ' +test_expect_success 'do not touch unmerged entries matching $path but not in $tree' ' + git checkout next && + git reset --hard && + + cat dir/common >expect.common && + EMPTY_SHA1=$(git hash-object -w --stdin </dev/null) && + git rm dir/next0 && + cat >expect.next0 <<-EOF && + 100644 $EMPTY_SHA1 1 dir/next0 + 100644 $EMPTY_SHA1 2 dir/next0 + EOF + git update-index --index-info <expect.next0 && + + git checkout master dir && + + test_cmp expect.common dir/common && + test_path_is_file dir/master && + git diff --exit-code master dir/master && + git ls-files -s dir/next0 >actual.next0 && + test_cmp expect.next0 actual.next0 +' + test_done diff --git a/t/t2030-unresolve-info.sh b/t/t2030-unresolve-info.sh index f2620650ce..309199bca2 100755 --- a/t/t2030-unresolve-info.sh +++ b/t/t2030-unresolve-info.sh @@ -44,14 +44,21 @@ prime_resolve_undo () { test_expect_success setup ' mkdir fi && + printf "a\0a" >binary && + git add binary && test_commit initial fi/le first && git branch side && git branch another && + printf "a\0b" >binary && + git add binary && test_commit second fi/le second && git checkout side && test_commit third fi/le third && + git branch add-add && git checkout another && test_commit fourth fi/le fourth && + git checkout add-add && + test_commit fifth add-differently && git checkout master ' @@ -167,4 +174,22 @@ test_expect_success 'rerere and rerere forget (subdirectory)' ' test_cmp expect actual ' +test_expect_success 'rerere forget (binary)' ' + git checkout -f side && + printf "a\0c" >binary && + git commit -a -m binary && + test_must_fail git merge second && + git rerere forget binary +' + +test_expect_success 'rerere forget (add-add conflict)' ' + git checkout -f master && + echo master >add-differently && + git add add-differently && + git commit -m "add differently" && + test_must_fail git merge fifth && + git rerere forget add-differently 2>actual && + test_i18ngrep "no remembered" actual +' + test_done diff --git a/t/t2200-add-update.sh b/t/t2200-add-update.sh index 4cdebda6a5..c317254b9a 100755 --- a/t/t2200-add-update.sh +++ b/t/t2200-add-update.sh @@ -80,6 +80,22 @@ test_expect_success 'change gets noticed' ' ' +# Note that this is scheduled to change in Git 2.0, when +# "git add -u" will become full-tree by default. +test_expect_success 'non-limited update in subdir leaves root alone' ' + ( + cd dir1 && + echo even more >>sub2 && + git add -u + ) && + cat >expect <<-\EOF && + check + top + EOF + git diff-files --name-only >actual && + test_cmp expect actual +' + test_expect_success SYMLINKS 'replace a file with a symlink' ' rm foo && diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 12f1e4a63c..d969f0ecd8 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -7,20 +7,18 @@ test_description='git branch assorted tests' . ./test-lib.sh -test_expect_success \ - 'prepare a trivial repository' \ - 'echo Hello > A && - git update-index --add A && - git commit -m "Initial commit." && - echo World >> A && - git update-index --add A && - git commit -m "Second commit." && - HEAD=$(git rev-parse --verify HEAD)' - -test_expect_success \ - 'git branch --help should not have created a bogus branch' ' - test_might_fail git branch --help </dev/null >/dev/null 2>/dev/null && - test_path_is_missing .git/refs/heads/--help +test_expect_success 'prepare a trivial repository' ' + echo Hello >A && + git update-index --add A && + git commit -m "Initial commit." && + echo World >>A && + git update-index --add A && + git commit -m "Second commit." && + HEAD=$(git rev-parse --verify HEAD)' + +test_expect_success 'git branch --help should not have created a bogus branch' ' + test_might_fail git branch --help </dev/null >/dev/null 2>/dev/null && + test_path_is_missing .git/refs/heads/--help ' test_expect_success 'branch -h in broken repository' ' @@ -34,67 +32,67 @@ test_expect_success 'branch -h in broken repository' ' test_i18ngrep "[Uu]sage" broken/usage ' -test_expect_success \ - 'git branch abc should create a branch' \ - 'git branch abc && test_path_is_file .git/refs/heads/abc' +test_expect_success 'git branch abc should create a branch' ' + git branch abc && test_path_is_file .git/refs/heads/abc +' -test_expect_success \ - 'git branch a/b/c should create a branch' \ - 'git branch a/b/c && test_path_is_file .git/refs/heads/a/b/c' +test_expect_success 'git branch a/b/c should create a branch' ' + git branch a/b/c && test_path_is_file .git/refs/heads/a/b/c +' -test_expect_success \ - 'git branch HEAD should fail' \ - 'test_must_fail git branch HEAD' +test_expect_success 'git branch HEAD should fail' ' + test_must_fail git branch HEAD +' cat >expect <<EOF $_z40 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master EOF -test_expect_success \ - 'git branch -l d/e/f should create a branch and a log' \ - 'GIT_COMMITTER_DATE="2005-05-26 23:30" \ - git branch -l d/e/f && - test_path_is_file .git/refs/heads/d/e/f && - test_path_is_file .git/logs/refs/heads/d/e/f && - test_cmp expect .git/logs/refs/heads/d/e/f' - -test_expect_success \ - 'git branch -d d/e/f should delete a branch and a log' \ - 'git branch -d d/e/f && - test_path_is_missing .git/refs/heads/d/e/f && - test_path_is_missing .git/logs/refs/heads/d/e/f' - -test_expect_success \ - 'git branch j/k should work after branch j has been deleted' \ - 'git branch j && - git branch -d j && - git branch j/k' - -test_expect_success \ - 'git branch l should work after branch l/m has been deleted' \ - 'git branch l/m && - git branch -d l/m && - git branch l' - -test_expect_success \ - 'git branch -m dumps usage' \ - 'test_expect_code 128 git branch -m 2>err && - test_i18ngrep "too many branches for a rename operation" err' - -test_expect_success \ - 'git branch -m m m/m should work' \ - 'git branch -l m && - git branch -m m m/m && - test_path_is_file .git/logs/refs/heads/m/m' - -test_expect_success \ - 'git branch -m n/n n should work' \ - 'git branch -l n/n && +test_expect_success 'git branch -l d/e/f should create a branch and a log' ' + GIT_COMMITTER_DATE="2005-05-26 23:30" \ + git branch -l d/e/f && + test_path_is_file .git/refs/heads/d/e/f && + test_path_is_file .git/logs/refs/heads/d/e/f && + test_cmp expect .git/logs/refs/heads/d/e/f +' + +test_expect_success 'git branch -d d/e/f should delete a branch and a log' ' + git branch -d d/e/f && + test_path_is_missing .git/refs/heads/d/e/f && + test_path_is_missing .git/logs/refs/heads/d/e/f +' + +test_expect_success 'git branch j/k should work after branch j has been deleted' ' + git branch j && + git branch -d j && + git branch j/k +' + +test_expect_success 'git branch l should work after branch l/m has been deleted' ' + git branch l/m && + git branch -d l/m && + git branch l +' + +test_expect_success 'git branch -m dumps usage' ' + test_expect_code 128 git branch -m 2>err && + test_i18ngrep "branch name required" err +' + +test_expect_success 'git branch -m m m/m should work' ' + git branch -l m && + git branch -m m m/m && + test_path_is_file .git/logs/refs/heads/m/m +' + +test_expect_success 'git branch -m n/n n should work' ' + git branch -l n/n && git branch -m n/n n && - test_path_is_file .git/logs/refs/heads/n' + test_path_is_file .git/logs/refs/heads/n +' test_expect_success 'git branch -m o/o o should fail when o/p exists' ' git branch o/o && - git branch o/p && + git branch o/p && test_must_fail git branch -m o/o o ' @@ -252,19 +250,20 @@ mv .git/config-saved .git/config git config branch.s/s.dummy Hello -test_expect_success \ - 'git branch -m s/s s should work when s/t is deleted' \ - 'git branch -l s/s && +test_expect_success 'git branch -m s/s s should work when s/t is deleted' ' + git branch -l s/s && test_path_is_file .git/logs/refs/heads/s/s && - git branch -l s/t && + git branch -l s/t && test_path_is_file .git/logs/refs/heads/s/t && - git branch -d s/t && - git branch -m s/s s && - test_path_is_file .git/logs/refs/heads/s' + git branch -d s/t && + git branch -m s/s s && + test_path_is_file .git/logs/refs/heads/s +' -test_expect_success 'config information was renamed, too' \ - "test $(git config branch.s.dummy) = Hello && - test_must_fail git config branch.s/s/dummy" +test_expect_success 'config information was renamed, too' ' + test $(git config branch.s.dummy) = Hello && + test_must_fail git config branch.s/s/dummy +' test_expect_success 'deleting a symref' ' git branch target && @@ -285,8 +284,7 @@ test_expect_success 'deleting a dangling symref' ' test_i18ncmp expect actual ' -test_expect_success 'renaming a symref is not allowed' \ -' +test_expect_success 'renaming a symref is not allowed' ' git symbolic-ref refs/heads/master2 refs/heads/master && test_must_fail git branch -m master2 master3 && git symbolic-ref refs/heads/master2 && @@ -294,163 +292,191 @@ test_expect_success 'renaming a symref is not allowed' \ test_path_is_missing .git/refs/heads/master3 ' -test_expect_success SYMLINKS \ - 'git branch -m u v should fail when the reflog for u is a symlink' ' - git branch -l u && - mv .git/logs/refs/heads/u real-u && - ln -s real-u .git/logs/refs/heads/u && - test_must_fail git branch -m u v -' - -test_expect_success 'test tracking setup via --track' \ - 'git config remote.local.url . && - git config remote.local.fetch refs/heads/*:refs/remotes/local/* && - (git show-ref -q refs/remotes/local/master || git fetch local) && - git branch --track my1 local/master && - test $(git config branch.my1.remote) = local && - test $(git config branch.my1.merge) = refs/heads/master' - -test_expect_success 'test tracking setup (non-wildcard, matching)' \ - 'git config remote.local.url . && - git config remote.local.fetch refs/heads/master:refs/remotes/local/master && - (git show-ref -q refs/remotes/local/master || git fetch local) && - git branch --track my4 local/master && - test $(git config branch.my4.remote) = local && - test $(git config branch.my4.merge) = refs/heads/master' - -test_expect_success 'test tracking setup (non-wildcard, not matching)' \ - 'git config remote.local.url . && - git config remote.local.fetch refs/heads/s:refs/remotes/local/s && - (git show-ref -q refs/remotes/local/master || git fetch local) && - git branch --track my5 local/master && - ! test "$(git config branch.my5.remote)" = local && - ! test "$(git config branch.my5.merge)" = refs/heads/master' - -test_expect_success 'test tracking setup via config' \ - 'git config branch.autosetupmerge true && - git config remote.local.url . && - git config remote.local.fetch refs/heads/*:refs/remotes/local/* && - (git show-ref -q refs/remotes/local/master || git fetch local) && - git branch my3 local/master && - test $(git config branch.my3.remote) = local && - test $(git config branch.my3.merge) = refs/heads/master' - -test_expect_success 'test overriding tracking setup via --no-track' \ - 'git config branch.autosetupmerge true && - git config remote.local.url . && - git config remote.local.fetch refs/heads/*:refs/remotes/local/* && - (git show-ref -q refs/remotes/local/master || git fetch local) && - git branch --no-track my2 local/master && - git config branch.autosetupmerge false && - ! test "$(git config branch.my2.remote)" = local && - ! test "$(git config branch.my2.merge)" = refs/heads/master' - -test_expect_success 'no tracking without .fetch entries' \ - 'git config branch.autosetupmerge true && - git branch my6 s && - git config branch.automsetupmerge false && - test -z "$(git config branch.my6.remote)" && - test -z "$(git config branch.my6.merge)"' - -test_expect_success 'test tracking setup via --track but deeper' \ - 'git config remote.local.url . && - git config remote.local.fetch refs/heads/*:refs/remotes/local/* && - (git show-ref -q refs/remotes/local/o/o || git fetch local) && - git branch --track my7 local/o/o && - test "$(git config branch.my7.remote)" = local && - test "$(git config branch.my7.merge)" = refs/heads/o/o' - -test_expect_success 'test deleting branch deletes branch config' \ - 'git branch -d my7 && - test -z "$(git config branch.my7.remote)" && - test -z "$(git config branch.my7.merge)"' - -test_expect_success 'test deleting branch without config' \ - 'git branch my7 s && - sha1=$(git rev-parse my7 | cut -c 1-7) && - echo "Deleted branch my7 (was $sha1)." >expect && - git branch -d my7 >actual 2>&1 && - test_i18ncmp expect actual' - -test_expect_success 'test --track without .fetch entries' \ - 'git branch --track my8 && - test "$(git config branch.my8.remote)" && - test "$(git config branch.my8.merge)"' - -test_expect_success \ - 'branch from non-branch HEAD w/autosetupmerge=always' \ - 'git config branch.autosetupmerge always && - git branch my9 HEAD^ && - git config branch.autosetupmerge false' - -test_expect_success \ - 'branch from non-branch HEAD w/--track causes failure' \ - 'test_must_fail git branch --track my10 HEAD^' - -test_expect_success \ - 'branch from tag w/--track causes failure' \ - 'git tag foobar && - test_must_fail git branch --track my11 foobar' - -test_expect_success '--set-upstream-to fails on multiple branches' \ - 'test_must_fail git branch --set-upstream-to master a b c' - -test_expect_success '--set-upstream-to fails on detached HEAD' \ - 'git checkout HEAD^{} && - test_must_fail git branch --set-upstream-to master && - git checkout -' - -test_expect_success 'use --set-upstream-to modify HEAD' \ - 'test_config branch.master.remote foo && - test_config branch.master.merge foo && - git branch my12 - git branch --set-upstream-to my12 && - test "$(git config branch.master.remote)" = "." && - test "$(git config branch.master.merge)" = "refs/heads/my12"' - -test_expect_success 'use --set-upstream-to modify a particular branch' \ - 'git branch my13 - git branch --set-upstream-to master my13 && - test "$(git config branch.my13.remote)" = "." && - test "$(git config branch.my13.merge)" = "refs/heads/master"' - -test_expect_success '--unset-upstream should fail if given a non-existent branch' \ - 'test_must_fail git branch --unset-upstream i-dont-exist' - -test_expect_success 'test --unset-upstream on HEAD' \ - 'git branch my14 - test_config branch.master.remote foo && - test_config branch.master.merge foo && - git branch --set-upstream-to my14 && - git branch --unset-upstream && - test_must_fail git config branch.master.remote && - test_must_fail git config branch.master.merge && - # fail for a branch without upstream set - test_must_fail git branch --unset-upstream -' - -test_expect_success '--unset-upstream should fail on multiple branches' \ - 'test_must_fail git branch --unset-upstream a b c' - -test_expect_success '--unset-upstream should fail on detached HEAD' \ - 'git checkout HEAD^{} && - test_must_fail git branch --unset-upstream && - git checkout - -' - -test_expect_success 'test --unset-upstream on a particular branch' \ - 'git branch my15 - git branch --set-upstream-to master my14 && - git branch --unset-upstream my14 && - test_must_fail git config branch.my14.remote && - test_must_fail git config branch.my14.merge' - -test_expect_success '--set-upstream shows message when creating a new branch that exists as remote-tracking' \ - 'git update-ref refs/remotes/origin/master HEAD && - git branch --set-upstream origin/master 2>actual && - test_when_finished git update-ref -d refs/remotes/origin/master && - test_when_finished git branch -d origin/master && - cat >expected <<EOF && +test_expect_success SYMLINKS 'git branch -m u v should fail when the reflog for u is a symlink' ' + git branch -l u && + mv .git/logs/refs/heads/u real-u && + ln -s real-u .git/logs/refs/heads/u && + test_must_fail git branch -m u v +' + +test_expect_success 'test tracking setup via --track' ' + git config remote.local.url . && + git config remote.local.fetch refs/heads/*:refs/remotes/local/* && + (git show-ref -q refs/remotes/local/master || git fetch local) && + git branch --track my1 local/master && + test $(git config branch.my1.remote) = local && + test $(git config branch.my1.merge) = refs/heads/master +' + +test_expect_success 'test tracking setup (non-wildcard, matching)' ' + git config remote.local.url . && + git config remote.local.fetch refs/heads/master:refs/remotes/local/master && + (git show-ref -q refs/remotes/local/master || git fetch local) && + git branch --track my4 local/master && + test $(git config branch.my4.remote) = local && + test $(git config branch.my4.merge) = refs/heads/master +' + +test_expect_success 'test tracking setup (non-wildcard, not matching)' ' + git config remote.local.url . && + git config remote.local.fetch refs/heads/s:refs/remotes/local/s && + (git show-ref -q refs/remotes/local/master || git fetch local) && + git branch --track my5 local/master && + ! test "$(git config branch.my5.remote)" = local && + ! test "$(git config branch.my5.merge)" = refs/heads/master +' + +test_expect_success 'test tracking setup via config' ' + git config branch.autosetupmerge true && + git config remote.local.url . && + git config remote.local.fetch refs/heads/*:refs/remotes/local/* && + (git show-ref -q refs/remotes/local/master || git fetch local) && + git branch my3 local/master && + test $(git config branch.my3.remote) = local && + test $(git config branch.my3.merge) = refs/heads/master +' + +test_expect_success 'test overriding tracking setup via --no-track' ' + git config branch.autosetupmerge true && + git config remote.local.url . && + git config remote.local.fetch refs/heads/*:refs/remotes/local/* && + (git show-ref -q refs/remotes/local/master || git fetch local) && + git branch --no-track my2 local/master && + git config branch.autosetupmerge false && + ! test "$(git config branch.my2.remote)" = local && + ! test "$(git config branch.my2.merge)" = refs/heads/master +' + +test_expect_success 'no tracking without .fetch entries' ' + git config branch.autosetupmerge true && + git branch my6 s && + git config branch.automsetupmerge false && + test -z "$(git config branch.my6.remote)" && + test -z "$(git config branch.my6.merge)" +' + +test_expect_success 'test tracking setup via --track but deeper' ' + git config remote.local.url . && + git config remote.local.fetch refs/heads/*:refs/remotes/local/* && + (git show-ref -q refs/remotes/local/o/o || git fetch local) && + git branch --track my7 local/o/o && + test "$(git config branch.my7.remote)" = local && + test "$(git config branch.my7.merge)" = refs/heads/o/o +' + +test_expect_success 'test deleting branch deletes branch config' ' + git branch -d my7 && + test -z "$(git config branch.my7.remote)" && + test -z "$(git config branch.my7.merge)" +' + +test_expect_success 'test deleting branch without config' ' + git branch my7 s && + sha1=$(git rev-parse my7 | cut -c 1-7) && + echo "Deleted branch my7 (was $sha1)." >expect && + git branch -d my7 >actual 2>&1 && + test_i18ncmp expect actual +' + +test_expect_success 'test --track without .fetch entries' ' + git branch --track my8 && + test "$(git config branch.my8.remote)" && + test "$(git config branch.my8.merge)" +' + +test_expect_success 'branch from non-branch HEAD w/autosetupmerge=always' ' + git config branch.autosetupmerge always && + git branch my9 HEAD^ && + git config branch.autosetupmerge false +' + +test_expect_success 'branch from non-branch HEAD w/--track causes failure' ' + test_must_fail git branch --track my10 HEAD^ +' + +test_expect_success 'branch from tag w/--track causes failure' ' + git tag foobar && + test_must_fail git branch --track my11 foobar +' + +test_expect_success '--set-upstream-to fails on multiple branches' ' + test_must_fail git branch --set-upstream-to master a b c +' + +test_expect_success '--set-upstream-to fails on detached HEAD' ' + git checkout HEAD^{} && + test_must_fail git branch --set-upstream-to master && + git checkout - +' + +test_expect_success '--set-upstream-to fails on a missing dst branch' ' + test_must_fail git branch --set-upstream-to master does-not-exist +' + +test_expect_success '--set-upstream-to fails on a missing src branch' ' + test_must_fail git branch --set-upstream-to does-not-exist master +' + +test_expect_success '--set-upstream-to fails on a non-ref' ' + test_must_fail git branch --set-upstream-to HEAD^{} +' + +test_expect_success 'use --set-upstream-to modify HEAD' ' + test_config branch.master.remote foo && + test_config branch.master.merge foo && + git branch my12 + git branch --set-upstream-to my12 && + test "$(git config branch.master.remote)" = "." && + test "$(git config branch.master.merge)" = "refs/heads/my12" +' + +test_expect_success 'use --set-upstream-to modify a particular branch' ' + git branch my13 + git branch --set-upstream-to master my13 && + test "$(git config branch.my13.remote)" = "." && + test "$(git config branch.my13.merge)" = "refs/heads/master" +' + +test_expect_success '--unset-upstream should fail if given a non-existent branch' ' + test_must_fail git branch --unset-upstream i-dont-exist +' + +test_expect_success 'test --unset-upstream on HEAD' ' + git branch my14 + test_config branch.master.remote foo && + test_config branch.master.merge foo && + git branch --set-upstream-to my14 && + git branch --unset-upstream && + test_must_fail git config branch.master.remote && + test_must_fail git config branch.master.merge && + # fail for a branch without upstream set + test_must_fail git branch --unset-upstream +' + +test_expect_success '--unset-upstream should fail on multiple branches' ' + test_must_fail git branch --unset-upstream a b c +' + +test_expect_success '--unset-upstream should fail on detached HEAD' ' + git checkout HEAD^{} && + test_must_fail git branch --unset-upstream && + git checkout - +' + +test_expect_success 'test --unset-upstream on a particular branch' ' + git branch my15 + git branch --set-upstream-to master my14 && + git branch --unset-upstream my14 && + test_must_fail git config branch.my14.remote && + test_must_fail git config branch.my14.merge +' + +test_expect_success '--set-upstream shows message when creating a new branch that exists as remote-tracking' ' + git update-ref refs/remotes/origin/master HEAD && + git branch --set-upstream origin/master 2>actual && + test_when_finished git update-ref -d refs/remotes/origin/master && + test_when_finished git branch -d origin/master && + cat >expected <<EOF && The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to If you wanted to make '"'master'"' track '"'origin/master'"', do this: @@ -458,38 +484,38 @@ If you wanted to make '"'master'"' track '"'origin/master'"', do this: git branch -d origin/master git branch --set-upstream-to origin/master EOF - test_cmp expected actual + test_cmp expected actual ' -test_expect_success '--set-upstream with two args only shows the deprecation message' \ - 'git branch --set-upstream master my13 2>actual && - test_when_finished git branch --unset-upstream master && - cat >expected <<EOF && +test_expect_success '--set-upstream with two args only shows the deprecation message' ' + git branch --set-upstream master my13 2>actual && + test_when_finished git branch --unset-upstream master && + cat >expected <<EOF && The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to EOF - test_cmp expected actual + test_cmp expected actual ' -test_expect_success '--set-upstream with one arg only shows the deprecation message if the branch existed' \ - 'git branch --set-upstream my13 2>actual && - test_when_finished git branch --unset-upstream my13 && - cat >expected <<EOF && +test_expect_success '--set-upstream with one arg only shows the deprecation message if the branch existed' ' + git branch --set-upstream my13 2>actual && + test_when_finished git branch --unset-upstream my13 && + cat >expected <<EOF && The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to EOF - test_cmp expected actual + test_cmp expected actual ' # Keep this test last, as it changes the current branch cat >expect <<EOF $_z40 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master EOF -test_expect_success \ - 'git checkout -b g/h/i -l should create a branch and a log' \ - 'GIT_COMMITTER_DATE="2005-05-26 23:30" \ - git checkout -b g/h/i -l master && - test_path_is_file .git/refs/heads/g/h/i && - test_path_is_file .git/logs/refs/heads/g/h/i && - test_cmp expect .git/logs/refs/heads/g/h/i' +test_expect_success 'git checkout -b g/h/i -l should create a branch and a log' ' + GIT_COMMITTER_DATE="2005-05-26 23:30" \ + git checkout -b g/h/i -l master && + test_path_is_file .git/refs/heads/g/h/i && + test_path_is_file .git/logs/refs/heads/g/h/i && + test_cmp expect .git/logs/refs/heads/g/h/i +' test_expect_success 'checkout -b makes reflog by default' ' git checkout master && @@ -760,7 +786,7 @@ test_expect_success 'detect misconfigured autosetuprebase (bad value)' ' test_expect_success 'detect misconfigured autosetuprebase (no value)' ' git config --unset branch.autosetuprebase && - echo "[branch] autosetuprebase" >> .git/config && + echo "[branch] autosetuprebase" >>.git/config && test_must_fail git branch && git config --unset branch.autosetuprebase ' diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh index 76fe7e0060..ba4f98e800 100755 --- a/t/t3203-branch-output.sh +++ b/t/t3203-branch-output.sh @@ -94,13 +94,13 @@ test_expect_success 'git branch -v pattern does not show branch summaries' ' test_must_fail git branch -v branch* ' -cat >expect <<'EOF' -* (no branch) +test_expect_success 'git branch shows detached HEAD properly' ' + cat >expect <<EOF && +* (detached from $(git rev-parse --short HEAD^0)) branch-one branch-two master EOF -test_expect_success 'git branch shows detached HEAD properly' ' git checkout HEAD^0 && git branch >actual && test_i18ncmp expect actual diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index 1de0ebda25..f6cc102657 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -138,8 +138,7 @@ test_expect_success 'rebase a single mode change' ' ' test_expect_success 'rebase is not broken by diff.renames' ' - git config diff.renames copies && - test_when_finished "git config --unset diff.renames" && + test_config diff.renames copies && git checkout filemove && GIT_TRACE=1 git rebase force-3way ' diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 15dcbd42d3..a58406d12f 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -937,8 +937,7 @@ test_expect_success 'rebase --edit-todo can be used to modify todo' ' test_expect_success 'rebase -i respects core.commentchar' ' git reset --hard && git checkout E^0 && - git config core.commentchar "\\" && - test_when_finished "git config --unset core.commentchar" && + test_config core.commentchar "\\" && write_script remove-all-but-first.sh <<-\EOF && sed -e "2,\$s/^/\\\\/" "$1" >"$1.tmp" && mv "$1.tmp" "$1" diff --git a/t/t3508-cherry-pick-many-commits.sh b/t/t3508-cherry-pick-many-commits.sh index 4e7136b837..19c99d7ef1 100755 --- a/t/t3508-cherry-pick-many-commits.sh +++ b/t/t3508-cherry-pick-many-commits.sh @@ -55,6 +55,12 @@ one two" ' +test_expect_success 'cherry-pick three one two: fails' ' + git checkout -f master && + git reset --hard first && + test_must_fail git cherry-pick three one two: +' + test_expect_success 'output to keep user entertained during multi-pick' ' cat <<-\EOF >expected && [master OBJID] second diff --git a/t/t3511-cherry-pick-x.sh b/t/t3511-cherry-pick-x.sh new file mode 100755 index 0000000000..f97727975b --- /dev/null +++ b/t/t3511-cherry-pick-x.sh @@ -0,0 +1,219 @@ +#!/bin/sh + +test_description='Test cherry-pick -x and -s' + +. ./test-lib.sh + +pristine_detach () { + git cherry-pick --quit && + git checkout -f "$1^0" && + git read-tree -u --reset HEAD && + git clean -d -f -f -q -x +} + +mesg_one_line='base: commit message' + +mesg_no_footer="$mesg_one_line + +OneWordBodyThatsNotA-S-o-B" + +mesg_with_footer="$mesg_no_footer + +Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> +Signed-off-by: A.U. Thor <author@example.com> +Signed-off-by: B.U. Thor <buthor@example.com>" + +mesg_broken_footer="$mesg_no_footer + +The signed-off-by string should begin with the words Signed-off-by followed +by a colon and space, and then the signers name and email address. e.g. +Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" + +mesg_with_footer_sob="$mesg_with_footer +Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" + +mesg_with_cherry_footer="$mesg_with_footer_sob +(cherry picked from commit da39a3ee5e6b4b0d3255bfef95601890afd80709) +Tested-by: C.U. Thor <cuthor@example.com>" + + +test_expect_success setup ' + git config advice.detachedhead false && + echo unrelated >unrelated && + git add unrelated && + test_commit initial foo a && + test_commit "$mesg_one_line" foo b mesg-one-line && + git reset --hard initial && + test_commit "$mesg_no_footer" foo b mesg-no-footer && + git reset --hard initial && + test_commit "$mesg_broken_footer" foo b mesg-broken-footer && + git reset --hard initial && + test_commit "$mesg_with_footer" foo b mesg-with-footer && + git reset --hard initial && + test_commit "$mesg_with_footer_sob" foo b mesg-with-footer-sob && + git reset --hard initial && + test_commit "$mesg_with_cherry_footer" foo b mesg-with-cherry-footer && + pristine_detach initial && + test_commit conflicting unrelated +' + +test_expect_success 'cherry-pick -x inserts blank line after one line subject' ' + pristine_detach initial && + sha1=`git rev-parse mesg-one-line^0` && + git cherry-pick -x mesg-one-line && + cat <<-EOF >expect && + $mesg_one_line + + (cherry picked from commit $sha1) + EOF + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + +test_expect_success 'cherry-pick -s inserts blank line after one line subject' ' + pristine_detach initial && + git cherry-pick -s mesg-one-line && + cat <<-EOF >expect && + $mesg_one_line + + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> + EOF + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + +test_expect_success 'cherry-pick -s inserts blank line after non-conforming footer' ' + pristine_detach initial && + git cherry-pick -s mesg-broken-footer && + cat <<-EOF >expect && + $mesg_broken_footer + + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> + EOF + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + +test_expect_success 'cherry-pick -x inserts blank line when conforming footer not found' ' + pristine_detach initial && + sha1=`git rev-parse mesg-no-footer^0` && + git cherry-pick -x mesg-no-footer && + cat <<-EOF >expect && + $mesg_no_footer + + (cherry picked from commit $sha1) + EOF + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + +test_expect_success 'cherry-pick -s inserts blank line when conforming footer not found' ' + pristine_detach initial && + git cherry-pick -s mesg-no-footer && + cat <<-EOF >expect && + $mesg_no_footer + + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> + EOF + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + +test_expect_success 'cherry-pick -x -s inserts blank line when conforming footer not found' ' + pristine_detach initial && + sha1=`git rev-parse mesg-no-footer^0` && + git cherry-pick -x -s mesg-no-footer && + cat <<-EOF >expect && + $mesg_no_footer + + (cherry picked from commit $sha1) + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> + EOF + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + +test_expect_success 'cherry-pick -s adds sob when last sob doesnt match committer' ' + pristine_detach initial && + git cherry-pick -s mesg-with-footer && + cat <<-EOF >expect && + $mesg_with_footer + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> + EOF + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + +test_expect_success 'cherry-pick -x -s adds sob when last sob doesnt match committer' ' + pristine_detach initial && + sha1=`git rev-parse mesg-with-footer^0` && + git cherry-pick -x -s mesg-with-footer && + cat <<-EOF >expect && + $mesg_with_footer + (cherry picked from commit $sha1) + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> + EOF + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + +test_expect_success 'cherry-pick -s refrains from adding duplicate trailing sob' ' + pristine_detach initial && + git cherry-pick -s mesg-with-footer-sob && + cat <<-EOF >expect && + $mesg_with_footer_sob + EOF + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + +test_expect_success 'cherry-pick -x -s adds sob even when trailing sob exists for committer' ' + pristine_detach initial && + sha1=`git rev-parse mesg-with-footer-sob^0` && + git cherry-pick -x -s mesg-with-footer-sob && + cat <<-EOF >expect && + $mesg_with_footer_sob + (cherry picked from commit $sha1) + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> + EOF + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + +test_expect_success 'cherry-pick -x treats "(cherry picked from..." line as part of footer' ' + pristine_detach initial && + sha1=`git rev-parse mesg-with-cherry-footer^0` && + git cherry-pick -x mesg-with-cherry-footer && + cat <<-EOF >expect && + $mesg_with_cherry_footer + (cherry picked from commit $sha1) + EOF + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + +test_expect_success 'cherry-pick -s treats "(cherry picked from..." line as part of footer' ' + pristine_detach initial && + git cherry-pick -s mesg-with-cherry-footer && + cat <<-EOF >expect && + $mesg_with_cherry_footer + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> + EOF + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + +test_expect_success 'cherry-pick -x -s treats "(cherry picked from..." line as part of footer' ' + pristine_detach initial && + sha1=`git rev-parse mesg-with-cherry-footer^0` && + git cherry-pick -x -s mesg-with-cherry-footer && + cat <<-EOF >expect && + $mesg_with_cherry_footer + (cherry picked from commit $sha1) + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> + EOF + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh index 37bf5f13b0..0c44e9f5d0 100755 --- a/t/t3600-rm.sh +++ b/t/t3600-rm.sh @@ -622,4 +622,69 @@ test_expect_success 'rm of a populated nested submodule with a nested .git direc rm -rf submod ' +test_expect_success 'rm of d/f when d has become a non-directory' ' + rm -rf d && + mkdir d && + >d/f && + git add d && + rm -rf d && + >d && + git rm d/f && + test_must_fail git rev-parse --verify :d/f && + test_path_is_file d +' + +test_expect_success SYMLINKS 'rm of d/f when d has become a dangling symlink' ' + rm -rf d && + mkdir d && + >d/f && + git add d && + rm -rf d && + ln -s nonexistent d && + git rm d/f && + test_must_fail git rev-parse --verify :d/f && + test -h d && + test_path_is_missing d +' + +test_expect_success 'rm of file when it has become a directory' ' + rm -rf d && + >d && + git add d && + rm -f d && + mkdir d && + >d/f && + test_must_fail git rm d && + git rev-parse --verify :d && + test_path_is_file d/f +' + +test_expect_success SYMLINKS 'rm across a symlinked leading path (no index)' ' + rm -rf d e && + mkdir e && + echo content >e/f && + ln -s e d && + git add -A e d && + git commit -m "symlink d to e, e/f exists" && + test_must_fail git rm d/f && + git rev-parse --verify :d && + git rev-parse --verify :e/f && + test -h d && + test_path_is_file e/f +' + +test_expect_failure SYMLINKS 'rm across a symlinked leading path (w/ index)' ' + rm -rf d e && + mkdir d && + echo content >d/f && + git add -A e d && + git commit -m "d/f exists" && + mv d e && + ln -s e d && + test_must_fail git rm d/f && + git rev-parse --verify :d/f && + test -h d && + test_path_is_file e/f +' + test_done diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 098a6ae4a0..9fab25cc96 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -319,7 +319,7 @@ test_expect_success PERL 'split hunk "add -p (edit)"' ' # times to get out. # # 2. Correct version applies the (not)edited version, and asks - # about the next hunk, against wich we say q and program + # about the next hunk, against which we say q and program # exits. for a in s e q n q q do diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index bb1fc47fe8..58d418098d 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -742,21 +742,21 @@ test_expect_success 'format-patch --signature --cover-letter' ' test 2 = $(grep "my sig" output | wc -l) ' -test_expect_success 'format.signature="" supresses signatures' ' +test_expect_success 'format.signature="" suppresses signatures' ' git config format.signature "" && git format-patch --stdout -1 >output && check_patch output && ! grep "^-- \$" output ' -test_expect_success 'format-patch --no-signature supresses signatures' ' +test_expect_success 'format-patch --no-signature suppresses signatures' ' git config --unset-all format.signature && git format-patch --stdout --no-signature -1 >output && check_patch output && ! grep "^-- \$" output ' -test_expect_success 'format-patch --signature="" supresses signatures' ' +test_expect_success 'format-patch --signature="" suppresses signatures' ' git format-patch --stdout --signature="" -1 >output && check_patch output && ! grep "^-- \$" output @@ -972,6 +972,268 @@ test_expect_success 'empty subject prefix does not have extra space' ' test_cmp expect actual ' +append_signoff() +{ + C=$(git commit-tree HEAD^^{tree} -p HEAD) && + git format-patch --stdout --signoff $C^..$C >append_signoff.patch && + sed -n -e "1,/^---$/p" append_signoff.patch | + egrep -n "^Subject|Sign|^$" +} + +test_expect_success 'signoff: commit with no body' ' + append_signoff </dev/null >actual && + cat <<\EOF | sed "s/EOL$//" >expected && +4:Subject: [PATCH] EOL +8: +9:Signed-off-by: C O Mitter <committer@example.com> +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: commit with only subject' ' + echo subject | append_signoff >actual && + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +9:Signed-off-by: C O Mitter <committer@example.com> +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: commit with only subject that does not end with NL' ' + printf subject | append_signoff >actual && + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +9:Signed-off-by: C O Mitter <committer@example.com> +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: no existing signoffs' ' + append_signoff <<\EOF >actual && +subject + +body +EOF + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +10: +11:Signed-off-by: C O Mitter <committer@example.com> +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: no existing signoffs and no trailing NL' ' + printf "subject\n\nbody" | append_signoff >actual && + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +10: +11:Signed-off-by: C O Mitter <committer@example.com> +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: some random signoff' ' + append_signoff <<\EOF >actual && +subject + +body + +Signed-off-by: my@house +EOF + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +10: +11:Signed-off-by: my@house +12:Signed-off-by: C O Mitter <committer@example.com> +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: misc conforming footer elements' ' + append_signoff <<\EOF >actual && +subject + +body + +Signed-off-by: my@house +(cherry picked from commit da39a3ee5e6b4b0d3255bfef95601890afd80709) +Tested-by: Some One <someone@example.com> +Bug: 1234 +EOF + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +10: +11:Signed-off-by: my@house +15:Signed-off-by: C O Mitter <committer@example.com> +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: some random signoff-alike' ' + append_signoff <<\EOF >actual && +subject + +body +Fooled-by-me: my@house +EOF + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +11: +12:Signed-off-by: C O Mitter <committer@example.com> +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: not really a signoff' ' + append_signoff <<\EOF >actual && +subject + +I want to mention about Signed-off-by: here. +EOF + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +9:I want to mention about Signed-off-by: here. +10: +11:Signed-off-by: C O Mitter <committer@example.com> +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: not really a signoff (2)' ' + append_signoff <<\EOF >actual && +subject + +My unfortunate +Signed-off-by: example happens to be wrapped here. +EOF + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +10:Signed-off-by: example happens to be wrapped here. +11: +12:Signed-off-by: C O Mitter <committer@example.com> +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: valid S-o-b paragraph in the middle' ' + append_signoff <<\EOF >actual && +subject + +Signed-off-by: my@house +Signed-off-by: your@house + +A lot of houses. +EOF + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +9:Signed-off-by: my@house +10:Signed-off-by: your@house +11: +13: +14:Signed-off-by: C O Mitter <committer@example.com> +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: the same signoff at the end' ' + append_signoff <<\EOF >actual && +subject + +body + +Signed-off-by: C O Mitter <committer@example.com> +EOF + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +10: +11:Signed-off-by: C O Mitter <committer@example.com> +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: the same signoff at the end, no trailing NL' ' + printf "subject\n\nSigned-off-by: C O Mitter <committer@example.com>" | + append_signoff >actual && + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +9:Signed-off-by: C O Mitter <committer@example.com> +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: the same signoff NOT at the end' ' + append_signoff <<\EOF >actual && +subject + +body + +Signed-off-by: C O Mitter <committer@example.com> +Signed-off-by: my@house +EOF + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +10: +11:Signed-off-by: C O Mitter <committer@example.com> +12:Signed-off-by: my@house +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: detect garbage in non-conforming footer' ' + append_signoff <<\EOF >actual && +subject + +body + +Tested-by: my@house +Some Trash +Signed-off-by: C O Mitter <committer@example.com> +EOF + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +10: +13:Signed-off-by: C O Mitter <committer@example.com> +14: +15:Signed-off-by: C O Mitter <committer@example.com> +EOF + test_cmp expected actual +' + +test_expect_success 'signoff: footer begins with non-signoff without @ sign' ' + append_signoff <<\EOF >actual && +subject + +body + +Reviewed-id: Noone +Tested-by: my@house +Change-id: Ideadbeef +Signed-off-by: C O Mitter <committer@example.com> +Bug: 1234 +EOF + cat >expected <<\EOF && +4:Subject: [PATCH] subject +8: +10: +14:Signed-off-by: C O Mitter <committer@example.com> +EOF + test_cmp expected actual +' + test_expect_success 'format patch ignores color.ui' ' test_unconfig color.ui && git format-patch --stdout -1 >expect && @@ -1022,4 +1284,37 @@ test_expect_success 'cover letter using branch description (6)' ' grep hello actual >/dev/null ' +test_expect_success 'cover letter with nothing' ' + git format-patch --stdout --cover-letter >actual && + test_line_count = 0 actual +' + +test_expect_success 'cover letter auto' ' + mkdir -p tmp && + test_when_finished "rm -rf tmp; + git config --unset format.coverletter" && + + git config format.coverletter auto && + git format-patch -o tmp -1 >list && + test_line_count = 1 list && + git format-patch -o tmp -2 >list && + test_line_count = 3 list +' + +test_expect_success 'cover letter auto user override' ' + mkdir -p tmp && + test_when_finished "rm -rf tmp; + git config --unset format.coverletter" && + + git config format.coverletter auto && + git format-patch -o tmp --cover-letter -1 >list && + test_line_count = 2 list && + git format-patch -o tmp --cover-letter -2 >list && + test_line_count = 3 list && + git format-patch -o tmp --no-cover-letter -1 >list && + test_line_count = 1 list && + git format-patch -o tmp --no-cover-letter -2 >list && + test_line_count = 2 list +' + test_done diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh index 40ab333a8a..f2f55fc51c 100755 --- a/t/t4034-diff-words.sh +++ b/t/t4034-diff-words.sh @@ -230,7 +230,7 @@ test_expect_success '.gitattributes override config' ' ' test_expect_success 'setup: remove diff driver regex' ' - test_might_fail git config --unset diff.testdriver.wordRegex + test_unconfig diff.testdriver.wordRegex ' test_expect_success 'use configured regex' ' @@ -335,8 +335,7 @@ test_expect_success 'word-diff with diff.sbe' ' c EOF - test_when_finished "git config --unset diff.suppress-blank-empty" && - git config diff.suppress-blank-empty true && + test_config diff.suppress-blank-empty true && word_diff --word-diff=plain ' @@ -368,7 +367,7 @@ test_expect_success 'setup history with two files' ' test_expect_success 'wordRegex for the first file does not apply to the second' ' echo "*.tex diff=tex" >.gitattributes && - git config diff.tex.wordRegex "[a-z]+|." && + test_config diff.tex.wordRegex "[a-z]+|." && cat >expect <<-\EOF && diff --git a/a.tex b/a.tex --- a/a.tex diff --git a/t/t4038-diff-combined.sh b/t/t4038-diff-combined.sh index 614425adac..1261dbbdf5 100755 --- a/t/t4038-diff-combined.sh +++ b/t/t4038-diff-combined.sh @@ -3,6 +3,7 @@ test_description='combined diff' . ./test-lib.sh +. "$TEST_DIRECTORY"/diff-lib.sh setup_helper () { one=$1 branch=$2 side=$3 && @@ -113,4 +114,243 @@ test_expect_success 'check --cc --raw with forty trees' ' grep "^::::::::::::::::::::::::::::::::::::::::[^:]" out ' +test_expect_success 'setup combined ignore spaces' ' + git checkout master && + >test && + git add test && + git commit -m initial && + + tr -d Q <<-\EOF >test && + always coalesce + eol space coalesce Q + space change coalesce + all spa ces coalesce + eol spaces Q + space change + all spa ces + EOF + git commit -m "test space change" -a && + + git checkout -b side HEAD^ && + tr -d Q <<-\EOF >test && + always coalesce + eol space coalesce + space change coalesce + all spaces coalesce + eol spaces + space change + all spaces + EOF + git commit -m "test other space changes" -a && + + test_must_fail git merge master && + tr -d Q <<-\EOF >test && + eol spaces Q + space change + all spa ces + EOF + git commit -m merged -a +' + +test_expect_success 'check combined output (no ignore space)' ' + git show >actual.tmp && + sed -e "1,/^@@@/d" < actual.tmp >actual && + tr -d Q <<-\EOF >expected && + --always coalesce + - eol space coalesce + - space change coalesce + - all spaces coalesce + - eol spaces + - space change + - all spaces + -eol space coalesce Q + -space change coalesce + -all spa ces coalesce + + eol spaces Q + + space change + + all spa ces + EOF + compare_diff_patch expected actual +' + +test_expect_success 'check combined output (ignore space at eol)' ' + git show --ignore-space-at-eol >actual.tmp && + sed -e "1,/^@@@/d" < actual.tmp >actual && + tr -d Q <<-\EOF >expected && + --always coalesce + --eol space coalesce + - space change coalesce + - all spaces coalesce + -space change coalesce + -all spa ces coalesce + eol spaces Q + - space change + - all spaces + + space change + + all spa ces + EOF + compare_diff_patch expected actual +' + +test_expect_success 'check combined output (ignore space change)' ' + git show -b >actual.tmp && + sed -e "1,/^@@@/d" < actual.tmp >actual && + tr -d Q <<-\EOF >expected && + --always coalesce + --eol space coalesce + --space change coalesce + - all spaces coalesce + -all spa ces coalesce + eol spaces Q + space change + - all spaces + + all spa ces + EOF + compare_diff_patch expected actual +' + +test_expect_success 'check combined output (ignore all spaces)' ' + git show -w >actual.tmp && + sed -e "1,/^@@@/d" < actual.tmp >actual && + tr -d Q <<-\EOF >expected && + --always coalesce + --eol space coalesce + --space change coalesce + --all spaces coalesce + eol spaces Q + space change + all spa ces + EOF + compare_diff_patch expected actual +' + +test_expect_success 'combine diff coalesce simple' ' + >test && + git add test && + git commit -m initial && + test_seq 4 >test && + git commit -a -m empty1 && + git branch side1 && + git checkout HEAD^ && + test_seq 5 >test && + git commit -a -m empty2 && + test_must_fail git merge side1 && + >test && + git commit -a -m merge && + git show >actual.tmp && + sed -e "1,/^@@@/d" < actual.tmp >actual && + tr -d Q <<-\EOF >expected && + --1 + --2 + --3 + --4 + - 5 + EOF + compare_diff_patch expected actual +' + +test_expect_success 'combine diff coalesce tricky' ' + >test && + git add test && + git commit -m initial --allow-empty && + cat <<-\EOF >test && + 3 + 1 + 2 + 3 + 4 + EOF + git commit -a -m empty1 && + git branch -f side1 && + git checkout HEAD^ && + cat <<-\EOF >test && + 1 + 3 + 5 + 4 + EOF + git commit -a -m empty2 && + git branch -f side2 && + test_must_fail git merge side1 && + >test && + git commit -a -m merge && + git show >actual.tmp && + sed -e "1,/^@@@/d" < actual.tmp >actual && + tr -d Q <<-\EOF >expected && + -3 + --1 + -2 + --3 + - 5 + --4 + EOF + compare_diff_patch expected actual && + git checkout -f side1 && + test_must_fail git merge side2 && + >test && + git commit -a -m merge && + git show >actual.tmp && + sed -e "1,/^@@@/d" < actual.tmp >actual && + tr -d Q <<-\EOF >expected && + - 3 + --1 + - 2 + --3 + -5 + --4 + EOF + compare_diff_patch expected actual +' + +test_expect_failure 'combine diff coalesce three parents' ' + >test && + git add test && + git commit -m initial --allow-empty && + cat <<-\EOF >test && + 3 + 1 + 2 + 3 + 4 + EOF + git commit -a -m empty1 && + git checkout -B side1 && + git checkout HEAD^ && + cat <<-\EOF >test && + 1 + 3 + 7 + 5 + 4 + EOF + git commit -a -m empty2 && + git branch -f side2 && + git checkout HEAD^ && + cat <<-\EOF >test && + 3 + 1 + 6 + 5 + 4 + EOF + git commit -a -m empty3 && + >test && + git add test && + TREE=$(git write-tree) && + COMMIT=$(git commit-tree -p HEAD -p side1 -p side2 -m merge $TREE) && + git show $COMMIT >actual.tmp && + sed -e "1,/^@@@/d" < actual.tmp >actual && + tr -d Q <<-\EOF >expected && + -- 3 + ---1 + - 6 + - 2 + --3 + -7 + - -5 + ---4 + EOF + compare_diff_patch expected actual +' + test_done diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh index 6f6ee88b28..5d0c598338 100755 --- a/t/t4124-apply-ws-rule.sh +++ b/t/t4124-apply-ws-rule.sh @@ -47,7 +47,7 @@ test_fix () { # find touched lines $DIFF file target | sed -n -e "s/^> //p" >fixed - # the changed lines are all expeced to change + # the changed lines are all expected to change fixed_cnt=$(wc -l <fixed) case "$1" in '') expect_cnt=$fixed_cnt ;; @@ -486,4 +486,30 @@ test_expect_success 'same, but with CR-LF line endings && cr-at-eol unset' ' test_cmp one expect ' +test_expect_success 'whitespace=fix to expand' ' + qz_to_tab_space >preimage <<-\EOF && + QQa + QQb + QQc + ZZZZZZZZZZZZZZZZd + QQe + QQf + QQg + EOF + qz_to_tab_space >patch <<-\EOF && + diff --git a/preimage b/preimage + --- a/preimage + +++ b/preimage + @@ -1,7 +1,6 @@ + QQa + QQb + QQc + -QQd + QQe + QQf + QQg + EOF + git -c core.whitespace=tab-in-indent apply --whitespace=fix patch +' + test_done diff --git a/t/t4150-am.sh b/t/t4150-am.sh index cdafd7e7c1..12f6b027ac 100755 --- a/t/t4150-am.sh +++ b/t/t4150-am.sh @@ -17,7 +17,7 @@ test_expect_success 'setup: messages' ' vero eos et accusam et justo duo dolores et ea rebum. EOF - q_to_tab <<-\EOF >>msg && + qz_to_tab_space <<-\EOF >>msg && QDuis autem vel eum iriure dolor in hendrerit in vulputate velit Qesse molestie consequat, vel illum dolore eu feugiat nulla facilisis Qat vero eros et accumsan et iusto odio dignissim qui blandit diff --git a/t/t4202-log.sh b/t/t4202-log.sh index fa686b887d..9243a97993 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -419,8 +419,6 @@ test_expect_success 'log --graph with merge' ' ' test_expect_success 'log.decorate configuration' ' - test_might_fail git config --unset-all log.decorate && - git log --oneline >expect.none && git log --oneline --decorate >expect.short && git log --oneline --decorate=full >expect.full && @@ -429,8 +427,7 @@ test_expect_success 'log.decorate configuration' ' git log --oneline >actual && test_cmp expect.short actual && - git config --unset-all log.decorate && - git config log.decorate true && + test_config log.decorate true && git log --oneline >actual && test_cmp expect.short actual && git log --oneline --decorate=full >actual && @@ -438,8 +435,7 @@ test_expect_success 'log.decorate configuration' ' git log --oneline --decorate=no >actual && test_cmp expect.none actual && - git config --unset-all log.decorate && - git config log.decorate no && + test_config log.decorate no && git log --oneline >actual && test_cmp expect.none actual && git log --oneline --decorate >actual && @@ -447,8 +443,7 @@ test_expect_success 'log.decorate configuration' ' git log --oneline --decorate=full >actual && test_cmp expect.full actual && - git config --unset-all log.decorate && - git config log.decorate 1 && + test_config log.decorate 1 && git log --oneline >actual && test_cmp expect.short actual && git log --oneline --decorate=full >actual && @@ -456,8 +451,7 @@ test_expect_success 'log.decorate configuration' ' git log --oneline --decorate=no >actual && test_cmp expect.none actual && - git config --unset-all log.decorate && - git config log.decorate short && + test_config log.decorate short && git log --oneline >actual && test_cmp expect.short actual && git log --oneline --no-decorate >actual && @@ -465,8 +459,7 @@ test_expect_success 'log.decorate configuration' ' git log --oneline --decorate=full >actual && test_cmp expect.full actual && - git config --unset-all log.decorate && - git config log.decorate full && + test_config log.decorate full && git log --oneline >actual && test_cmp expect.full actual && git log --oneline --no-decorate >actual && @@ -474,16 +467,15 @@ test_expect_success 'log.decorate configuration' ' git log --oneline --decorate >actual && test_cmp expect.short actual - git config --unset-all log.decorate && + test_unconfig log.decorate && git log --pretty=raw >expect.raw && - git config log.decorate full && + test_config log.decorate full && git log --pretty=raw >actual && test_cmp expect.raw actual ' test_expect_success 'reflog is expected format' ' - test_might_fail git config --remove-section log && git log -g --abbrev-commit --pretty=oneline >expect && git reflog >actual && test_cmp expect actual @@ -496,10 +488,6 @@ test_expect_success 'whatchanged is expected format' ' ' test_expect_success 'log.abbrevCommit configuration' ' - test_when_finished "git config --unset log.abbrevCommit" && - - test_might_fail git config --unset log.abbrevCommit && - git log --abbrev-commit >expect.log.abbrev && git log --no-abbrev-commit >expect.log.full && git log --pretty=raw >expect.log.raw && @@ -508,7 +496,7 @@ test_expect_success 'log.abbrevCommit configuration' ' git whatchanged --abbrev-commit >expect.whatchanged.abbrev && git whatchanged --no-abbrev-commit >expect.whatchanged.full && - git config log.abbrevCommit true && + test_config log.abbrevCommit true && git log >actual && test_cmp expect.log.abbrev actual && diff --git a/t/t4209-log-pickaxe.sh b/t/t4209-log-pickaxe.sh index eed727341d..38fb80f643 100755 --- a/t/t4209-log-pickaxe.sh +++ b/t/t4209-log-pickaxe.sh @@ -80,6 +80,20 @@ test_expect_success 'log -G -i (match)' ' test_cmp expect actual ' +test_expect_success 'log -G --textconv (missing textconv tool)' ' + echo "* diff=test" >.gitattributes && + test_must_fail git -c diff.test.textconv=missing log -Gfoo && + rm .gitattributes +' + +test_expect_success 'log -G --no-textconv (missing textconv tool)' ' + echo "* diff=test" >.gitattributes && + git -c diff.test.textconv=missing log -Gfoo --no-textconv >actual && + >expect && + test_cmp expect actual && + rm .gitattributes +' + test_expect_success 'log -S (nomatch)' ' git log -Spicked --format=%H >actual && >expect && @@ -116,4 +130,18 @@ test_expect_success 'log -S -i (nomatch)' ' test_cmp expect actual ' +test_expect_success 'log -S --textconv (missing textconv tool)' ' + echo "* diff=test" >.gitattributes && + test_must_fail git -c diff.test.textconv=missing log -Sfoo && + rm .gitattributes +' + +test_expect_success 'log -S --no-textconv (missing textconv tool)' ' + echo "* diff=test" >.gitattributes && + git -c diff.test.textconv=missing log -Sfoo --no-textconv >actual && + >expect && + test_cmp expect actual && + rm .gitattributes +' + test_done diff --git a/t/t4300-merge-tree.sh b/t/t4300-merge-tree.sh index d0b2a457b8..bd43b3d8ef 100755 --- a/t/t4300-merge-tree.sh +++ b/t/t4300-merge-tree.sh @@ -26,8 +26,6 @@ EXPECTED test_expect_success 'file add !A, B' ' cat >expected <<\EXPECTED && -added in local - our 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE EXPECTED git reset --hard initial && @@ -38,9 +36,6 @@ EXPECTED test_expect_success 'file add A, B (same)' ' cat >expected <<\EXPECTED && -added in both - our 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE - their 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE EXPECTED git reset --hard initial && @@ -181,9 +176,6 @@ AAA" && test_expect_success 'file remove A, !B' ' cat >expected <<\EXPECTED && -removed in local - base 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE - their 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE EXPECTED git reset --hard initial && @@ -283,8 +275,6 @@ test_expect_success 'turn tree to file' ' test_commit "make-file" "dir" "CCC" && git merge-tree add-tree add-another-tree make-file >actual && cat >expect <<-\EOF && - added in local - our 100644 ba629238ca89489f2b350e196ca445e09d8bb834 dir/another removed in remote base 100644 43d5a8ed6ef6c00ff775008633f95787d088285d dir/path our 100644 43d5a8ed6ef6c00ff775008633f95787d088285d dir/path diff --git a/t/t5004-archive-corner-cases.sh b/t/t5004-archive-corner-cases.sh index cdb7d7a7f9..bfdb56a069 100755 --- a/t/t5004-archive-corner-cases.sh +++ b/t/t5004-archive-corner-cases.sh @@ -28,7 +28,7 @@ check_dir() { } test_expect_success 'tar archive of empty tree is empty' ' - git archive --format=tar HEAD >empty.tar && + git archive --format=tar HEAD: >empty.tar && make_dir extract && "$TAR" xf empty.tar -C extract && check_dir extract diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh index d645328609..e4bb3a1457 100755 --- a/t/t5304-prune.sh +++ b/t/t5304-prune.sh @@ -195,4 +195,30 @@ test_expect_success 'gc: prune old objects after local clone' ' ) ' +test_expect_success 'garbage report in count-objects -v' ' + : >.git/objects/pack/foo && + : >.git/objects/pack/foo.bar && + : >.git/objects/pack/foo.keep && + : >.git/objects/pack/foo.pack && + : >.git/objects/pack/fake.bar && + : >.git/objects/pack/fake.keep && + : >.git/objects/pack/fake.pack && + : >.git/objects/pack/fake.idx && + : >.git/objects/pack/fake2.keep && + : >.git/objects/pack/fake3.idx && + git count-objects -v 2>stderr && + grep "index file .git/objects/pack/fake.idx is too small" stderr && + grep "^warning:" stderr | sort >actual && + cat >expected <<\EOF && +warning: garbage found: .git/objects/pack/fake.bar +warning: garbage found: .git/objects/pack/foo +warning: garbage found: .git/objects/pack/foo.bar +warning: no corresponding .idx nor .pack: .git/objects/pack/fake2.keep +warning: no corresponding .idx: .git/objects/pack/foo.keep +warning: no corresponding .idx: .git/objects/pack/foo.pack +warning: no corresponding .pack: .git/objects/pack/fake3.idx +EOF + test_cmp expected actual +' + test_done diff --git a/t/t5404-tracking-branches.sh b/t/t5404-tracking-branches.sh index c24003565d..2b8c0bac7d 100755 --- a/t/t5404-tracking-branches.sh +++ b/t/t5404-tracking-branches.sh @@ -36,7 +36,7 @@ test_expect_success 'prepare pushable branches' ' ' test_expect_success 'mixed-success push returns error' ' - test_must_fail git push + test_must_fail git push origin : ' test_expect_success 'check tracking branches updated correctly after push' ' diff --git a/t/t5503-tagfollow.sh b/t/t5503-tagfollow.sh index 60de2d6ede..f30c03885c 100755 --- a/t/t5503-tagfollow.sh +++ b/t/t5503-tagfollow.sh @@ -4,10 +4,6 @@ test_description='test automatic tag following' . ./test-lib.sh -if ! test_have_prereq NOT_MINGW; then - say "GIT_DEBUG_SEND_PACK not supported - skipping tests" -fi - # End state of the repository: # # T - tag1 S - tag2 @@ -17,7 +13,7 @@ fi # \ C - origin/cat \ # origin/master master -test_expect_success NOT_MINGW setup ' +test_expect_success setup ' test_tick && echo ichi >file && git add file && @@ -39,28 +35,35 @@ test_expect_success NOT_MINGW setup ' ' U=UPLOAD_LOG +UPATH="$(pwd)/$U" -test_expect_success NOT_MINGW 'setup expect' ' +test_expect_success 'setup expect' ' cat - <<EOF >expect -#S want $A -#E EOF ' -test_expect_success NOT_MINGW 'fetch A (new commit : 1 connection)' ' +get_needs () { + test -s "$1" && + perl -alne ' + next unless $F[1] eq "upload-pack<"; + last if $F[2] eq "0000"; + print $F[2], " ", $F[3]; + ' "$1" +} + +test_expect_success 'fetch A (new commit : 1 connection)' ' rm -f $U && ( cd cloned && - GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U && + GIT_TRACE_PACKET=$UPATH git fetch && test $A = $(git rev-parse --verify origin/master) ) && - test -s $U && - cut -d" " -f1,2 $U >actual && + get_needs $U >actual && test_cmp expect actual ' -test_expect_success NOT_MINGW "create tag T on A, create C on branch cat" ' +test_expect_success "create tag T on A, create C on branch cat" ' git tag -a -m tag1 tag1 $A && T=$(git rev-parse --verify tag1) && @@ -72,30 +75,27 @@ test_expect_success NOT_MINGW "create tag T on A, create C on branch cat" ' git checkout master ' -test_expect_success NOT_MINGW 'setup expect' ' +test_expect_success 'setup expect' ' cat - <<EOF >expect -#S want $C want $T -#E EOF ' -test_expect_success NOT_MINGW 'fetch C, T (new branch, tag : 1 connection)' ' +test_expect_success 'fetch C, T (new branch, tag : 1 connection)' ' rm -f $U && ( cd cloned && - GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U && + GIT_TRACE_PACKET=$UPATH git fetch && test $C = $(git rev-parse --verify origin/cat) && test $T = $(git rev-parse --verify tag1) && test $A = $(git rev-parse --verify tag1^0) ) && - test -s $U && - cut -d" " -f1,2 $U >actual && + get_needs $U >actual && test_cmp expect actual ' -test_expect_success NOT_MINGW "create commits O, B, tag S on B" ' +test_expect_success "create commits O, B, tag S on B" ' test_tick && echo O >file && git add file && @@ -111,39 +111,34 @@ test_expect_success NOT_MINGW "create commits O, B, tag S on B" ' S=$(git rev-parse --verify tag2) ' -test_expect_success NOT_MINGW 'setup expect' ' +test_expect_success 'setup expect' ' cat - <<EOF >expect -#S want $B want $S -#E EOF ' -test_expect_success NOT_MINGW 'fetch B, S (commit and tag : 1 connection)' ' +test_expect_success 'fetch B, S (commit and tag : 1 connection)' ' rm -f $U && ( cd cloned && - GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U && + GIT_TRACE_PACKET=$UPATH git fetch && test $B = $(git rev-parse --verify origin/master) && test $B = $(git rev-parse --verify tag2^0) && test $S = $(git rev-parse --verify tag2) ) && - test -s $U && - cut -d" " -f1,2 $U >actual && + get_needs $U >actual && test_cmp expect actual ' -test_expect_success NOT_MINGW 'setup expect' ' +test_expect_success 'setup expect' ' cat - <<EOF >expect -#S want $B want $S -#E EOF ' -test_expect_success NOT_MINGW 'new clone fetch master and tags' ' +test_expect_success 'new clone fetch master and tags' ' git branch -D cat rm -f $U ( @@ -151,15 +146,14 @@ test_expect_success NOT_MINGW 'new clone fetch master and tags' ' cd clone2 && git init && git remote add origin .. && - GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U && + GIT_TRACE_PACKET=$UPATH git fetch && test $B = $(git rev-parse --verify origin/master) && test $S = $(git rev-parse --verify tag2) && test $B = $(git rev-parse --verify tag2^0) && test $T = $(git rev-parse --verify tag1) && test $A = $(git rev-parse --verify tag1^0) ) && - test -s $U && - cut -d" " -f1,2 $U >actual && + get_needs $U >actual && test_cmp expect actual ' diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index ccc55ebf4b..6579a86724 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -345,7 +345,7 @@ test_expect_success 'fetch mirrors do not act as mirrors during push' ' ) && (cd mirror-fetch/child && git branch -m renamed renamed2 && - git push parent + git push parent : ) && (cd mirror-fetch/parent && git rev-parse --verify renamed && diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index c31e5c1c52..4691d51b8c 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -1,16 +1,28 @@ #!/bin/sh -test_description='fetching and pushing, with or without wildcard' +test_description='Basic fetch/push functionality. + +This test checks the following functionality: + +* command-line syntax +* refspecs +* fast-forward detection, and overriding it +* configuration +* hooks +* --porcelain output format +* hiderefs +' . ./test-lib.sh D=`pwd` mk_empty () { - rm -fr testrepo && - mkdir testrepo && + repo_name="$1" + rm -fr "$repo_name" && + mkdir "$repo_name" && ( - cd testrepo && + cd "$repo_name" && git init && git config receive.denyCurrentBranch warn && mv .git/hooks .git/hooks-disabled @@ -18,32 +30,33 @@ mk_empty () { } mk_test () { - mk_empty && + repo_name="$1" + shift + + mk_empty "$repo_name" && ( for ref in "$@" do - git push testrepo $the_first_commit:refs/$ref || { - echo "Oops, push refs/$ref failure" - exit 1 - } + git push "$repo_name" $the_first_commit:refs/$ref || + exit done && - cd testrepo && + cd "$repo_name" && for ref in "$@" do - r=$(git show-ref -s --verify refs/$ref) && - test "z$r" = "z$the_first_commit" || { - echo "Oops, refs/$ref is wrong" - exit 1 - } + echo "$the_first_commit" >expect && + git show-ref -s --verify refs/$ref >actual && + test_cmp expect actual || + exit done && git fsck --full ) } mk_test_with_hooks() { + repo_name=$1 mk_test "$@" && ( - cd testrepo && + cd "$repo_name" && mkdir .git/hooks && cd .git/hooks && @@ -75,22 +88,23 @@ mk_test_with_hooks() { } mk_child() { - rm -rf "$1" && - git clone testrepo "$1" + rm -rf "$2" && + git clone "$1" "$2" } check_push_result () { + repo_name="$1" + shift + ( - cd testrepo && - it="$1" && - shift + cd "$repo_name" && + echo "$1" >expect && + shift && for ref in "$@" do - r=$(git show-ref -s --verify refs/$ref) && - test "z$r" = "z$it" || { - echo "Oops, refs/$ref is wrong" - exit 1 - } + git show-ref -s --verify refs/$ref >actual && + test_cmp expect actual || + exit done && git fsck --full ) @@ -113,35 +127,33 @@ test_expect_success setup ' ' test_expect_success 'fetch without wildcard' ' - mk_empty && + mk_empty testrepo && ( cd testrepo && git fetch .. refs/heads/master:refs/remotes/origin/master && - r=$(git show-ref -s --verify refs/remotes/origin/master) && - test "z$r" = "z$the_commit" && - - test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + echo "$the_commit commit refs/remotes/origin/master" >expect && + git for-each-ref refs/remotes/origin >actual && + test_cmp expect actual ) ' test_expect_success 'fetch with wildcard' ' - mk_empty && + mk_empty testrepo && ( cd testrepo && git config remote.up.url .. && git config remote.up.fetch "refs/heads/*:refs/remotes/origin/*" && git fetch up && - r=$(git show-ref -s --verify refs/remotes/origin/master) && - test "z$r" = "z$the_commit" && - - test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + echo "$the_commit commit refs/remotes/origin/master" >expect && + git for-each-ref refs/remotes/origin >actual && + test_cmp expect actual ) ' test_expect_success 'fetch with insteadOf' ' - mk_empty && + mk_empty testrepo && ( TRASH=$(pwd)/ && cd testrepo && @@ -150,15 +162,14 @@ test_expect_success 'fetch with insteadOf' ' git config remote.up.fetch "refs/heads/*:refs/remotes/origin/*" && git fetch up && - r=$(git show-ref -s --verify refs/remotes/origin/master) && - test "z$r" = "z$the_commit" && - - test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + echo "$the_commit commit refs/remotes/origin/master" >expect && + git for-each-ref refs/remotes/origin >actual && + test_cmp expect actual ) ' test_expect_success 'fetch with pushInsteadOf (should not rewrite)' ' - mk_empty && + mk_empty testrepo && ( TRASH=$(pwd)/ && cd testrepo && @@ -167,321 +178,310 @@ test_expect_success 'fetch with pushInsteadOf (should not rewrite)' ' git config remote.up.fetch "refs/heads/*:refs/remotes/origin/*" && git fetch up && - r=$(git show-ref -s --verify refs/remotes/origin/master) && - test "z$r" = "z$the_commit" && - - test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + echo "$the_commit commit refs/remotes/origin/master" >expect && + git for-each-ref refs/remotes/origin >actual && + test_cmp expect actual ) ' test_expect_success 'push without wildcard' ' - mk_empty && + mk_empty testrepo && git push testrepo refs/heads/master:refs/remotes/origin/master && ( cd testrepo && - r=$(git show-ref -s --verify refs/remotes/origin/master) && - test "z$r" = "z$the_commit" && - - test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + echo "$the_commit commit refs/remotes/origin/master" >expect && + git for-each-ref refs/remotes/origin >actual && + test_cmp expect actual ) ' test_expect_success 'push with wildcard' ' - mk_empty && + mk_empty testrepo && git push testrepo "refs/heads/*:refs/remotes/origin/*" && ( cd testrepo && - r=$(git show-ref -s --verify refs/remotes/origin/master) && - test "z$r" = "z$the_commit" && - - test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + echo "$the_commit commit refs/remotes/origin/master" >expect && + git for-each-ref refs/remotes/origin >actual && + test_cmp expect actual ) ' test_expect_success 'push with insteadOf' ' - mk_empty && + mk_empty testrepo && TRASH="$(pwd)/" && - git config "url.$TRASH.insteadOf" trash/ && + test_config "url.$TRASH.insteadOf" trash/ && git push trash/testrepo refs/heads/master:refs/remotes/origin/master && ( cd testrepo && - r=$(git show-ref -s --verify refs/remotes/origin/master) && - test "z$r" = "z$the_commit" && - - test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + echo "$the_commit commit refs/remotes/origin/master" >expect && + git for-each-ref refs/remotes/origin >actual && + test_cmp expect actual ) ' test_expect_success 'push with pushInsteadOf' ' - mk_empty && + mk_empty testrepo && TRASH="$(pwd)/" && - git config "url.$TRASH.pushInsteadOf" trash/ && + test_config "url.$TRASH.pushInsteadOf" trash/ && git push trash/testrepo refs/heads/master:refs/remotes/origin/master && ( cd testrepo && - r=$(git show-ref -s --verify refs/remotes/origin/master) && - test "z$r" = "z$the_commit" && - - test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + echo "$the_commit commit refs/remotes/origin/master" >expect && + git for-each-ref refs/remotes/origin >actual && + test_cmp expect actual ) ' test_expect_success 'push with pushInsteadOf and explicit pushurl (pushInsteadOf should not rewrite)' ' - mk_empty && - TRASH="$(pwd)/" && - git config "url.trash2/.pushInsteadOf" trash/ && - git config remote.r.url trash/wrong && - git config remote.r.pushurl "$TRASH/testrepo" && + mk_empty testrepo && + test_config "url.trash2/.pushInsteadOf" testrepo/ && + test_config "url.trash3/.pusnInsteadOf" trash/wrong && + test_config remote.r.url trash/wrong && + test_config remote.r.pushurl "testrepo/" && git push r refs/heads/master:refs/remotes/origin/master && ( cd testrepo && - r=$(git show-ref -s --verify refs/remotes/origin/master) && - test "z$r" = "z$the_commit" && - - test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + echo "$the_commit commit refs/remotes/origin/master" >expect && + git for-each-ref refs/remotes/origin >actual && + test_cmp expect actual ) ' test_expect_success 'push with matching heads' ' - mk_test heads/master && - git push testrepo && - check_push_result $the_commit heads/master + mk_test testrepo heads/master && + git push testrepo : && + check_push_result testrepo $the_commit heads/master ' test_expect_success 'push with matching heads on the command line' ' - mk_test heads/master && + mk_test testrepo heads/master && git push testrepo : && - check_push_result $the_commit heads/master + check_push_result testrepo $the_commit heads/master ' test_expect_success 'failed (non-fast-forward) push with matching heads' ' - mk_test heads/master && + mk_test testrepo heads/master && git push testrepo : && git commit --amend -massaged && test_must_fail git push testrepo && - check_push_result $the_commit heads/master && + check_push_result testrepo $the_commit heads/master && git reset --hard $the_commit ' test_expect_success 'push --force with matching heads' ' - mk_test heads/master && + mk_test testrepo heads/master && git push testrepo : && git commit --amend -massaged && - git push --force testrepo && - ! check_push_result $the_commit heads/master && + git push --force testrepo : && + ! check_push_result testrepo $the_commit heads/master && git reset --hard $the_commit ' test_expect_success 'push with matching heads and forced update' ' - mk_test heads/master && + mk_test testrepo heads/master && git push testrepo : && git commit --amend -massaged && git push testrepo +: && - ! check_push_result $the_commit heads/master && + ! check_push_result testrepo $the_commit heads/master && git reset --hard $the_commit ' test_expect_success 'push with no ambiguity (1)' ' - mk_test heads/master && + mk_test testrepo heads/master && git push testrepo master:master && - check_push_result $the_commit heads/master + check_push_result testrepo $the_commit heads/master ' test_expect_success 'push with no ambiguity (2)' ' - mk_test remotes/origin/master && + mk_test testrepo remotes/origin/master && git push testrepo master:origin/master && - check_push_result $the_commit remotes/origin/master + check_push_result testrepo $the_commit remotes/origin/master ' test_expect_success 'push with colon-less refspec, no ambiguity' ' - mk_test heads/master heads/t/master && + mk_test testrepo heads/master heads/t/master && git branch -f t/master master && git push testrepo master && - check_push_result $the_commit heads/master && - check_push_result $the_first_commit heads/t/master + check_push_result testrepo $the_commit heads/master && + check_push_result testrepo $the_first_commit heads/t/master ' test_expect_success 'push with weak ambiguity (1)' ' - mk_test heads/master remotes/origin/master && + mk_test testrepo heads/master remotes/origin/master && git push testrepo master:master && - check_push_result $the_commit heads/master && - check_push_result $the_first_commit remotes/origin/master + check_push_result testrepo $the_commit heads/master && + check_push_result testrepo $the_first_commit remotes/origin/master ' test_expect_success 'push with weak ambiguity (2)' ' - mk_test heads/master remotes/origin/master remotes/another/master && + mk_test testrepo heads/master remotes/origin/master remotes/another/master && git push testrepo master:master && - check_push_result $the_commit heads/master && - check_push_result $the_first_commit remotes/origin/master remotes/another/master + check_push_result testrepo $the_commit heads/master && + check_push_result testrepo $the_first_commit remotes/origin/master remotes/another/master ' test_expect_success 'push with ambiguity' ' - mk_test heads/frotz tags/frotz && - if git push testrepo master:frotz - then - echo "Oops, should have failed" - false - else - check_push_result $the_first_commit heads/frotz tags/frotz - fi + mk_test testrepo heads/frotz tags/frotz && + test_must_fail git push testrepo master:frotz && + check_push_result testrepo $the_first_commit heads/frotz tags/frotz ' test_expect_success 'push with colon-less refspec (1)' ' - mk_test heads/frotz tags/frotz && + mk_test testrepo heads/frotz tags/frotz && git branch -f frotz master && git push testrepo frotz && - check_push_result $the_commit heads/frotz && - check_push_result $the_first_commit tags/frotz + check_push_result testrepo $the_commit heads/frotz && + check_push_result testrepo $the_first_commit tags/frotz ' test_expect_success 'push with colon-less refspec (2)' ' - mk_test heads/frotz tags/frotz && + mk_test testrepo heads/frotz tags/frotz && if git show-ref --verify -q refs/heads/frotz then git branch -D frotz fi && git tag -f frotz && git push -f testrepo frotz && - check_push_result $the_commit tags/frotz && - check_push_result $the_first_commit heads/frotz + check_push_result testrepo $the_commit tags/frotz && + check_push_result testrepo $the_first_commit heads/frotz ' test_expect_success 'push with colon-less refspec (3)' ' - mk_test && + mk_test testrepo && if git show-ref --verify -q refs/tags/frotz then git tag -d frotz fi && git branch -f frotz master && git push testrepo frotz && - check_push_result $the_commit heads/frotz && + check_push_result testrepo $the_commit heads/frotz && test 1 = $( cd testrepo && git show-ref | wc -l ) ' test_expect_success 'push with colon-less refspec (4)' ' - mk_test && + mk_test testrepo && if git show-ref --verify -q refs/heads/frotz then git branch -D frotz fi && git tag -f frotz && git push testrepo frotz && - check_push_result $the_commit tags/frotz && + check_push_result testrepo $the_commit tags/frotz && test 1 = $( cd testrepo && git show-ref | wc -l ) ' test_expect_success 'push head with non-existent, incomplete dest' ' - mk_test && + mk_test testrepo && git push testrepo master:branch && - check_push_result $the_commit heads/branch + check_push_result testrepo $the_commit heads/branch ' test_expect_success 'push tag with non-existent, incomplete dest' ' - mk_test && + mk_test testrepo && git tag -f v1.0 && git push testrepo v1.0:tag && - check_push_result $the_commit tags/tag + check_push_result testrepo $the_commit tags/tag ' test_expect_success 'push sha1 with non-existent, incomplete dest' ' - mk_test && + mk_test testrepo && test_must_fail git push testrepo `git rev-parse master`:foo ' test_expect_success 'push ref expression with non-existent, incomplete dest' ' - mk_test && + mk_test testrepo && test_must_fail git push testrepo master^:branch ' test_expect_success 'push with HEAD' ' - mk_test heads/master && + mk_test testrepo heads/master && git checkout master && git push testrepo HEAD && - check_push_result $the_commit heads/master + check_push_result testrepo $the_commit heads/master ' test_expect_success 'push with HEAD nonexisting at remote' ' - mk_test heads/master && + mk_test testrepo heads/master && git checkout -b local master && git push testrepo HEAD && - check_push_result $the_commit heads/local + check_push_result testrepo $the_commit heads/local ' test_expect_success 'push with +HEAD' ' - mk_test heads/master && + mk_test testrepo heads/master && git checkout master && git branch -D local && git checkout -b local && git push testrepo master local && - check_push_result $the_commit heads/master && - check_push_result $the_commit heads/local && + check_push_result testrepo $the_commit heads/master && + check_push_result testrepo $the_commit heads/local && # Without force rewinding should fail git reset --hard HEAD^ && test_must_fail git push testrepo HEAD && - check_push_result $the_commit heads/local && + check_push_result testrepo $the_commit heads/local && # With force rewinding should succeed git push testrepo +HEAD && - check_push_result $the_first_commit heads/local + check_push_result testrepo $the_first_commit heads/local ' test_expect_success 'push HEAD with non-existent, incomplete dest' ' - mk_test && + mk_test testrepo && git checkout master && git push testrepo HEAD:branch && - check_push_result $the_commit heads/branch + check_push_result testrepo $the_commit heads/branch ' test_expect_success 'push with config remote.*.push = HEAD' ' - mk_test heads/local && + mk_test testrepo heads/local && git checkout master && git branch -f local $the_commit && ( @@ -489,46 +489,68 @@ test_expect_success 'push with config remote.*.push = HEAD' ' git checkout local && git reset --hard $the_first_commit ) && - git config remote.there.url testrepo && - git config remote.there.push HEAD && - git config branch.master.remote there && + test_config remote.there.url testrepo && + test_config remote.there.push HEAD && + test_config branch.master.remote there && + git push && + check_push_result testrepo $the_commit heads/master && + check_push_result testrepo $the_first_commit heads/local +' + +test_expect_success 'push with remote.pushdefault' ' + mk_test up_repo heads/master && + mk_test down_repo heads/master && + test_config remote.up.url up_repo && + test_config remote.down.url down_repo && + test_config branch.master.remote up && + test_config remote.pushdefault down && + test_config push.default matching && git push && - check_push_result $the_commit heads/master && - check_push_result $the_first_commit heads/local + check_push_result up_repo $the_first_commit heads/master && + check_push_result down_repo $the_commit heads/master ' -# clean up the cruft left with the previous one -git config --remove-section remote.there -git config --remove-section branch.master - test_expect_success 'push with config remote.*.pushurl' ' - mk_test heads/master && + mk_test testrepo heads/master && git checkout master && - git config remote.there.url test2repo && - git config remote.there.pushurl testrepo && - git push there && - check_push_result $the_commit heads/master + test_config remote.there.url test2repo && + test_config remote.there.pushurl testrepo && + git push there : && + check_push_result testrepo $the_commit heads/master +' + +test_expect_success 'push with config branch.*.pushremote' ' + mk_test up_repo heads/master && + mk_test side_repo heads/master && + mk_test down_repo heads/master && + test_config remote.up.url up_repo && + test_config remote.pushdefault side_repo && + test_config remote.down.url down_repo && + test_config branch.master.remote up && + test_config branch.master.pushremote down && + test_config push.default matching && + git push && + check_push_result up_repo $the_first_commit heads/master && + check_push_result side_repo $the_first_commit heads/master && + check_push_result down_repo $the_commit heads/master ' -# clean up the cruft left with the previous one -git config --remove-section remote.there - test_expect_success 'push with dry-run' ' - mk_test heads/master && + mk_test testrepo heads/master && ( cd testrepo && old_commit=$(git show-ref -s --verify refs/heads/master) ) && - git push --dry-run testrepo && - check_push_result $old_commit heads/master + git push --dry-run testrepo : && + check_push_result testrepo $old_commit heads/master ' test_expect_success 'push updates local refs' ' - mk_test heads/master && - mk_child child && + mk_test testrepo heads/master && + mk_child testrepo child && ( cd child && git pull .. master && @@ -541,9 +563,9 @@ test_expect_success 'push updates local refs' ' test_expect_success 'push updates up-to-date local refs' ' - mk_test heads/master && - mk_child child1 && - mk_child child2 && + mk_test testrepo heads/master && + mk_child testrepo child1 && + mk_child testrepo child2 && (cd child1 && git pull .. master && git push) && ( cd child2 && @@ -557,8 +579,8 @@ test_expect_success 'push updates up-to-date local refs' ' test_expect_success 'push preserves up-to-date packed refs' ' - mk_test heads/master && - mk_child child && + mk_test testrepo heads/master && + mk_child testrepo child && ( cd child && git push && @@ -569,8 +591,8 @@ test_expect_success 'push preserves up-to-date packed refs' ' test_expect_success 'push does not update local refs on failure' ' - mk_test heads/master && - mk_child child && + mk_test testrepo heads/master && + mk_child testrepo child && mkdir testrepo/.git/hooks && echo "#!/no/frobnication/today" >testrepo/.git/hooks/pre-receive && chmod +x testrepo/.git/hooks/pre-receive && @@ -586,7 +608,7 @@ test_expect_success 'push does not update local refs on failure' ' test_expect_success 'allow deleting an invalid remote ref' ' - mk_test heads/master && + mk_test testrepo heads/master && rm -f testrepo/.git/objects/??/* && git push testrepo :refs/heads/master && (cd testrepo && test_must_fail git rev-parse --verify refs/heads/master) @@ -594,7 +616,7 @@ test_expect_success 'allow deleting an invalid remote ref' ' ' test_expect_success 'pushing valid refs triggers post-receive and post-update hooks' ' - mk_test_with_hooks heads/master heads/next && + mk_test_with_hooks testrepo heads/master heads/next && orgmaster=$(cd testrepo && git show-ref -s --verify refs/heads/master) && newmaster=$(git show-ref -s --verify refs/heads/master) && orgnext=$(cd testrepo && git show-ref -s --verify refs/heads/next) && @@ -630,7 +652,7 @@ test_expect_success 'pushing valid refs triggers post-receive and post-update ho ' test_expect_success 'deleting dangling ref triggers hooks with correct args' ' - mk_test_with_hooks heads/master && + mk_test_with_hooks testrepo heads/master && rm -f testrepo/.git/objects/??/* && git push testrepo :refs/heads/master && ( @@ -659,7 +681,7 @@ test_expect_success 'deleting dangling ref triggers hooks with correct args' ' ' test_expect_success 'deletion of a non-existent ref is not fed to post-receive and post-update hooks' ' - mk_test_with_hooks heads/master && + mk_test_with_hooks testrepo heads/master && orgmaster=$(cd testrepo && git show-ref -s --verify refs/heads/master) && newmaster=$(git show-ref -s --verify refs/heads/master) && git push testrepo master :refs/heads/nonexistent && @@ -691,7 +713,7 @@ test_expect_success 'deletion of a non-existent ref is not fed to post-receive a ' test_expect_success 'deletion of a non-existent ref alone does trigger post-receive and post-update hooks' ' - mk_test_with_hooks heads/master && + mk_test_with_hooks testrepo heads/master && git push testrepo :refs/heads/nonexistent && ( cd testrepo/.git && @@ -711,7 +733,7 @@ test_expect_success 'deletion of a non-existent ref alone does trigger post-rece ' test_expect_success 'mixed ref updates, deletes, invalid deletes trigger hooks with correct input' ' - mk_test_with_hooks heads/master heads/next heads/pu && + mk_test_with_hooks testrepo heads/master heads/next heads/pu && orgmaster=$(cd testrepo && git show-ref -s --verify refs/heads/master) && newmaster=$(git show-ref -s --verify refs/heads/master) && orgnext=$(cd testrepo && git show-ref -s --verify refs/heads/next) && @@ -757,14 +779,14 @@ test_expect_success 'mixed ref updates, deletes, invalid deletes trigger hooks w ' test_expect_success 'allow deleting a ref using --delete' ' - mk_test heads/master && + mk_test testrepo heads/master && (cd testrepo && git config receive.denyDeleteCurrent warn) && git push testrepo --delete master && (cd testrepo && test_must_fail git rev-parse --verify refs/heads/master) ' test_expect_success 'allow deleting a tag using --delete' ' - mk_test heads/master && + mk_test testrepo heads/master && git tag -a -m dummy_message deltag heads/master && git push testrepo --tags && (cd testrepo && git rev-parse --verify -q refs/tags/deltag) && @@ -773,17 +795,17 @@ test_expect_success 'allow deleting a tag using --delete' ' ' test_expect_success 'push --delete without args aborts' ' - mk_test heads/master && + mk_test testrepo heads/master && test_must_fail git push testrepo --delete ' test_expect_success 'push --delete refuses src:dest refspecs' ' - mk_test heads/master && + mk_test testrepo heads/master && test_must_fail git push testrepo --delete master:foo ' test_expect_success 'warn on push to HEAD of non-bare repository' ' - mk_test heads/master && + mk_test testrepo heads/master && ( cd testrepo && git checkout master && @@ -794,7 +816,7 @@ test_expect_success 'warn on push to HEAD of non-bare repository' ' ' test_expect_success 'deny push to HEAD of non-bare repository' ' - mk_test heads/master && + mk_test testrepo heads/master && ( cd testrepo && git checkout master && @@ -804,7 +826,7 @@ test_expect_success 'deny push to HEAD of non-bare repository' ' ' test_expect_success 'allow push to HEAD of bare repository (bare)' ' - mk_test heads/master && + mk_test testrepo heads/master && ( cd testrepo && git checkout master && @@ -816,7 +838,7 @@ test_expect_success 'allow push to HEAD of bare repository (bare)' ' ' test_expect_success 'allow push to HEAD of non-bare repository (config)' ' - mk_test heads/master && + mk_test testrepo heads/master && ( cd testrepo && git checkout master && @@ -827,63 +849,63 @@ test_expect_success 'allow push to HEAD of non-bare repository (config)' ' ' test_expect_success 'fetch with branches' ' - mk_empty && + mk_empty testrepo && git branch second $the_first_commit && git checkout second && echo ".." > testrepo/.git/branches/branch1 && ( cd testrepo && git fetch branch1 && - r=$(git show-ref -s --verify refs/heads/branch1) && - test "z$r" = "z$the_commit" && - test 1 = $(git for-each-ref refs/heads | wc -l) + echo "$the_commit commit refs/heads/branch1" >expect && + git for-each-ref refs/heads >actual && + test_cmp expect actual ) && git checkout master ' test_expect_success 'fetch with branches containing #' ' - mk_empty && + mk_empty testrepo && echo "..#second" > testrepo/.git/branches/branch2 && ( cd testrepo && git fetch branch2 && - r=$(git show-ref -s --verify refs/heads/branch2) && - test "z$r" = "z$the_first_commit" && - test 1 = $(git for-each-ref refs/heads | wc -l) + echo "$the_first_commit commit refs/heads/branch2" >expect && + git for-each-ref refs/heads >actual && + test_cmp expect actual ) && git checkout master ' test_expect_success 'push with branches' ' - mk_empty && + mk_empty testrepo && git checkout second && echo "testrepo" > .git/branches/branch1 && git push branch1 && ( cd testrepo && - r=$(git show-ref -s --verify refs/heads/master) && - test "z$r" = "z$the_first_commit" && - test 1 = $(git for-each-ref refs/heads | wc -l) + echo "$the_first_commit commit refs/heads/master" >expect && + git for-each-ref refs/heads >actual && + test_cmp expect actual ) ' test_expect_success 'push with branches containing #' ' - mk_empty && + mk_empty testrepo && echo "testrepo#branch3" > .git/branches/branch2 && git push branch2 && ( cd testrepo && - r=$(git show-ref -s --verify refs/heads/branch3) && - test "z$r" = "z$the_first_commit" && - test 1 = $(git for-each-ref refs/heads | wc -l) + echo "$the_first_commit commit refs/heads/branch3" >expect && + git for-each-ref refs/heads >actual && + test_cmp expect actual ) && git checkout master ' test_expect_success 'push into aliased refs (consistent)' ' - mk_test heads/master && - mk_child child1 && - mk_child child2 && + mk_test testrepo heads/master && + mk_child testrepo child1 && + mk_child testrepo child2 && ( cd child1 && git branch foo && @@ -903,9 +925,9 @@ test_expect_success 'push into aliased refs (consistent)' ' ' test_expect_success 'push into aliased refs (inconsistent)' ' - mk_test heads/master && - mk_child child1 && - mk_child child2 && + mk_test testrepo heads/master && + mk_child testrepo child1 && + mk_child testrepo child2 && ( cd child1 && git branch foo && @@ -930,9 +952,9 @@ test_expect_success 'push into aliased refs (inconsistent)' ' ' test_expect_success 'push requires --force to update lightweight tag' ' - mk_test heads/master && - mk_child child1 && - mk_child child2 && + mk_test testrepo heads/master && + mk_child testrepo child1 && + mk_child testrepo child2 && ( cd child1 && git tag Tag && @@ -951,28 +973,28 @@ test_expect_success 'push requires --force to update lightweight tag' ' ' test_expect_success 'push --porcelain' ' - mk_empty && + mk_empty testrepo && echo >.git/foo "To testrepo" && echo >>.git/foo "* refs/heads/master:refs/remotes/origin/master [new branch]" && echo >>.git/foo "Done" && git push >.git/bar --porcelain testrepo refs/heads/master:refs/remotes/origin/master && ( cd testrepo && - r=$(git show-ref -s --verify refs/remotes/origin/master) && - test "z$r" = "z$the_commit" && - test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + echo "$the_commit commit refs/remotes/origin/master" >expect && + git for-each-ref refs/remotes/origin >actual && + test_cmp expect actual ) && test_cmp .git/foo .git/bar ' test_expect_success 'push --porcelain bad url' ' - mk_empty && + mk_empty testrepo && test_must_fail git push >.git/bar --porcelain asdfasdfasd refs/heads/master:refs/remotes/origin/master && test_must_fail grep -q Done .git/bar ' test_expect_success 'push --porcelain rejected' ' - mk_empty && + mk_empty testrepo && git push testrepo refs/heads/master:refs/remotes/origin/master && (cd testrepo && git reset --hard origin/master^ @@ -986,7 +1008,7 @@ test_expect_success 'push --porcelain rejected' ' ' test_expect_success 'push --porcelain --dry-run rejected' ' - mk_empty && + mk_empty testrepo && git push testrepo refs/heads/master:refs/remotes/origin/master && (cd testrepo && git reset --hard origin/master @@ -1001,25 +1023,25 @@ test_expect_success 'push --porcelain --dry-run rejected' ' ' test_expect_success 'push --prune' ' - mk_test heads/master heads/second heads/foo heads/bar && - git push --prune testrepo && - check_push_result $the_commit heads/master && - check_push_result $the_first_commit heads/second && - ! check_push_result $the_first_commit heads/foo heads/bar + mk_test testrepo heads/master heads/second heads/foo heads/bar && + git push --prune testrepo : && + check_push_result testrepo $the_commit heads/master && + check_push_result testrepo $the_first_commit heads/second && + ! check_push_result testrepo $the_first_commit heads/foo heads/bar ' test_expect_success 'push --prune refspec' ' - mk_test tmp/master tmp/second tmp/foo tmp/bar && + mk_test testrepo tmp/master tmp/second tmp/foo tmp/bar && git push --prune testrepo "refs/heads/*:refs/tmp/*" && - check_push_result $the_commit tmp/master && - check_push_result $the_first_commit tmp/second && - ! check_push_result $the_first_commit tmp/foo tmp/bar + check_push_result testrepo $the_commit tmp/master && + check_push_result testrepo $the_first_commit tmp/second && + ! check_push_result testrepo $the_first_commit tmp/foo tmp/bar ' for configsection in transfer receive do test_expect_success "push to update a ref hidden by $configsection.hiderefs" ' - mk_test heads/master hidden/one hidden/two hidden/three && + mk_test testrepo heads/master hidden/one hidden/two hidden/three && ( cd testrepo && git config $configsection.hiderefs refs/hidden @@ -1027,20 +1049,127 @@ do # push to unhidden ref succeeds normally git push testrepo master:refs/heads/master && - check_push_result $the_commit heads/master && + check_push_result testrepo $the_commit heads/master && # push to update a hidden ref should fail test_must_fail git push testrepo master:refs/hidden/one && - check_push_result $the_first_commit hidden/one && + check_push_result testrepo $the_first_commit hidden/one && # push to delete a hidden ref should fail test_must_fail git push testrepo :refs/hidden/two && - check_push_result $the_first_commit hidden/two && + check_push_result testrepo $the_first_commit hidden/two && # idempotent push to update a hidden ref should fail test_must_fail git push testrepo $the_first_commit:refs/hidden/three && - check_push_result $the_first_commit hidden/three + check_push_result testrepo $the_first_commit hidden/three ' done +test_expect_success 'fetch exact SHA1' ' + mk_test testrepo heads/master hidden/one && + git push testrepo master:refs/hidden/one && + ( + cd testrepo && + git config transfer.hiderefs refs/hidden + ) && + check_push_result testrepo $the_commit hidden/one && + + mk_child testrepo child && + ( + cd child && + + # make sure $the_commit does not exist here + git repack -a -d && + git prune && + test_must_fail git cat-file -t $the_commit && + + # fetching the hidden object should fail by default + test_must_fail git fetch -v ../testrepo $the_commit:refs/heads/copy && + test_must_fail git rev-parse --verify refs/heads/copy && + + # the server side can allow it to succeed + ( + cd ../testrepo && + git config uploadpack.allowtipsha1inwant true + ) && + + git fetch -v ../testrepo $the_commit:refs/heads/copy && + result=$(git rev-parse --verify refs/heads/copy) && + test "$the_commit" = "$result" + ) +' + +test_expect_success 'fetch follows tags by default' ' + mk_test testrepo heads/master && + rm -fr src dst && + git init src && + ( + cd src && + git pull ../testrepo master && + git tag -m "annotated" tag && + git for-each-ref >tmp1 && + ( + cat tmp1 + sed -n "s|refs/heads/master$|refs/remotes/origin/master|p" tmp1 + ) | + sort -k 3 >../expect + ) && + git init dst && + ( + cd dst && + git remote add origin ../src && + git config branch.master.remote origin && + git config branch.master.merge refs/heads/master && + git pull && + git for-each-ref >../actual + ) && + test_cmp expect actual +' + +test_expect_success 'push does not follow tags by default' ' + mk_test testrepo heads/master && + rm -fr src dst && + git init src && + git init --bare dst && + ( + cd src && + git pull ../testrepo master && + git tag -m "annotated" tag && + git checkout -b another && + git commit --allow-empty -m "future commit" && + git tag -m "future" future && + git checkout master && + git for-each-ref refs/heads/master >../expect && + git push ../dst master + ) && + ( + cd dst && + git for-each-ref >../actual + ) && + test_cmp expect actual +' + +test_expect_success 'push --follow-tag only pushes relevant tags' ' + mk_test testrepo heads/master && + rm -fr src dst && + git init src && + git init --bare dst && + ( + cd src && + git pull ../testrepo master && + git tag -m "annotated" tag && + git checkout -b another && + git commit --allow-empty -m "future commit" && + git tag -m "future" future && + git checkout master && + git for-each-ref refs/heads/master refs/tags/tag >../expect + git push --follow-tag ../dst master + ) && + ( + cd dst && + git for-each-ref >../actual + ) && + test_cmp expect actual +' + test_done diff --git a/t/t5517-push-mirror.sh b/t/t5517-push-mirror.sh index e2ad260508..12a5dfb17e 100755 --- a/t/t5517-push-mirror.sh +++ b/t/t5517-push-mirror.sh @@ -256,7 +256,7 @@ test_expect_success 'remote.foo.mirror=no has no effect' ' git branch keep master && git push --mirror up && git branch -D keep && - git push up + git push up : ) && ( cd mirror && diff --git a/t/t5519-push-alternates.sh b/t/t5519-push-alternates.sh index c00c9b071d..11fcd37700 100755 --- a/t/t5519-push-alternates.sh +++ b/t/t5519-push-alternates.sh @@ -40,7 +40,7 @@ test_expect_success 'alice works and pushes' ' cd alice-work && echo more >file && git commit -a -m second && - git push ../alice-pub + git push ../alice-pub : ) ' @@ -57,7 +57,7 @@ test_expect_success 'bob fetches from alice, works and pushes' ' git pull ../alice-pub master && echo more bob >file && git commit -a -m third && - git push ../bob-pub + git push ../bob-pub : ) && # Check that the second commit by Alice is not sent @@ -86,7 +86,7 @@ test_expect_success 'alice works and pushes again' ' cd alice-work && echo more alice >file && git commit -a -m fourth && - git push ../alice-pub + git push ../alice-pub : ) ' @@ -99,7 +99,7 @@ test_expect_success 'bob works and pushes' ' cd bob-work && echo yet more bob >file && git commit -a -m fifth && - git push ../bob-pub + git push ../bob-pub : ) ' @@ -115,7 +115,7 @@ test_expect_success 'alice works and pushes yet again' ' git commit -a -m sixth.2 && echo more and more alice >>file && git commit -a -m sixth.3 && - git push ../alice-pub + git push ../alice-pub : ) ' @@ -136,7 +136,7 @@ test_expect_success 'bob works and pushes again' ' git hash-object -t commit -w commit && echo even more bob >file && git commit -a -m seventh && - git push ../bob-pub + git push ../bob-pub : ) ' diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh index 35304b41e9..6af6c63350 100755 --- a/t/t5520-pull.sh +++ b/t/t5520-pull.sh @@ -96,8 +96,7 @@ test_expect_success '--rebase' ' ' test_expect_success 'pull.rebase' ' git reset --hard before-rebase && - git config --bool pull.rebase true && - test_when_finished "git config --unset pull.rebase" && + test_config pull.rebase true && git pull . copy && test $(git rev-parse HEAD^) = $(git rev-parse copy) && test new = $(git show HEAD:file2) @@ -105,8 +104,7 @@ test_expect_success 'pull.rebase' ' test_expect_success 'branch.to-rebase.rebase' ' git reset --hard before-rebase && - git config --bool branch.to-rebase.rebase true && - test_when_finished "git config --unset branch.to-rebase.rebase" && + test_config branch.to-rebase.rebase true && git pull . copy && test $(git rev-parse HEAD^) = $(git rev-parse copy) && test new = $(git show HEAD:file2) @@ -114,10 +112,8 @@ test_expect_success 'branch.to-rebase.rebase' ' test_expect_success 'branch.to-rebase.rebase should override pull.rebase' ' git reset --hard before-rebase && - git config --bool pull.rebase true && - test_when_finished "git config --unset pull.rebase" && - git config --bool branch.to-rebase.rebase false && - test_when_finished "git config --unset branch.to-rebase.rebase" && + test_config pull.rebase true && + test_config branch.to-rebase.rebase false && git pull . copy && test $(git rev-parse HEAD^) != $(git rev-parse copy) && test new = $(git show HEAD:file2) @@ -171,9 +167,9 @@ test_expect_success 'pull --rebase dies early with dirty working directory' ' git update-ref refs/remotes/me/copy copy^ && COPY=$(git rev-parse --verify me/copy) && git rebase --onto $COPY copy && - git config branch.to-rebase.remote me && - git config branch.to-rebase.merge refs/heads/copy && - git config branch.to-rebase.rebase true && + test_config branch.to-rebase.remote me && + test_config branch.to-rebase.merge refs/heads/copy && + test_config branch.to-rebase.rebase true && echo dirty >> file && git add file && test_must_fail git pull && diff --git a/t/t5521-pull-options.sh b/t/t5521-pull-options.sh index 1b06691bb4..aa31abe32b 100755 --- a/t/t5521-pull-options.sh +++ b/t/t5521-pull-options.sh @@ -19,6 +19,17 @@ test_expect_success 'git pull -q' ' test ! -s out) ' +test_expect_success 'git pull -q --rebase' ' + mkdir clonedqrb && + (cd clonedqrb && git init && + git pull -q --rebase "../parent" >out 2>err && + test ! -s err && + test ! -s out && + git pull -q --rebase "../parent" >out 2>err && + test ! -s err && + test ! -s out) +' + test_expect_success 'git pull' ' mkdir cloned && (cd cloned && git init && @@ -27,6 +38,14 @@ test_expect_success 'git pull' ' test ! -s out) ' +test_expect_success 'git pull --rebase' ' + mkdir clonedrb && + (cd clonedrb && git init && + git pull --rebase "../parent" >out 2>err && + test -s err && + test ! -s out) +' + test_expect_success 'git pull -v' ' mkdir clonedv && (cd clonedv && git init && @@ -35,6 +54,14 @@ test_expect_success 'git pull -v' ' test ! -s out) ' +test_expect_success 'git pull -v --rebase' ' + mkdir clonedvrb && + (cd clonedvrb && git init && + git pull -v --rebase "../parent" >out 2>err && + test -s err && + test ! -s out) +' + test_expect_success 'git pull -v -q' ' mkdir clonedvq && (cd clonedvq && git init && diff --git a/t/t5531-deep-submodule-push.sh b/t/t5531-deep-submodule-push.sh index 1947c28c64..8c16e045a0 100755 --- a/t/t5531-deep-submodule-push.sh +++ b/t/t5531-deep-submodule-push.sh @@ -16,6 +16,7 @@ test_expect_success setup ' ( cd gar/bage && git init && + git config push.default matching && >junk && git add junk && git commit -m "Initial junk" diff --git a/t/t5541-http-push.sh b/t/t5541-http-push.sh index 8a9dc85931..beb00be4b1 100755 --- a/t/t5541-http-push.sh +++ b/t/t5541-http-push.sh @@ -181,8 +181,7 @@ test_expect_success 'push (chunked)' ' git checkout master && test_commit commit path3 && HEAD=$(git rev-parse --verify HEAD) && - git config http.postbuffer 4 && - test_when_finished "git config --unset http.postbuffer" && + test_config http.postbuffer 4 && git push -v -v origin $BRANCH 2>err && grep "POST git-receive-pack (chunked)" err && (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git && diff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh index 80d20c876b..f7d0f146f0 100755 --- a/t/t5550-http-fetch.sh +++ b/t/t5550-http-fetch.sh @@ -13,6 +13,7 @@ LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5550'} start_httpd test_expect_success 'setup repository' ' + git config push.default matching && echo content1 >file && git add file && git commit -m one diff --git a/t/t5551-http-fetch.sh b/t/t5551-http-fetch.sh index 47eb76921d..b23efbbfd9 100755 --- a/t/t5551-http-fetch.sh +++ b/t/t5551-http-fetch.sh @@ -13,6 +13,7 @@ LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5551'} start_httpd test_expect_success 'setup repository' ' + git config push.default matching && echo content >file && git add file && git commit -m one @@ -162,6 +163,30 @@ test_expect_success 'invalid Content-Type rejected' ' grep "not valid:" actual ' +test_expect_success 'create namespaced refs' ' + test_commit namespaced && + git push public HEAD:refs/namespaces/ns/refs/heads/master && + git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/repo.git" \ + symbolic-ref refs/namespaces/ns/HEAD refs/namespaces/ns/refs/heads/master +' + +test_expect_success 'smart clone respects namespace' ' + git clone "$HTTPD_URL/smart_namespace/repo.git" ns-smart && + echo namespaced >expect && + git --git-dir=ns-smart/.git log -1 --format=%s >actual && + test_cmp expect actual +' + +test_expect_success 'dumb clone via http-backend respects namespace' ' + git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/repo.git" \ + config http.getanyfile true && + GIT_SMART_HTTP=0 git clone \ + "$HTTPD_URL/smart_namespace/repo.git" ns-dumb && + echo namespaced >expect && + git --git-dir=ns-dumb/.git log -1 --format=%s >actual && + test_cmp expect actual +' + test -n "$GIT_TEST_LONG" && test_set_prereq EXPENSIVE test_expect_success EXPENSIVE 'create 50,000 tags in the repo' ' diff --git a/t/t5570-git-daemon.sh b/t/t5570-git-daemon.sh index a3a4e47e1d..f01edffa3c 100755 --- a/t/t5570-git-daemon.sh +++ b/t/t5570-git-daemon.sh @@ -8,6 +8,7 @@ LIB_GIT_DAEMON_PORT=${LIB_GIT_DAEMON_PORT-5570} start_git_daemon test_expect_success 'setup repository' ' + git config push.default matching && echo content >file && git add file && git commit -m one diff --git a/t/t5700-clone-reference.sh b/t/t5700-clone-reference.sh index c47d450cc3..60f1552ade 100755 --- a/t/t5700-clone-reference.sh +++ b/t/t5700-clone-reference.sh @@ -54,11 +54,14 @@ cd "$base_dir" rm -f "$U.D" -test_expect_success 'cloning with reference (no -l -s)' \ -'GIT_DEBUG_SEND_PACK=3 git clone --reference B "file://$(pwd)/A" D 3>"$U.D"' +test_expect_success 'cloning with reference (no -l -s)' ' + GIT_TRACE_PACKET=$U.D git clone --reference B "file://$(pwd)/A" D +' -test_expect_success 'fetched no objects' \ -'! grep "^want" "$U.D"' +test_expect_success 'fetched no objects' ' + test -s "$U.D" && + ! grep " want" "$U.D" +' cd "$base_dir" @@ -173,12 +176,13 @@ test_expect_success 'fetch with incomplete alternates' ' ( cd K && git remote add J "file://$base_dir/J" && - GIT_DEBUG_SEND_PACK=3 git fetch J 3>"$U.K" + GIT_TRACE_PACKET=$U.K git fetch J ) && master_object=$(cd A && git for-each-ref --format="%(objectname)" refs/heads/master) && - ! grep "^want $master_object" "$U.K" && + test -s "$U.K" && + ! grep " want $master_object" "$U.K" && tag_object=$(cd A && git for-each-ref --format="%(objectname)" refs/tags/HEAD) && - ! grep "^want $tag_object" "$U.K" + ! grep " want $tag_object" "$U.K" ' test_done diff --git a/t/t5704-bundle.sh b/t/t5704-bundle.sh index 9e43731fe5..a45c31692e 100755 --- a/t/t5704-bundle.sh +++ b/t/t5704-bundle.sh @@ -58,4 +58,14 @@ test_expect_success 'ridiculously long subject in boundary' ' grep "^-[0-9a-f]\\{40\\} " boundary ' +test_expect_success 'prerequisites with an empty commit message' ' + : >file1 && + git add file1 && + test_tick && + git commit --allow-empty-message -m "" && + test_commit file2 && + git bundle create bundle HEAD^.. && + git bundle verify bundle +' + test_done diff --git a/t/t5710-info-alternate.sh b/t/t5710-info-alternate.sh index aa045295de..8956c21617 100755 --- a/t/t5710-info-alternate.sh +++ b/t/t5710-info-alternate.sh @@ -58,13 +58,7 @@ test_expect_success 'creating too deep nesting' \ git clone -l -s D E && git clone -l -s E F && git clone -l -s F G && -git clone -l -s G H' - -test_expect_success 'invalidity of deepest repository' \ -'cd H && { - test_valid_repo - test $? -ne 0 -}' +test_must_fail git clone --bare -l -s G H' cd "$base_dir" diff --git a/t/t6012-rev-list-simplify.sh b/t/t6012-rev-list-simplify.sh index 839ad97b79..dd6dc844e7 100755 --- a/t/t6012-rev-list-simplify.sh +++ b/t/t6012-rev-list-simplify.sh @@ -56,19 +56,37 @@ test_expect_success setup ' echo "Final change" >file && test_tick && git commit -a -m "Final change" && - note I + note I && + + git symbolic-ref HEAD refs/heads/unrelated && + git rm -f "*" && + echo "Unrelated branch" >side && + git add side && + test_tick && git commit -m "Side root" && + note J && + + git checkout master && + test_tick && git merge -m "Coolest" unrelated && + note K && + + echo "Immaterial" >elif && + git add elif && + test_tick && git commit -m "Last" && + note L ' FMT='tformat:%P %H | %s' -check_result () { +check_outcome () { + outcome=$1 + shift for c in $1 do echo "$c" done >expect && shift && param="$*" && - test_expect_success "log $param" ' + test_expect_$outcome "log $param" ' git log --pretty="$FMT" --parents $param | unnote >actual && sed -e "s/^.* \([^ ]*\) .*/\1/" >check <actual && @@ -79,11 +97,15 @@ check_result () { ' } -check_result 'I H G F E D C B A' --full-history -check_result 'I H E C B A' --full-history -- file -check_result 'I H E C B A' --full-history --topo-order -- file -check_result 'I H E C B A' --full-history --date-order -- file -check_result 'I E C B A' --simplify-merges -- file +check_result () { + check_outcome success "$@" +} + +check_result 'L K J I H G F E D C B A' --full-history +check_result 'K I H E C B A' --full-history -- file +check_result 'K I H E C B A' --full-history --topo-order -- file +check_result 'K I H E C B A' --full-history --date-order -- file +check_outcome failure 'I E C B A' --simplify-merges -- file check_result 'I B A' -- file check_result 'I B A' --topo-order -- file check_result 'H' --first-parent -- another-file diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index 3e0e15fb3e..8bf53de3ef 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -164,7 +164,7 @@ test_expect_success 'bisect start: existing ".git/BISECT_START" not modified if cp .git/BISECT_START saved && test_must_fail git bisect start $HASH4 foo -- && git branch > branch.output && - test_i18ngrep "* (no branch)" branch.output > /dev/null && + test_i18ngrep "* (no branch, bisect started on other)" branch.output > /dev/null && test_cmp saved .git/BISECT_START ' test_expect_success 'bisect start: no ".git/BISECT_START" if mistaken rev' ' @@ -190,7 +190,7 @@ test_expect_success 'bisect start: no ".git/BISECT_START" if checkout error' ' # $HASH1 is good, $HASH4 is bad, we skip $HASH3 # but $HASH2 is bad, # so we should find $HASH2 as the first bad commit -test_expect_success 'bisect skip: successfull result' ' +test_expect_success 'bisect skip: successful result' ' git bisect reset && git bisect start $HASH4 $HASH1 && git bisect skip && diff --git a/t/t6200-fmt-merge-msg.sh b/t/t6200-fmt-merge-msg.sh index 992c2a0467..54b5744cc5 100755 --- a/t/t6200-fmt-merge-msg.sh +++ b/t/t6200-fmt-merge-msg.sh @@ -112,8 +112,8 @@ test_expect_success '[merge] summary/log configuration' ' Common #1 EOF - git config merge.log true && - test_might_fail git config --unset-all merge.summary && + test_config merge.log true && + test_unconfig merge.summary && git checkout master && test_tick && @@ -121,8 +121,8 @@ test_expect_success '[merge] summary/log configuration' ' git fmt-merge-msg <.git/FETCH_HEAD >actual1 && - test_might_fail git config --unset-all merge.log && - git config merge.summary true && + test_unconfig merge.log && + test_config merge.summary true && git checkout master && test_tick && @@ -134,11 +134,6 @@ test_expect_success '[merge] summary/log configuration' ' test_cmp expected actual2 ' -test_expect_success 'setup: clear [merge] configuration' ' - test_might_fail git config --unset-all merge.log && - test_might_fail git config --unset-all merge.summary -' - test_expect_success 'setup FETCH_HEAD' ' git checkout master && test_tick && @@ -180,6 +175,24 @@ test_expect_success 'merge.log=5 shows all 5 commits' ' test_cmp expected actual ' +test_expect_success '--log=5 with custom comment character' ' + cat >expected <<-EOF && + Merge branch ${apos}left${apos} + + x By Another Author (3) and A U Thor (2) + x Via Another Committer + * left: + Left #5 + Left #4 + Left #3 + Common #2 + Common #1 + EOF + + git -c core.commentchar="x" fmt-merge-msg --log=5 <.git/FETCH_HEAD >actual && + test_cmp expected actual +' + test_expect_success 'merge.log=0 disables shortlog' ' echo "Merge branch ${apos}left${apos}" >expected git -c merge.log=0 fmt-merge-msg <.git/FETCH_HEAD >actual && @@ -248,14 +261,14 @@ test_expect_success 'fmt-merge-msg -m' ' Common #1 EOF - test_might_fail git config --unset merge.log && - test_might_fail git config --unset merge.summary && + test_unconfig merge.log && + test_unconfig merge.summary && git checkout master && git fetch "$(pwd)" left && git fmt-merge-msg -m "Sync with left" <.git/FETCH_HEAD >actual && git fmt-merge-msg --log -m "Sync with left" \ <.git/FETCH_HEAD >actual.log && - git config merge.log true && + test_config merge.log true && git fmt-merge-msg -m "Sync with left" \ <.git/FETCH_HEAD >actual.log-config && git fmt-merge-msg --no-log -m "Sync with left" \ @@ -290,29 +303,29 @@ test_expect_success 'setup: expected shortlog for two branches' ' ' test_expect_success 'shortlog for two branches' ' - git config merge.log true && - test_might_fail git config --unset-all merge.summary && + test_config merge.log true && + test_unconfig merge.summary && git checkout master && test_tick && git fetch . left right && git fmt-merge-msg <.git/FETCH_HEAD >actual1 && - test_might_fail git config --unset-all merge.log && - git config merge.summary true && + test_unconfig merge.log && + test_config merge.summary true && git checkout master && test_tick && git fetch . left right && git fmt-merge-msg <.git/FETCH_HEAD >actual2 && - git config merge.log yes && - test_might_fail git config --unset-all merge.summary && + test_config merge.log yes && + test_unconfig merge.summary && git checkout master && test_tick && git fetch . left right && git fmt-merge-msg <.git/FETCH_HEAD >actual3 && - test_might_fail git config --unset-all merge.log && - git config merge.summary yes && + test_unconfig merge.log && + test_config merge.summary yes && git checkout master && test_tick && git fetch . left right && @@ -325,8 +338,8 @@ test_expect_success 'shortlog for two branches' ' ' test_expect_success 'merge-msg -F' ' - test_might_fail git config --unset-all merge.log && - git config merge.summary yes && + test_unconfig merge.log && + test_config merge.summary yes && git checkout master && test_tick && git fetch . left right && @@ -335,8 +348,8 @@ test_expect_success 'merge-msg -F' ' ' test_expect_success 'merge-msg -F in subdirectory' ' - test_might_fail git config --unset-all merge.log && - git config merge.summary yes && + test_unconfig merge.log && + test_config merge.summary yes && git checkout master && test_tick && git fetch . left right && @@ -350,8 +363,8 @@ test_expect_success 'merge-msg -F in subdirectory' ' ' test_expect_success 'merge-msg with nothing to merge' ' - test_might_fail git config --unset-all merge.log && - git config merge.summary yes && + test_unconfig merge.log && + test_config merge.summary yes && >empty && @@ -376,8 +389,8 @@ test_expect_success 'merge-msg tag' ' Common #1 EOF - test_might_fail git config --unset-all merge.log && - git config merge.summary yes && + test_unconfig merge.log && + test_config merge.summary yes && git checkout master && test_tick && @@ -406,8 +419,8 @@ test_expect_success 'merge-msg two tags' ' Common #1 EOF - test_might_fail git config --unset-all merge.log && - git config merge.summary yes && + test_unconfig merge.log && + test_config merge.summary yes && git checkout master && test_tick && @@ -436,8 +449,8 @@ test_expect_success 'merge-msg tag and branch' ' Common #1 EOF - test_might_fail git config --unset-all merge.log && - git config merge.summary yes && + test_unconfig merge.log && + test_config merge.summary yes && git checkout master && test_tick && @@ -464,6 +477,8 @@ test_expect_success 'merge-msg lots of commits' ' echo " ..." } >expected && + test_config merge.summary yes && + git checkout master && test_tick && git fetch . long && @@ -472,4 +487,43 @@ test_expect_success 'merge-msg lots of commits' ' test_cmp expected actual ' +test_expect_success 'merge-msg with "merging" an annotated tag' ' + test_config merge.log true && + + git checkout master^0 && + git commit --allow-empty -m "One step ahead" && + git tag -a -m "An annotated one" annote HEAD && + + git checkout master && + git fetch . annote && + + git fmt-merge-msg <.git/FETCH_HEAD >actual && + { + cat <<-\EOF + Merge tag '\''annote'\'' + + An annotated one + + * tag '\''annote'\'': + One step ahead + EOF + } >expected && + test_cmp expected actual && + + test_when_finished "git reset --hard" && + annote=$(git rev-parse annote) && + git merge --no-commit $annote && + { + cat <<-EOF + Merge tag '\''$annote'\'' + + An annotated one + + * tag '\''$annote'\'': + One step ahead + EOF + } >expected && + test_cmp expected .git/MERGE_MSG +' + test_done diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh index 1e7a209efa..9496736a89 100755 --- a/t/t7003-filter-branch.sh +++ b/t/t7003-filter-branch.sh @@ -64,6 +64,20 @@ test_expect_success 'correct GIT_DIR while using -d' ' grep drepo "$TRASHDIR/backup-refs" ' +test_expect_success 'tree-filter works with -d' ' + git init drepo-tree && + ( + cd drepo-tree && + test_commit one && + git filter-branch -d "$TRASHDIR/dfoo" \ + --tree-filter "echo changed >one.t" && + echo changed >expect && + git cat-file blob HEAD:one.t >actual && + test_cmp expect actual && + test_cmp one.t actual + ) +' + test_expect_success 'Fail if commit filter fails' ' test_must_fail git filter-branch -f --commit-filter "exit 1" HEAD ' diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index 2683cba7e3..ff265353a3 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -757,4 +757,115 @@ test_expect_success 'submodule add with an existing name fails unless forced' ' ) ' +test_expect_success 'set up a second submodule' ' + git submodule add ./init2 example2 && + git commit -m "submodule example2 added" +' + +test_expect_success 'submodule deinit should remove the whole submodule section from .git/config' ' + git config submodule.example.foo bar && + git config submodule.example2.frotz nitfol && + git submodule deinit init && + test -z "$(git config --get-regexp "submodule\.example\.")" && + test -n "$(git config --get-regexp "submodule\.example2\.")" && + test -f example2/.git && + rmdir init +' + +test_expect_success 'submodule deinit . deinits all initialized submodules' ' + git submodule update --init && + git config submodule.example.foo bar && + git config submodule.example2.frotz nitfol && + test_must_fail git submodule deinit && + git submodule deinit . >actual && + test -z "$(git config --get-regexp "submodule\.example\.")" && + test -z "$(git config --get-regexp "submodule\.example2\.")" && + test_i18ngrep "Cleared directory .init" actual && + test_i18ngrep "Cleared directory .example2" actual && + rmdir init example2 +' + +test_expect_success 'submodule deinit deinits a submodule when its work tree is missing or empty' ' + git submodule update --init && + rm -rf init example2/* example2/.git && + git submodule deinit init example2 >actual && + test -z "$(git config --get-regexp "submodule\.example\.")" && + test -z "$(git config --get-regexp "submodule\.example2\.")" && + test_i18ngrep ! "Cleared directory .init" actual && + test_i18ngrep "Cleared directory .example2" actual && + rmdir init +' + +test_expect_success 'submodule deinit fails when the submodule contains modifications unless forced' ' + git submodule update --init && + echo X >>init/s && + test_must_fail git submodule deinit init && + test -n "$(git config --get-regexp "submodule\.example\.")" && + test -f example2/.git && + git submodule deinit -f init >actual && + test -z "$(git config --get-regexp "submodule\.example\.")" && + test_i18ngrep "Cleared directory .init" actual && + rmdir init +' + +test_expect_success 'submodule deinit fails when the submodule contains untracked files unless forced' ' + git submodule update --init && + echo X >>init/untracked && + test_must_fail git submodule deinit init && + test -n "$(git config --get-regexp "submodule\.example\.")" && + test -f example2/.git && + git submodule deinit -f init >actual && + test -z "$(git config --get-regexp "submodule\.example\.")" && + test_i18ngrep "Cleared directory .init" actual && + rmdir init +' + +test_expect_success 'submodule deinit fails when the submodule HEAD does not match unless forced' ' + git submodule update --init && + ( + cd init && + git checkout HEAD^ + ) && + test_must_fail git submodule deinit init && + test -n "$(git config --get-regexp "submodule\.example\.")" && + test -f example2/.git && + git submodule deinit -f init >actual && + test -z "$(git config --get-regexp "submodule\.example\.")" && + test_i18ngrep "Cleared directory .init" actual && + rmdir init +' + +test_expect_success 'submodule deinit is silent when used on an uninitialized submodule' ' + git submodule update --init && + git submodule deinit init >actual && + test_i18ngrep "Submodule .example. (.*) unregistered for path .init" actual && + test_i18ngrep "Cleared directory .init" actual && + git submodule deinit init >actual && + test_i18ngrep ! "Submodule .example. (.*) unregistered for path .init" actual && + test_i18ngrep "Cleared directory .init" actual && + git submodule deinit . >actual && + test_i18ngrep ! "Submodule .example. (.*) unregistered for path .init" actual && + test_i18ngrep "Submodule .example2. (.*) unregistered for path .example2" actual && + test_i18ngrep "Cleared directory .init" actual && + git submodule deinit . >actual && + test_i18ngrep ! "Submodule .example. (.*) unregistered for path .init" actual && + test_i18ngrep ! "Submodule .example2. (.*) unregistered for path .example2" actual && + test_i18ngrep "Cleared directory .init" actual && + rmdir init example2 +' + +test_expect_success 'submodule deinit fails when submodule has a .git directory even when forced' ' + git submodule update --init && + ( + cd init && + rm .git && + cp -R ../.git/modules/example .git && + GIT_WORK_TREE=. git config --unset core.worktree + ) && + test_must_fail git submodule deinit init && + test_must_fail git submodule deinit -f init && + test -d init/.git && + test -n "$(git config --get-regexp "submodule\.example\.")" +' + test_done diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh index 1a3d20bdc3..a4ffea0dbe 100755 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh @@ -596,14 +596,14 @@ test_expect_success 'submodule add places git-dir in superprojects git-dir recur git log > ../../../expected ) && git commit -m "added subsubmodule" && - git push + git push origin : ) && (cd .git/modules/deeper/submodule/modules/subsubmodule && git log > ../../../../../actual ) && git add deeper/submodule && git commit -m "update submodule" && - git push && + git push origin : && test_cmp actual expected ) ' @@ -665,8 +665,10 @@ test_expect_success 'submodule add properly re-creates deeper level submodules' test_expect_success 'submodule update properly revives a moved submodule' ' (cd super && + H=$(git rev-parse --short HEAD) && git commit -am "pre move" && - git status >expect&& + H2=$(git rev-parse --short HEAD) && + git status | sed "s/$H/XXX/" >expect && H=$(cd submodule2; git rev-parse HEAD) && git rm --cached submodule2 && rm -rf submodule2 && @@ -675,7 +677,7 @@ test_expect_success 'submodule update properly revives a moved submodule' ' git config -f .gitmodules submodule.submodule2.path "moved/sub module" git commit -am "post move" && git submodule update && - git status >actual && + git status | sed "s/$H2/XXX/" >actual && test_cmp expect actual ) ' diff --git a/t/t7500-commit.sh b/t/t7500-commit.sh index 1c908f4d39..436b7b606e 100755 --- a/t/t7500-commit.sh +++ b/t/t7500-commit.sh @@ -36,8 +36,7 @@ test_expect_success 'nonexistent template file should return error' ' ' test_expect_success 'nonexistent template file in config should return error' ' - git config commit.template "$PWD"/notexist && - test_when_finished "git config --unset commit.template" && + test_config commit.template "$PWD"/notexist && ( GIT_EDITOR="echo hello >\"\$1\"" && export GIT_EDITOR && @@ -93,14 +92,13 @@ test_expect_success '-t option should be short for --template' ' test_expect_success 'config-specified template should commit' ' echo "new template" > "$TEMPLATE" && - git config commit.template "$TEMPLATE" && + test_config commit.template "$TEMPLATE" && echo "more content" >> foo && git add foo && ( test_set_editor "$TEST_DIRECTORY"/t7500/add-content && git commit ) && - git config --unset commit.template && commit_msg_is "new templatecommit message" ' diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh index 292bc082b2..a4938b1e45 100755 --- a/t/t7502-commit.sh +++ b/t/t7502-commit.sh @@ -171,10 +171,9 @@ test_expect_success 'verbose' ' test_expect_success 'verbose respects diff config' ' - git config color.diff always && + test_config color.diff always && git status -v >actual && - grep "\[1mdiff --git" actual && - git config --unset color.diff + grep "\[1mdiff --git" actual ' mesg_with_comment_and_newlines=' @@ -263,32 +262,40 @@ test_expect_success 'cleanup commit message (fail on invalid cleanup mode config test_expect_success 'cleanup commit message (no config and no option uses default)' ' echo content >>file && git add file && - test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment && - git commit --no-status && + ( + test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment && + git commit --no-status + ) && commit_msg_is "commit message" ' test_expect_success 'cleanup commit message (option overrides default)' ' echo content >>file && git add file && - test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment && - git commit --cleanup=whitespace --no-status && + ( + test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment && + git commit --cleanup=whitespace --no-status + ) && commit_msg_is "commit message # comment" ' test_expect_success 'cleanup commit message (config overrides default)' ' echo content >>file && git add file && - test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment && - git -c commit.cleanup=whitespace commit --no-status && + ( + test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment && + git -c commit.cleanup=whitespace commit --no-status + ) && commit_msg_is "commit message # comment" ' test_expect_success 'cleanup commit message (option overrides config)' ' echo content >>file && git add file && - test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment && - git -c commit.cleanup=whitespace commit --cleanup=default && + ( + test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment && + git -c commit.cleanup=whitespace commit --cleanup=default + ) && commit_msg_is "commit message" ' @@ -417,6 +424,18 @@ test_expect_success 'A single-liner subject with a token plus colon is not a foo ' +test_expect_success 'commit -s places sob on third line after two empty lines' ' + git commit -s --allow-empty --allow-empty-message && + cat <<-EOF >expect && + + + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> + + EOF + sed -e "/^#/d" -e "s/^:.*//" .git/COMMIT_EDITMSG >actual && + test_cmp expect actual +' + write_script .git/FAKE_EDITOR <<\EOF mv "$1" "$1.orig" ( @@ -427,16 +446,6 @@ EOF echo '## Custom template' >template -clear_config () { - ( - git config --unset-all "$1" - case $? in - 0|5) exit 0 ;; - *) exit 1 ;; - esac - ) -} - try_commit () { git reset --hard && echo >>negative && @@ -452,67 +461,57 @@ try_commit () { try_commit_status_combo () { test_expect_success 'commit' ' - clear_config commit.status && try_commit "" && test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit' ' - clear_config commit.status && try_commit "" && test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit --status' ' - clear_config commit.status && try_commit --status && test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit --no-status' ' - clear_config commit.status && try_commit --no-status && test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit with commit.status = yes' ' - clear_config commit.status && - git config commit.status yes && + test_config commit.status yes && try_commit "" && test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit with commit.status = no' ' - clear_config commit.status && - git config commit.status no && + test_config commit.status no && try_commit "" && test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit --status with commit.status = yes' ' - clear_config commit.status && - git config commit.status yes && + test_config commit.status yes && try_commit --status && test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit --no-status with commit.status = yes' ' - clear_config commit.status && - git config commit.status yes && + test_config commit.status yes && try_commit --no-status && test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit --status with commit.status = no' ' - clear_config commit.status && - git config commit.status no && + test_config commit.status no && try_commit --status && test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit --no-status with commit.status = no' ' - clear_config commit.status && - git config commit.status no && + test_config commit.status no && try_commit --no-status && test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG ' @@ -526,8 +525,7 @@ use_template="-t template" try_commit_status_combo test_expect_success 'commit --status with custom comment character' ' - test_when_finished "git config --unset core.commentchar" && - git config core.commentchar ";" && + test_config core.commentchar ";" && try_commit --status && test_i18ngrep "^; Changes to be committed:" .git/COMMIT_EDITMSG ' diff --git a/t/t7508-status.sh b/t/t7508-status.sh index aecb4d1e5f..e2ffdacc26 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -131,8 +131,7 @@ cat >expect <<\EOF EOF test_expect_success 'status (advice.statusHints false)' ' - test_when_finished "git config --unset advice.statusHints" && - git config advice.statusHints false && + test_config advice.statusHints false && git status >output && test_i18ncmp expect output @@ -332,8 +331,7 @@ test_expect_success 'status -uno' ' ' test_expect_success 'status (status.showUntrackedFiles no)' ' - git config status.showuntrackedfiles no - test_when_finished "git config --unset status.showuntrackedfiles" && + test_config status.showuntrackedfiles no && git status >output && test_i18ncmp expect output ' @@ -348,12 +346,11 @@ cat >expect <<EOF # # Untracked files not listed EOF -git config advice.statusHints false test_expect_success 'status -uno (advice.statusHints false)' ' + test_config advice.statusHints false && git status -uno >output && test_i18ncmp expect output ' -git config --unset advice.statusHints cat >expect << EOF M dir1/modified @@ -400,8 +397,7 @@ test_expect_success 'status -unormal' ' ' test_expect_success 'status (status.showUntrackedFiles normal)' ' - git config status.showuntrackedfiles normal - test_when_finished "git config --unset status.showuntrackedfiles" && + test_config status.showuntrackedfiles normal git status >output && test_i18ncmp expect output ' @@ -459,8 +455,7 @@ test_expect_success 'status -uall' ' ' test_expect_success 'status (status.showUntrackedFiles all)' ' - git config status.showuntrackedfiles all - test_when_finished "git config --unset status.showuntrackedfiles" && + test_config status.showuntrackedfiles all git status >output && test_i18ncmp expect output ' @@ -485,10 +480,9 @@ test_expect_success 'status -s -uall' ' test_cmp expect output ' test_expect_success 'status -s (status.showUntrackedFiles all)' ' - git config status.showuntrackedfiles all + test_config status.showuntrackedfiles all && git status -s >output && rm -rf dir3 && - git config --unset status.showuntrackedfiles && test_cmp expect output ' @@ -588,15 +582,13 @@ cat >expect <<\EOF EOF test_expect_success 'status with color.ui' ' - git config color.ui always && - test_when_finished "git config --unset color.ui" && + test_config color.ui always && git status | test_decode_color >output && test_i18ncmp expect output ' test_expect_success 'status with color.status' ' - git config color.status always && - test_when_finished "git config --unset color.status" && + test_config color.status always && git status | test_decode_color >output && test_i18ncmp expect output ' @@ -720,8 +712,7 @@ EOF test_expect_success 'status without relative paths' ' - git config status.relativePaths false && - test_when_finished "git config --unset status.relativePaths" && + test_config status.relativePaths false && (cd dir1 && git status) >output && test_i18ncmp expect output @@ -740,8 +731,7 @@ EOF test_expect_success 'status -s without relative paths' ' - git config status.relativePaths false && - test_when_finished "git config --unset status.relativePaths" && + test_config status.relativePaths false && (cd dir1 && git status -s) >output && test_cmp expect output @@ -1038,15 +1028,14 @@ test_expect_success '--ignore-submodules=untracked suppresses submodules with un ' test_expect_success '.gitmodules ignore=untracked suppresses submodules with untracked content' ' - git config diff.ignoreSubmodules dirty && + test_config diff.ignoreSubmodules dirty && git status >output && test_i18ncmp expect output && git config --add -f .gitmodules submodule.subname.ignore untracked && git config --add -f .gitmodules submodule.subname.path sm && git status >output && test_i18ncmp expect output && - git config -f .gitmodules --remove-section submodule.subname && - git config --unset diff.ignoreSubmodules + git config -f .gitmodules --remove-section submodule.subname ' test_expect_success '.git/config ignore=untracked suppresses submodules with untracked content' ' @@ -1066,15 +1055,14 @@ test_expect_success '--ignore-submodules=dirty suppresses submodules with untrac ' test_expect_success '.gitmodules ignore=dirty suppresses submodules with untracked content' ' - git config diff.ignoreSubmodules dirty && + test_config diff.ignoreSubmodules dirty && git status >output && ! test -s actual && git config --add -f .gitmodules submodule.subname.ignore dirty && git config --add -f .gitmodules submodule.subname.path sm && git status >output && test_i18ncmp expect output && - git config -f .gitmodules --remove-section submodule.subname && - git config --unset diff.ignoreSubmodules + git config -f .gitmodules --remove-section submodule.subname ' test_expect_success '.git/config ignore=dirty suppresses submodules with untracked content' ' @@ -1291,15 +1279,13 @@ cat > expect << EOF EOF test_expect_success "status (core.commentchar with submodule summary)" ' - test_when_finished "git config --unset core.commentchar" && - git config core.commentchar ";" && + test_config core.commentchar ";" && git status >output && test_i18ncmp expect output ' test_expect_success "status (core.commentchar with two chars with submodule summary)" ' - test_when_finished "git config --unset core.commentchar" && - git config core.commentchar ";;" && + test_config core.commentchar ";;" && git status >output && test_i18ncmp expect output ' diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh index 9d4610629d..bf08d4e098 100755 --- a/t/t7512-status-help.sh +++ b/t/t7512-status-help.sh @@ -77,7 +77,7 @@ test_expect_success 'status when rebase in progress before resolving conflicts' ONTO=$(git rev-parse --short HEAD^^) && test_must_fail git rebase HEAD^ --onto HEAD^^ && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached at $ONTO # You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''. # (fix conflicts and then run "git rebase --continue") # (use "git rebase --skip" to skip this patch) @@ -104,7 +104,7 @@ test_expect_success 'status when rebase in progress before rebase --continue' ' echo three >main.txt && git add main.txt && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached at $ONTO # You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''. # (all conflicts fixed: run "git rebase --continue") # @@ -136,7 +136,7 @@ test_expect_success 'status during rebase -i when conflicts unresolved' ' ONTO=$(git rev-parse --short rebase_i_conflicts) && test_must_fail git rebase -i rebase_i_conflicts && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached at $ONTO # You are currently rebasing branch '\''rebase_i_conflicts_second'\'' on '\''$ONTO'\''. # (fix conflicts and then run "git rebase --continue") # (use "git rebase --skip" to skip this patch) @@ -162,7 +162,7 @@ test_expect_success 'status during rebase -i after resolving conflicts' ' test_must_fail git rebase -i rebase_i_conflicts && git add main.txt && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached at $ONTO # You are currently rebasing branch '\''rebase_i_conflicts_second'\'' on '\''$ONTO'\''. # (all conflicts fixed: run "git rebase --continue") # @@ -188,9 +188,10 @@ test_expect_success 'status when rebasing -i in edit mode' ' export FAKE_LINES && test_when_finished "git rebase --abort" && ONTO=$(git rev-parse --short HEAD~2) && + TGT=$(git rev-parse --short two_rebase_i) && git rebase -i HEAD~2 && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached from $TGT # You are currently editing a commit while rebasing branch '\''rebase_i_edit'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) @@ -215,8 +216,9 @@ test_expect_success 'status when splitting a commit' ' ONTO=$(git rev-parse --short HEAD~3) && git rebase -i HEAD~3 && git reset HEAD^ && + TGT=$(git rev-parse --short HEAD) && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached at $TGT # You are currently splitting a commit while rebasing branch '\''split_commit'\'' on '\''$ONTO'\''. # (Once your working directory is clean, run "git rebase --continue") # @@ -244,10 +246,11 @@ test_expect_success 'status after editing the last commit with --amend during a export FAKE_LINES && test_when_finished "git rebase --abort" && ONTO=$(git rev-parse --short HEAD~3) && + TGT=$(git rev-parse --short three_amend) && git rebase -i HEAD~3 && git commit --amend -m "foo" && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached from $TGT # You are currently editing a commit while rebasing branch '\''amend_last'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) @@ -277,7 +280,7 @@ test_expect_success 'status: (continue first edit) second edit' ' git rebase -i HEAD~3 && git rebase --continue && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached from $ONTO # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) @@ -299,7 +302,7 @@ test_expect_success 'status: (continue first edit) second edit and split' ' git rebase --continue && git reset HEAD^ && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached from $ONTO # You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (Once your working directory is clean, run "git rebase --continue") # @@ -326,7 +329,7 @@ test_expect_success 'status: (continue first edit) second edit and amend' ' git rebase --continue && git commit --amend -m "foo" && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached from $ONTO # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) @@ -348,7 +351,7 @@ test_expect_success 'status: (amend first edit) second edit' ' git commit --amend -m "a" && git rebase --continue && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached from $ONTO # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) @@ -371,7 +374,7 @@ test_expect_success 'status: (amend first edit) second edit and split' ' git rebase --continue && git reset HEAD^ && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached from $ONTO # You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (Once your working directory is clean, run "git rebase --continue") # @@ -399,7 +402,7 @@ test_expect_success 'status: (amend first edit) second edit and amend' ' git rebase --continue && git commit --amend -m "d" && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached from $ONTO # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) @@ -423,7 +426,7 @@ test_expect_success 'status: (split first edit) second edit' ' git commit -m "e" && git rebase --continue && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached from $ONTO # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) @@ -448,7 +451,7 @@ test_expect_success 'status: (split first edit) second edit and split' ' git rebase --continue && git reset HEAD^ && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached from $ONTO # You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (Once your working directory is clean, run "git rebase --continue") # @@ -478,7 +481,7 @@ test_expect_success 'status: (split first edit) second edit and amend' ' git rebase --continue && git commit --amend -m "h" && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached from $ONTO # You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''. # (use "git commit --amend" to amend the current commit) # (use "git rebase --continue" once you are satisfied with your changes) @@ -573,9 +576,10 @@ test_expect_success 'status when bisecting' ' git bisect start && git bisect bad && git bisect good one_bisect && - cat >expected <<-\EOF && - # Not currently on any branch. - # You are currently bisecting branch '\''bisect'\''. + TGT=$(git rev-parse --short two_bisect) && + cat >expected <<-EOF && + # HEAD detached at $TGT + # You are currently bisecting, started from branch '\''bisect'\''. # (use "git bisect reset" to get back to the original branch) # nothing to commit (use -u to show untracked files) @@ -597,7 +601,7 @@ test_expect_success 'status when rebase conflicts with statushints disabled' ' ONTO=$(git rev-parse --short HEAD^^) && test_must_fail git rebase HEAD^ --onto HEAD^^ && cat >expected <<-EOF && - # Not currently on any branch. + # HEAD detached at $ONTO # You are currently rebasing branch '\''statushints_disabled'\'' on '\''$ONTO'\''. # # Unmerged paths: @@ -663,5 +667,73 @@ test_expect_success 'status when cherry-picking after resolving conflicts' ' test_i18ncmp expected actual ' +test_expect_success 'status showing detached from a tag' ' + test_commit atag tagging && + git checkout atag && + cat >expected <<-\EOF + # HEAD detached at atag + nothing to commit (use -u to show untracked files) + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + +test_expect_success 'status while reverting commit (conflicts)' ' + git checkout master && + echo before >to-revert.txt && + test_commit before to-revert.txt && + echo old >to-revert.txt && + test_commit old to-revert.txt && + echo new >to-revert.txt && + test_commit new to-revert.txt && + TO_REVERT=$(git rev-parse --short HEAD^) && + test_must_fail git revert $TO_REVERT && + cat >expected <<-EOF + # On branch master + # You are currently reverting commit $TO_REVERT. + # (fix conflicts and run "git revert --continue") + # (use "git revert --abort" to cancel the revert operation) + # + # Unmerged paths: + # (use "git reset HEAD <file>..." to unstage) + # (use "git add <file>..." to mark resolution) + # + # both modified: to-revert.txt + # + no changes added to commit (use "git add" and/or "git commit -a") + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + +test_expect_success 'status while reverting commit (conflicts resolved)' ' + echo reverted >to-revert.txt && + git add to-revert.txt && + cat >expected <<-EOF + # On branch master + # You are currently reverting commit $TO_REVERT. + # (all conflicts fixed: run "git revert --continue") + # (use "git revert --abort" to cancel the revert operation) + # + # Changes to be committed: + # (use "git reset HEAD <file>..." to unstage) + # + # modified: to-revert.txt + # + # Untracked files not listed (use -u option to show untracked files) + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' + +test_expect_success 'status after reverting commit' ' + git revert --continue && + cat >expected <<-\EOF + # On branch master + nothing to commit (use -u to show untracked files) + EOF + git status --untracked-files=no >actual && + test_i18ncmp expected actual +' test_done diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index 5e19598fe7..2f70433568 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -56,7 +56,8 @@ create_merge_msgs () { echo && git log --no-merges ^HEAD c2 c3 } >squash.1-5-9 && - echo >msg.nolog && + : >msg.nologff && + echo >msg.nolognoff && { echo "* tag 'c3':" && echo " commit 3" && @@ -244,8 +245,7 @@ test_expect_success 'merges with --ff-only' ' test_expect_success 'merges with merge.ff=only' ' git reset --hard c1 && test_tick && - test_when_finished "git config --unset merge.ff" && - git config merge.ff only && + test_config merge.ff "only" && test_must_fail git merge c2 && test_must_fail git merge c3 && test_must_fail git merge c2 c3 && @@ -336,7 +336,7 @@ test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c1 with c2 (no-commit in config)' ' git reset --hard c1 && - git config branch.master.mergeoptions "--no-commit" && + test_config branch.master.mergeoptions "--no-commit" && git merge c2 && verify_merge file result.1-5 && verify_head $c1 && @@ -346,12 +346,11 @@ test_expect_success 'merge c1 with c2 (no-commit in config)' ' test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c1 with c2 (log in config)' ' - git config branch.master.mergeoptions "" && git reset --hard c1 && git merge --log c2 && git show -s --pretty=tformat:%s%n%b >expect && - git config branch.master.mergeoptions --log && + test_config branch.master.mergeoptions "--log" && git reset --hard c1 && git merge c2 && git show -s --pretty=tformat:%s%n%b >actual && @@ -360,17 +359,12 @@ test_expect_success 'merge c1 with c2 (log in config)' ' ' test_expect_success 'merge c1 with c2 (log in config gets overridden)' ' - test_when_finished "git config --remove-section branch.master" && - test_when_finished "git config --remove-section merge" && - test_might_fail git config --remove-section branch.master && - test_might_fail git config --remove-section merge && - git reset --hard c1 && git merge c2 && git show -s --pretty=tformat:%s%n%b >expect && - git config branch.master.mergeoptions "--no-log" && - git config merge.log true && + test_config branch.master.mergeoptions "--no-log" && + test_config merge.log "true" && git reset --hard c1 && git merge c2 && git show -s --pretty=tformat:%s%n%b >actual && @@ -380,7 +374,7 @@ test_expect_success 'merge c1 with c2 (log in config gets overridden)' ' test_expect_success 'merge c1 with c2 (squash in config)' ' git reset --hard c1 && - git config branch.master.mergeoptions "--squash" && + test_config branch.master.mergeoptions "--squash" && git merge c2 && verify_merge file result.1-5 && verify_head $c1 && @@ -392,7 +386,7 @@ test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'override config option -n with --summary' ' git reset --hard c1 && - git config branch.master.mergeoptions "-n" && + test_config branch.master.mergeoptions "-n" && test_tick && git merge --summary c2 >diffstat.txt && verify_merge file result.1-5 msg.1-5 && @@ -406,7 +400,7 @@ test_expect_success 'override config option -n with --summary' ' test_expect_success 'override config option -n with --stat' ' git reset --hard c1 && - git config branch.master.mergeoptions "-n" && + test_config branch.master.mergeoptions "-n" && test_tick && git merge --stat c2 >diffstat.txt && verify_merge file result.1-5 msg.1-5 && @@ -422,7 +416,7 @@ test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'override config option --stat' ' git reset --hard c1 && - git config branch.master.mergeoptions "--stat" && + test_config branch.master.mergeoptions "--stat" && test_tick && git merge -n c2 >diffstat.txt && verify_merge file result.1-5 msg.1-5 && @@ -438,7 +432,7 @@ test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c1 with c2 (override --no-commit)' ' git reset --hard c1 && - git config branch.master.mergeoptions "--no-commit" && + test_config branch.master.mergeoptions "--no-commit" && test_tick && git merge --commit c2 && verify_merge file result.1-5 msg.1-5 && @@ -449,7 +443,7 @@ test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c1 with c2 (override --squash)' ' git reset --hard c1 && - git config branch.master.mergeoptions "--squash" && + test_config branch.master.mergeoptions "--squash" && test_tick && git merge --no-squash c2 && verify_merge file result.1-5 msg.1-5 && @@ -460,7 +454,6 @@ test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c0 with c1 (no-ff)' ' git reset --hard c0 && - git config branch.master.mergeoptions "" && test_tick && git merge --no-ff c1 && verify_merge file result.1 && @@ -471,10 +464,9 @@ test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c0 with c1 (merge.ff=false)' ' git reset --hard c0 && - git config merge.ff false && + test_config merge.ff "false" && test_tick && git merge c1 && - git config --remove-section merge && verify_merge file result.1 && verify_parents $c0 $c1 ' @@ -482,22 +474,19 @@ test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'combine branch.master.mergeoptions with merge.ff' ' git reset --hard c0 && - git config branch.master.mergeoptions --ff && - git config merge.ff false && + test_config branch.master.mergeoptions "--ff" && + test_config merge.ff "false" && test_tick && git merge c1 && - git config --remove-section "branch.master" && - git config --remove-section "merge" && verify_merge file result.1 && verify_parents "$c0" ' test_expect_success 'tolerate unknown values for merge.ff' ' git reset --hard c0 && - git config merge.ff something-new && + test_config merge.ff "something-new" && test_tick && git merge c1 2>message && - git config --remove-section "merge" && verify_head "$c1" && test_cmp empty message ' @@ -515,7 +504,7 @@ test_expect_success 'combining --ff-only and --no-ff is refused' ' test_expect_success 'merge c0 with c1 (ff overrides no-ff)' ' git reset --hard c0 && - git config branch.master.mergeoptions "--no-ff" && + test_config branch.master.mergeoptions "--no-ff" && git merge --ff c1 && verify_merge file result.1 && verify_head $c1 @@ -525,14 +514,20 @@ test_expect_success 'merge log message' ' git reset --hard c0 && git merge --no-log c2 && git show -s --pretty=format:%b HEAD >msg.act && - test_cmp msg.nolog msg.act && + test_cmp msg.nologff msg.act && + + git reset --hard c0 && + test_config branch.master.mergeoptions "--no-ff" && + git merge --no-log c2 && + git show -s --pretty=format:%b HEAD >msg.act && + test_cmp msg.nolognoff msg.act && git merge --log c3 && git show -s --pretty=format:%b HEAD >msg.act && test_cmp msg.log msg.act && git reset --hard HEAD^ && - git config merge.log yes && + test_config merge.log "yes" && git merge c3 && git show -s --pretty=format:%b HEAD >msg.act && test_cmp msg.log msg.act @@ -542,7 +537,6 @@ test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c1 with c0, c2, c0, and c1' ' git reset --hard c1 && - git config branch.master.mergeoptions "" && test_tick && git merge c0 c2 c0 c1 && verify_merge file result.1-5 && @@ -553,7 +547,6 @@ test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c1 with c0, c2, c0, and c1' ' git reset --hard c1 && - git config branch.master.mergeoptions "" && test_tick && git merge c0 c2 c0 c1 && verify_merge file result.1-5 && @@ -564,7 +557,6 @@ test_debug 'git log --graph --decorate --oneline --all' test_expect_success 'merge c1 with c1 and c2' ' git reset --hard c1 && - git config branch.master.mergeoptions "" && test_tick && git merge c1 c2 && verify_merge file result.1-5 && diff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh index b44b293950..25dac79889 100755 --- a/t/t7601-merge-pull-config.sh +++ b/t/t7601-merge-pull-config.sh @@ -109,7 +109,7 @@ test_expect_success 'setup conflicted merge' ' ' # First do the merge with resolve and recursive then verify that -# recusive is choosen. +# recusive is chosen. test_expect_success 'merge picks up the best result' ' git config --unset-all pull.twohead && diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh index bc38737b2a..d526b1d96a 100755 --- a/t/t7610-mergetool.sh +++ b/t/t7610-mergetool.sh @@ -237,7 +237,7 @@ test_expect_success 'mergetool takes partial path' ' git submodule update -N && test_must_fail git merge master && - #shouldnt need these lines + #should not need these lines #( yes "d" | git mergetool file11 >/dev/null 2>&1 ) && #( yes "d" | git mergetool file12 >/dev/null 2>&1 ) && #( yes "l" | git mergetool submod >/dev/null 2>&1 ) && diff --git a/t/t7612-merge-verify-signatures.sh b/t/t7612-merge-verify-signatures.sh new file mode 100755 index 0000000000..21a0bf8fb8 --- /dev/null +++ b/t/t7612-merge-verify-signatures.sh @@ -0,0 +1,61 @@ +#!/bin/sh + +test_description='merge signature verification tests' +. ./test-lib.sh +. "$TEST_DIRECTORY/lib-gpg.sh" + +test_expect_success GPG 'create signed commits' ' + echo 1 >file && git add file && + test_tick && git commit -m initial && + git tag initial && + + git checkout -b side-signed && + echo 3 >elif && git add elif && + test_tick && git commit -S -m "signed on side" && + git checkout initial && + + git checkout -b side-unsigned && + echo 3 >foo && git add foo && + test_tick && git commit -m "unsigned on side" && + git checkout initial && + + git checkout -b side-bad && + echo 3 >bar && git add bar && + test_tick && git commit -S -m "bad on side" && + git cat-file commit side-bad >raw && + sed -e "s/bad/forged bad/" raw >forged && + git hash-object -w -t commit forged >forged.commit && + git checkout initial && + + git checkout -b side-untrusted && + echo 3 >baz && git add baz && + test_tick && git commit -SB7227189 -m "untrusted on side" + + git checkout master +' + +test_expect_success GPG 'merge unsigned commit with verification' ' + test_must_fail git merge --ff-only --verify-signatures side-unsigned 2>mergeerror && + test_i18ngrep "does not have a GPG signature" mergeerror +' + +test_expect_success GPG 'merge commit with bad signature with verification' ' + test_must_fail git merge --ff-only --verify-signatures $(cat forged.commit) 2>mergeerror && + test_i18ngrep "has a bad GPG signature" mergeerror +' + +test_expect_success GPG 'merge commit with untrusted signature with verification' ' + test_must_fail git merge --ff-only --verify-signatures side-untrusted 2>mergeerror && + test_i18ngrep "has an untrusted GPG signature" mergeerror +' + +test_expect_success GPG 'merge signed commit with verification' ' + git merge --verbose --ff-only --verify-signatures side-signed >mergeoutput && + test_i18ngrep "has a good GPG signature" mergeoutput +' + +test_expect_success GPG 'merge commit with bad signature without verification' ' + git merge $(cat forged.commit) +' + +test_done diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index eb1d3f85b5..a6bd99eaf5 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -1,6 +1,6 @@ #!/bin/sh # -# Copyright (c) 2009, 2010 David Aguilar +# Copyright (c) 2009, 2010, 2012, 2013 David Aguilar # test_description='git-difftool @@ -10,47 +10,19 @@ Testing basic diff tool invocation . ./test-lib.sh -remove_config_vars() +difftool_test_setup () { - # Unset all config variables used by git-difftool - git config --unset diff.tool - git config --unset diff.guitool - git config --unset difftool.test-tool.cmd - git config --unset difftool.prompt - git config --unset merge.tool - git config --unset mergetool.test-tool.cmd - git config --unset mergetool.prompt - return 0 + test_config diff.tool test-tool && + test_config difftool.test-tool.cmd 'cat "$LOCAL"' && + test_config difftool.bogus-tool.cmd false } -restore_test_defaults() -{ - # Restores the test defaults used by several tests - remove_config_vars - unset GIT_DIFF_TOOL - unset GIT_DIFFTOOL_PROMPT - unset GIT_DIFFTOOL_NO_PROMPT - git config diff.tool test-tool && - git config difftool.test-tool.cmd 'cat $LOCAL' - git config difftool.bogus-tool.cmd false -} - -prompt_given() +prompt_given () { prompt="$1" test "$prompt" = "Launch 'test-tool' [Y/n]: branch" } -stdin_contains() -{ - grep >/dev/null "$1" -} - -stdin_doesnot_contain() -{ - ! stdin_contains "$1" -} - # Create a file on master and change it on branch test_expect_success PERL 'setup' ' echo master >file && @@ -65,249 +37,237 @@ test_expect_success PERL 'setup' ' # Configure a custom difftool.<tool>.cmd and use it test_expect_success PERL 'custom commands' ' - restore_test_defaults && - git config difftool.test-tool.cmd "cat \$REMOTE" && + difftool_test_setup && + test_config difftool.test-tool.cmd "cat \"\$REMOTE\"" && + echo master >expect && + git difftool --no-prompt branch >actual && + test_cmp expect actual && - diff=$(git difftool --no-prompt branch) && - test "$diff" = "master" && - - restore_test_defaults && - diff=$(git difftool --no-prompt branch) && - test "$diff" = "branch" + test_config difftool.test-tool.cmd "cat \"\$LOCAL\"" && + echo branch >expect && + git difftool --no-prompt branch >actual && + test_cmp expect actual ' -# Ensures that a custom difftool.<tool>.cmd overrides built-ins -test_expect_success PERL 'custom commands override built-ins' ' - restore_test_defaults && - git config difftool.defaults.cmd "cat \$REMOTE" && - - diff=$(git difftool --tool defaults --no-prompt branch) && - test "$diff" = "master" && - - git config --unset difftool.defaults.cmd +test_expect_success PERL 'custom tool commands override built-ins' ' + test_config difftool.vimdiff.cmd "cat \"\$REMOTE\"" && + echo master >expect && + git difftool --tool vimdiff --no-prompt branch >actual && + test_cmp expect actual ' -# Ensures that git-difftool ignores bogus --tool values test_expect_success PERL 'difftool ignores bad --tool values' ' - diff=$(git difftool --no-prompt --tool=bad-tool branch) - test "$?" = 1 && - test "$diff" = "" + : >expect && + test_expect_code 1 \ + git difftool --no-prompt --tool=bad-tool branch >actual && + test_cmp expect actual ' test_expect_success PERL 'difftool forwards arguments to diff' ' + difftool_test_setup && >for-diff && git add for-diff && echo changes>for-diff && git add for-diff && - diff=$(git difftool --cached --no-prompt -- for-diff) && - test "$diff" = "" && + : >expect && + git difftool --cached --no-prompt -- for-diff >actual && + test_cmp expect actual && git reset -- for-diff && rm for-diff ' test_expect_success PERL 'difftool honors --gui' ' - git config merge.tool bogus-tool && - git config diff.tool bogus-tool && - git config diff.guitool test-tool && + difftool_test_setup && + test_config merge.tool bogus-tool && + test_config diff.tool bogus-tool && + test_config diff.guitool test-tool && - diff=$(git difftool --no-prompt --gui branch) && - test "$diff" = "branch" && - - restore_test_defaults + echo branch >expect && + git difftool --no-prompt --gui branch >actual && + test_cmp expect actual ' test_expect_success PERL 'difftool --gui last setting wins' ' - git config diff.guitool bogus-tool && - git difftool --no-prompt --gui --no-gui && - - git config merge.tool bogus-tool && - git config diff.tool bogus-tool && - git config diff.guitool test-tool && - diff=$(git difftool --no-prompt --no-gui --gui branch) && - test "$diff" = "branch" && + difftool_test_setup && + : >expect && + git difftool --no-prompt --gui --no-gui >actual && + test_cmp expect actual && - restore_test_defaults + test_config merge.tool bogus-tool && + test_config diff.tool bogus-tool && + test_config diff.guitool test-tool && + echo branch >expect && + git difftool --no-prompt --no-gui --gui branch >actual && + test_cmp expect actual ' test_expect_success PERL 'difftool --gui works without configured diff.guitool' ' - git config diff.tool test-tool && - - diff=$(git difftool --no-prompt --gui branch) && - test "$diff" = "branch" && - - restore_test_defaults + difftool_test_setup && + echo branch >expect && + git difftool --no-prompt --gui branch >actual && + test_cmp expect actual ' # Specify the diff tool using $GIT_DIFF_TOOL test_expect_success PERL 'GIT_DIFF_TOOL variable' ' - test_might_fail git config --unset diff.tool && - GIT_DIFF_TOOL=test-tool && - export GIT_DIFF_TOOL && - - diff=$(git difftool --no-prompt branch) && - test "$diff" = "branch" && - - restore_test_defaults + difftool_test_setup && + git config --unset diff.tool && + echo branch >expect && + GIT_DIFF_TOOL=test-tool git difftool --no-prompt branch >actual && + test_cmp expect actual ' # Test the $GIT_*_TOOL variables and ensure # that $GIT_DIFF_TOOL always wins unless --tool is specified test_expect_success PERL 'GIT_DIFF_TOOL overrides' ' - git config diff.tool bogus-tool && - git config merge.tool bogus-tool && + difftool_test_setup && + test_config diff.tool bogus-tool && + test_config merge.tool bogus-tool && - GIT_DIFF_TOOL=test-tool && - export GIT_DIFF_TOOL && + echo branch >expect && + GIT_DIFF_TOOL=test-tool git difftool --no-prompt branch >actual && + test_cmp expect actual && - diff=$(git difftool --no-prompt branch) && - test "$diff" = "branch" && - - GIT_DIFF_TOOL=bogus-tool && - export GIT_DIFF_TOOL && - - diff=$(git difftool --no-prompt --tool=test-tool branch) && - test "$diff" = "branch" && - - restore_test_defaults + test_config diff.tool bogus-tool && + test_config merge.tool bogus-tool && + GIT_DIFF_TOOL=bogus-tool \ + git difftool --no-prompt --tool=test-tool branch >actual && + test_cmp expect actual ' # Test that we don't have to pass --no-prompt to difftool # when $GIT_DIFFTOOL_NO_PROMPT is true test_expect_success PERL 'GIT_DIFFTOOL_NO_PROMPT variable' ' - GIT_DIFFTOOL_NO_PROMPT=true && - export GIT_DIFFTOOL_NO_PROMPT && - - diff=$(git difftool branch) && - test "$diff" = "branch" && - - restore_test_defaults + difftool_test_setup && + echo branch >expect && + GIT_DIFFTOOL_NO_PROMPT=true git difftool branch >actual && + test_cmp expect actual ' # git-difftool supports the difftool.prompt variable. # Test that GIT_DIFFTOOL_PROMPT can override difftool.prompt = false test_expect_success PERL 'GIT_DIFFTOOL_PROMPT variable' ' - git config difftool.prompt false && - GIT_DIFFTOOL_PROMPT=true && - export GIT_DIFFTOOL_PROMPT && - - prompt=$(echo | git difftool branch | tail -1) && - prompt_given "$prompt" && - - restore_test_defaults + difftool_test_setup && + test_config difftool.prompt false && + echo >input && + GIT_DIFFTOOL_PROMPT=true git difftool branch <input >output && + prompt=$(tail -1 <output) && + prompt_given "$prompt" ' # Test that we don't have to pass --no-prompt when difftool.prompt is false test_expect_success PERL 'difftool.prompt config variable is false' ' - git config difftool.prompt false && - - diff=$(git difftool branch) && - test "$diff" = "branch" && - - restore_test_defaults + difftool_test_setup && + test_config difftool.prompt false && + echo branch >expect && + git difftool branch >actual && + test_cmp expect actual ' # Test that we don't have to pass --no-prompt when mergetool.prompt is false test_expect_success PERL 'difftool merge.prompt = false' ' + difftool_test_setup && test_might_fail git config --unset difftool.prompt && - git config mergetool.prompt false && - - diff=$(git difftool branch) && - test "$diff" = "branch" && - - restore_test_defaults + test_config mergetool.prompt false && + echo branch >expect && + git difftool branch >actual && + test_cmp expect actual ' # Test that the -y flag can override difftool.prompt = true test_expect_success PERL 'difftool.prompt can overridden with -y' ' - git config difftool.prompt true && - - diff=$(git difftool -y branch) && - test "$diff" = "branch" && - - restore_test_defaults + difftool_test_setup && + test_config difftool.prompt true && + echo branch >expect && + git difftool -y branch >actual && + test_cmp expect actual ' # Test that the --prompt flag can override difftool.prompt = false test_expect_success PERL 'difftool.prompt can overridden with --prompt' ' - git config difftool.prompt false && - - prompt=$(echo | git difftool --prompt branch | tail -1) && - prompt_given "$prompt" && - - restore_test_defaults + difftool_test_setup && + test_config difftool.prompt false && + echo >input && + git difftool --prompt branch <input >output && + prompt=$(tail -1 <output) && + prompt_given "$prompt" ' # Test that the last flag passed on the command-line wins test_expect_success PERL 'difftool last flag wins' ' - diff=$(git difftool --prompt --no-prompt branch) && - test "$diff" = "branch" && - - restore_test_defaults && - - prompt=$(echo | git difftool --no-prompt --prompt branch | tail -1) && - prompt_given "$prompt" && - - restore_test_defaults + difftool_test_setup && + echo branch >expect && + git difftool --prompt --no-prompt branch >actual && + test_cmp expect actual && + echo >input && + git difftool --no-prompt --prompt branch <input >output && + prompt=$(tail -1 <output) && + prompt_given "$prompt" ' # git-difftool falls back to git-mergetool config variables # so test that behavior here test_expect_success PERL 'difftool + mergetool config variables' ' - remove_config_vars && - git config merge.tool test-tool && - git config mergetool.test-tool.cmd "cat \$LOCAL" && - - diff=$(git difftool --no-prompt branch) && - test "$diff" = "branch" && + test_config merge.tool test-tool && + test_config mergetool.test-tool.cmd "cat \$LOCAL" && + echo branch >expect && + git difftool --no-prompt branch >actual && + test_cmp expect actual && # set merge.tool to something bogus, diff.tool to test-tool - git config merge.tool bogus-tool && - git config diff.tool test-tool && - - diff=$(git difftool --no-prompt branch) && - test "$diff" = "branch" && - - restore_test_defaults + test_config merge.tool bogus-tool && + test_config diff.tool test-tool && + git difftool --no-prompt branch >actual && + test_cmp expect actual ' test_expect_success PERL 'difftool.<tool>.path' ' - git config difftool.tkdiff.path echo && - diff=$(git difftool --tool=tkdiff --no-prompt branch) && - git config --unset difftool.tkdiff.path && - lines=$(echo "$diff" | grep file | wc -l) && - test "$lines" -eq 1 && - - restore_test_defaults + test_config difftool.tkdiff.path echo && + git difftool --tool=tkdiff --no-prompt branch >output && + lines=$(grep file output | wc -l) && + test "$lines" -eq 1 ' test_expect_success PERL 'difftool --extcmd=cat' ' - diff=$(git difftool --no-prompt --extcmd=cat branch) && - test "$diff" = branch"$LF"master + echo branch >expect && + echo master >>expect && + git difftool --no-prompt --extcmd=cat branch >actual && + test_cmp expect actual ' test_expect_success PERL 'difftool --extcmd cat' ' - diff=$(git difftool --no-prompt --extcmd cat branch) && - test "$diff" = branch"$LF"master + echo branch >expect && + echo master >>expect && + git difftool --no-prompt --extcmd=cat branch >actual && + test_cmp expect actual ' test_expect_success PERL 'difftool -x cat' ' - diff=$(git difftool --no-prompt -x cat branch) && - test "$diff" = branch"$LF"master + echo branch >expect && + echo master >>expect && + git difftool --no-prompt -x cat branch >actual && + test_cmp expect actual ' test_expect_success PERL 'difftool --extcmd echo arg1' ' - diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"echo\ \$1\" branch) && - test "$diff" = file + echo file >expect && + git difftool --no-prompt \ + --extcmd sh\ -c\ \"echo\ \$1\" branch >actual && + test_cmp expect actual ' test_expect_success PERL 'difftool --extcmd cat arg1' ' - diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"cat\ \$1\" branch) && - test "$diff" = master + echo master >expect && + git difftool --no-prompt \ + --extcmd sh\ -c\ \"cat\ \$1\" branch >actual && + test_cmp expect actual ' test_expect_success PERL 'difftool --extcmd cat arg2' ' - diff=$(git difftool --no-prompt --extcmd sh\ -c\ \"cat\ \$2\" branch) && - test "$diff" = branch + echo branch >expect && + git difftool --no-prompt \ + --extcmd sh\ -c\ \"cat\ \$2\" branch >actual && + test_cmp expect actual ' # Create a second file on master and a different version on branch @@ -324,26 +284,26 @@ test_expect_success PERL 'setup with 2 files different' ' ' test_expect_success PERL 'say no to the first file' ' - diff=$( (echo n; echo) | git difftool -x cat branch ) && - - echo "$diff" | stdin_contains m2 && - echo "$diff" | stdin_contains br2 && - echo "$diff" | stdin_doesnot_contain master && - echo "$diff" | stdin_doesnot_contain branch + (echo n && echo) >input && + git difftool -x cat branch <input >output && + grep m2 output && + grep br2 output && + ! grep master output && + ! grep branch output ' test_expect_success PERL 'say no to the second file' ' - diff=$( (echo; echo n) | git difftool -x cat branch ) && - - echo "$diff" | stdin_contains master && - echo "$diff" | stdin_contains branch && - echo "$diff" | stdin_doesnot_contain m2 && - echo "$diff" | stdin_doesnot_contain br2 + (echo && echo n) >input && + git difftool -x cat branch <input >output && + grep master output && + grep branch output && + ! grep m2 output && + ! grep br2 output ' test_expect_success PERL 'difftool --tool-help' ' - tool_help=$(git difftool --tool-help) && - echo "$tool_help" | stdin_contains tool + git difftool --tool-help >output && + grep tool output ' test_expect_success PERL 'setup change in subdirectory' ' @@ -354,34 +314,97 @@ test_expect_success PERL 'setup change in subdirectory' ' git commit -m "added sub/sub" && echo test >>file && echo test >>sub/sub && - git add . && + git add file sub/sub && git commit -m "modified both" ' -test_expect_success PERL 'difftool -d' ' - diff=$(git difftool -d --extcmd ls branch) && - echo "$diff" | stdin_contains sub && - echo "$diff" | stdin_contains file +run_dir_diff_test () { + test_expect_success PERL "$1 --no-symlinks" " + symlinks=--no-symlinks && + $2 + " + test_expect_success PERL,SYMLINKS "$1 --symlinks" " + symlinks=--symlinks && + $2 + " +} + +run_dir_diff_test 'difftool -d' ' + git difftool -d $symlinks --extcmd ls branch >output && + grep sub output && + grep file output ' -test_expect_success PERL 'difftool --dir-diff' ' - diff=$(git difftool --dir-diff --extcmd ls branch) && - echo "$diff" | stdin_contains sub && - echo "$diff" | stdin_contains file +run_dir_diff_test 'difftool --dir-diff' ' + git difftool --dir-diff $symlinks --extcmd ls branch >output && + grep sub output && + grep file output ' -test_expect_success PERL 'difftool --dir-diff ignores --prompt' ' - diff=$(git difftool --dir-diff --prompt --extcmd ls branch) && - echo "$diff" | stdin_contains sub && - echo "$diff" | stdin_contains file +run_dir_diff_test 'difftool --dir-diff ignores --prompt' ' + git difftool --dir-diff $symlinks --prompt --extcmd ls branch >output && + grep sub output && + grep file output ' -test_expect_success PERL 'difftool --dir-diff from subdirectory' ' +run_dir_diff_test 'difftool --dir-diff from subdirectory' ' ( cd sub && - diff=$(git difftool --dir-diff --extcmd ls branch) && - echo "$diff" | stdin_contains sub && - echo "$diff" | stdin_contains file + git difftool --dir-diff $symlinks --extcmd ls branch >output && + grep sub output && + grep file output + ) +' + +write_script .git/CHECK_SYMLINKS <<\EOF +for f in file file2 sub/sub +do + echo "$f" + readlink "$2/$f" +done >actual +EOF + +test_expect_success PERL,SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' ' + cat >expect <<-EOF && + file + $(pwd)/file + file2 + $(pwd)/file2 + sub/sub + $(pwd)/sub/sub + EOF + git difftool --dir-diff --symlink \ + --extcmd "./.git/CHECK_SYMLINKS" branch HEAD && + test_cmp actual expect +' + +write_script modify-file <<\EOF +echo "new content" >file +EOF + +test_expect_success PERL 'difftool --no-symlinks does not overwrite working tree file ' ' + echo "orig content" >file && + git difftool --dir-diff --no-symlinks --extcmd "$(pwd)/modify-file" branch && + echo "new content" >expect && + test_cmp expect file +' + +write_script modify-both-files <<\EOF +echo "wt content" >file && +echo "tmp content" >"$2/file" && +echo "$2" >tmpdir +EOF + +test_expect_success PERL 'difftool --no-symlinks detects conflict ' ' + ( + TMPDIR=$TRASH_DIRECTORY && + export TMPDIR && + echo "orig content" >file && + test_must_fail git difftool --dir-diff --no-symlinks --extcmd "$(pwd)/modify-both-files" branch && + echo "wt content" >expect && + test_cmp expect file && + echo "tmp content" >expect && + test_cmp expect "$(cat tmpdir)/file" ) ' diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index 97d6f4c7de..ebd5c5db45 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -101,7 +101,7 @@ test_expect_success $PREREQ \ test_expect_success $PREREQ 'Send patches with --envelope-sender' ' clean_fake_sendmail && - git send-email --envelope-sender="Patch Contributer <patch@example.com>" --suppress-cc=sob --from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors + git send-email --envelope-sender="Patch Contributor <patch@example.com>" --suppress-cc=sob --from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors ' test_expect_success $PREREQ 'setup expect' ' @@ -787,7 +787,7 @@ test_expect_success $PREREQ 'confirm detects EOF (auto causes failure)' ' test $ret = "0" ' -test_expect_success $PREREQ 'confirm doesnt loop forever' ' +test_expect_success $PREREQ 'confirm does not loop forever' ' CONFIRM=$(git config --get sendemail.confirm) && git config sendemail.confirm auto && GIT_SEND_EMAIL_NOTTY=1 && diff --git a/t/t9010-svn-fe.sh b/t/t9010-svn-fe.sh index b7eed2489f..6dafe7e99a 100755 --- a/t/t9010-svn-fe.sh +++ b/t/t9010-svn-fe.sh @@ -54,14 +54,6 @@ text_no_props () { >empty -test_expect_success 'setup: have pipes?' ' - rm -f frob && - if mkfifo frob - then - test_set_prereq PIPE - fi -' - test_expect_success PIPE 'empty dump' ' reinit_git && echo "SVN-fs-dump-format-version: 2" >input && diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index 2fcf269469..ac6f3b6af2 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -49,14 +49,6 @@ echo "$@"' >empty -test_expect_success 'setup: have pipes?' ' - rm -f frob && - if mkfifo frob - then - test_set_prereq PIPE - fi -' - ### ### series A ### diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh index 9502f2438a..043138631b 100755 --- a/t/t9400-git-cvsserver-server.sh +++ b/t/t9400-git-cvsserver-server.sh @@ -36,6 +36,7 @@ export CVSROOT CVS_SERVER rm -rf "$CVSWORK" "$SERVERDIR" test_expect_success 'setup' ' + git config push.default matching && echo >empty && git add empty && git commit -q -m "First Commit" && diff --git a/t/t9401-git-cvsserver-crlf.sh b/t/t9401-git-cvsserver-crlf.sh index 1c5bc84fa7..8c3db76301 100755 --- a/t/t9401-git-cvsserver-crlf.sh +++ b/t/t9401-git-cvsserver-crlf.sh @@ -84,6 +84,7 @@ export CVSROOT CVS_SERVER rm -rf "$CVSWORK" "$SERVERDIR" test_expect_success 'setup' ' + git config push.default matching && echo "Simple text file" >textfile.c && echo "File with embedded NUL: Q <- there" | q_to_nul > binfile.bin && mkdir subdir && diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh index 90bb6050c1..6783c14c1a 100755 --- a/t/t9500-gitweb-standalone-no-errors.sh +++ b/t/t9500-gitweb-standalone-no-errors.sh @@ -539,8 +539,7 @@ test_expect_success \ test_when_finished "GIT_COMMITTER_NAME=\"C O Mitter\"" && echo "ISO-8859-1" >> file && git add file && - git config i18n.commitencoding ISO-8859-1 && - test_when_finished "git config --unset i18n.commitencoding" && + test_config i18n.commitencoding ISO-8859-1 && git commit -F "$TEST_DIRECTORY"/t3900/ISO8859-1.txt && gitweb_run "p=.git;a=commit"' diff --git a/t/t9700/test.pl b/t/t9700/test.pl index 0d4e366232..1140767b50 100755 --- a/t/t9700/test.pl +++ b/t/t9700/test.pl @@ -45,7 +45,8 @@ is($r->get_color("color.test.slot1", "red"), $ansi_green, "get_color"); # Failure cases for config: # Save and restore STDERR; we will probably extract this into a # "dies_ok" method and possibly move the STDERR handling to Git.pm. -open our $tmpstderr, ">&STDERR" or die "cannot save STDERR"; close STDERR; +open our $tmpstderr, ">&STDERR" or die "cannot save STDERR"; +open STDERR, ">", "/dev/null" or die "cannot redirect STDERR to /dev/null"; is($r->config("test.dupstring"), "value2", "config: multivar"); eval { $r->config_bool("test.boolother") }; ok($@, "config_bool: non-boolean values fail"); diff --git a/t/t9808-git-p4-chdir.sh b/t/t9808-git-p4-chdir.sh index dc92e60cd6..11d2b5102c 100755 --- a/t/t9808-git-p4-chdir.sh +++ b/t/t9808-git-p4-chdir.sh @@ -42,6 +42,47 @@ test_expect_success 'P4CONFIG and relative dir clone' ' ) ' +# Common setup using .p4config to set P4CLIENT and P4PORT breaks +# if clone destination is relative. Make sure that chdir() expands +# the relative path in --dest to absolute. +test_expect_success 'p4 client root would be relative due to clone --dest' ' + test_when_finished cleanup_git && + ( + echo P4PORT=$P4PORT >git/.p4config && + P4CONFIG=.p4config && + export P4CONFIG && + unset P4PORT && + git p4 clone --dest="git" //depot + ) +' + +# When the p4 client Root is a symlink, make sure chdir() does not use +# getcwd() to convert it to a physical path. +test_expect_success SYMLINKS 'p4 client root symlink should stay symbolic' ' + physical="$TRASH_DIRECTORY/physical" && + symbolic="$TRASH_DIRECTORY/symbolic" && + test_when_finished "rm -rf \"$physical\"" && + test_when_finished "rm \"$symbolic\"" && + mkdir -p "$physical" && + ln -s "$physical" "$symbolic" && + test_when_finished cleanup_git && + ( + P4CLIENT=client-sym && + p4 client -i <<-EOF && + Client: $P4CLIENT + Description: $P4CLIENT + Root: $symbolic + LineEnd: unix + View: //depot/... //$P4CLIENT/... + EOF + git p4 clone --dest="$git" //depot && + cd "$git" && + test_commit file2 && + git config git-p4.skipSubmitEdit true && + git p4 submit + ) +' + test_expect_success 'kill p4d' ' kill_p4d ' diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index adc1372b3c..6d9d1418a0 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -69,6 +69,7 @@ run_completion () local -a COMPREPLY _words local _cword _words=( $1 ) + test "${1: -1}" == ' ' && _words+=('') (( _cword = ${#_words[@]} - 1 )) __git_wrap__git_main && print_comp } @@ -104,6 +105,23 @@ test_gitcomp () test_cmp expected out } +# Test __gitcomp_nl +# Arguments are: +# 1: current word (cur) +# -: the rest are passed to __gitcomp_nl +test_gitcomp_nl () +{ + local -a COMPREPLY && + sed -e 's/Z$//' >expected && + cur="$1" && + shift && + __gitcomp_nl "$@" && + print_comp && + test_cmp expected out +} + +invalid_variable_name='${foo.bar}' + test_expect_success '__gitcomp - trailing space - options' ' test_gitcomp "--re" "--dry-run --reuse-message= --reedit-message= --reset-author" <<-EOF @@ -147,8 +165,51 @@ test_expect_success '__gitcomp - suffix' ' EOF ' +test_expect_success '__gitcomp - doesnt fail because of invalid variable name' ' + __gitcomp "$invalid_variable_name" +' + +read -r -d "" refs <<-\EOF +maint +master +next +pu +EOF + +test_expect_success '__gitcomp_nl - trailing space' ' + test_gitcomp_nl "m" "$refs" <<-EOF + maint Z + master Z + EOF +' + +test_expect_success '__gitcomp_nl - prefix' ' + test_gitcomp_nl "--fixup=m" "$refs" "--fixup=" "m" <<-EOF + --fixup=maint Z + --fixup=master Z + EOF +' + +test_expect_success '__gitcomp_nl - suffix' ' + test_gitcomp_nl "branch.ma" "$refs" "branch." "ma" "." <<-\EOF + branch.maint.Z + branch.master.Z + EOF +' + +test_expect_success '__gitcomp_nl - no suffix' ' + test_gitcomp_nl "ma" "$refs" "" "ma" "" <<-\EOF + maintZ + masterZ + EOF +' + +test_expect_success '__gitcomp_nl - doesnt fail because of invalid variable name' ' + __gitcomp_nl "$invalid_variable_name" +' + test_expect_success 'basic' ' - run_completion "git \"\"" && + run_completion "git " && # built-in grep -q "^add \$" out && # script @@ -271,7 +332,7 @@ test_expect_success 'complete tree filename with spaces' ' EOF ' -test_expect_failure 'complete tree filename with metacharacters' ' +test_expect_success 'complete tree filename with metacharacters' ' echo content >"name with \${meta}" && git add . && git commit -m meta && diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh index 2101d914f2..e147a8d277 100755 --- a/t/t9903-bash-prompt.sh +++ b/t/t9903-bash-prompt.sh @@ -59,7 +59,7 @@ test_expect_success 'gitdir - .git directory in cwd' ' ' test_expect_success 'gitdir - .git directory in parent' ' - echo "$TRASH_DIRECTORY/.git" > expected && + echo "$(pwd -P)/.git" > expected && ( cd subdir/subsubdir && __gitdir > "$actual" @@ -77,7 +77,7 @@ test_expect_success 'gitdir - cwd is a .git directory' ' ' test_expect_success 'gitdir - parent is a .git directory' ' - echo "$TRASH_DIRECTORY/.git" > expected && + echo "$(pwd -P)/.git" > expected && ( cd .git/refs/heads && __gitdir > "$actual" @@ -115,7 +115,7 @@ test_expect_success 'gitdir - non-existing $GIT_DIR' ' ' test_expect_success 'gitdir - gitfile in cwd' ' - echo "$TRASH_DIRECTORY/otherrepo/.git" > expected && + echo "$(pwd -P)/otherrepo/.git" > expected && echo "gitdir: $TRASH_DIRECTORY/otherrepo/.git" > subdir/.git && test_when_finished "rm -f subdir/.git" && ( @@ -126,7 +126,7 @@ test_expect_success 'gitdir - gitfile in cwd' ' ' test_expect_success 'gitdir - gitfile in parent' ' - echo "$TRASH_DIRECTORY/otherrepo/.git" > expected && + echo "$(pwd -P)/otherrepo/.git" > expected && echo "gitdir: $TRASH_DIRECTORY/otherrepo/.git" > subdir/.git && test_when_finished "rm -f subdir/.git" && ( @@ -137,7 +137,7 @@ test_expect_success 'gitdir - gitfile in parent' ' ' test_expect_success SYMLINKS 'gitdir - resulting path avoids symlinks' ' - echo "$TRASH_DIRECTORY/otherrepo/.git" > expected && + echo "$(pwd -P)/otherrepo/.git" > expected && mkdir otherrepo/dir && test_when_finished "rm -rf otherrepo/dir" && ln -s otherrepo/dir link && diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index fa62d010f6..52510094ad 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -91,6 +91,10 @@ q_to_tab () { tr Q '\011' } +qz_to_tab_space () { + tr QZ '\011\040' +} + append_cr () { sed -e 's/$/Q/' | tr Q '\015' } @@ -135,12 +139,12 @@ test_pause () { fi } -# Call test_commit with the arguments "<message> [<file> [<contents>]]" +# Call test_commit with the arguments "<message> [<file> [<contents> [<tag>]]]" # # This will commit a file with the given contents and the given commit -# message. It will also add a tag with <message> as name. +# message, and tag the resulting commit with the given tag name. # -# Both <file> and <contents> default to <message>. +# <file>, <contents>, and <tag> all default to <message>. test_commit () { notick= && @@ -168,7 +172,7 @@ test_commit () { test_tick fi && git commit $signoff -m "$1" && - git tag "$1" + git tag "${4:-$1}" } # Call test_merge with the arguments "<message> <commit>", where <commit> @@ -536,6 +540,9 @@ test_must_fail () { elif test $exit_code = 127; then echo >&2 "test_must_fail: command not found: $*" return 1 + elif test $exit_code = 126; then + echo >&2 "test_must_fail: valgrind error: $*" + return 1 fi return 0 } diff --git a/t/test-lib.sh b/t/test-lib.sh index 1f510252ad..ca6bdef63d 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -193,7 +193,11 @@ do --no-color) color=; shift ;; --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind) - valgrind=t; verbose=t; shift ;; + valgrind=memcheck + shift ;; + --valgrind=*) + valgrind=$(expr "z$1" : 'z[^=]*=\(.*\)') + shift ;; --tee) shift ;; # was handled already --root=*) @@ -204,6 +208,8 @@ do esac done +test -n "$valgrind" && verbose=t + if test -n "$color" then say_color () { @@ -530,6 +536,8 @@ then PATH=$GIT_VALGRIND/bin:$PATH GIT_EXEC_PATH=$GIT_VALGRIND/bin export GIT_VALGRIND + GIT_VALGRIND_MODE="$valgrind" + export GIT_VALGRIND_MODE elif test -n "$GIT_TEST_INSTALLED" then GIT_EXEC_PATH=$($GIT_TEST_INSTALLED/git --exec-path) || @@ -592,14 +600,14 @@ then fi # Test repository -test="trash directory.$(basename "$0" .sh)" -test -n "$root" && test="$root/$test" -case "$test" in -/*) TRASH_DIRECTORY="$test" ;; - *) TRASH_DIRECTORY="$TEST_OUTPUT_DIRECTORY/$test" ;; +TRASH_DIRECTORY="trash directory.$(basename "$0" .sh)" +test -n "$root" && TRASH_DIRECTORY="$root/$TRASH_DIRECTORY" +case "$TRASH_DIRECTORY" in +/*) ;; # absolute path is good + *) TRASH_DIRECTORY="$TEST_OUTPUT_DIRECTORY/$TRASH_DIRECTORY" ;; esac test ! -z "$debug" || remove_trash=$TRASH_DIRECTORY -rm -fr "$test" || { +rm -fr "$TRASH_DIRECTORY" || { GIT_EXIT_OK=t echo >&5 "FATAL: Cannot prepare test area" exit 1 @@ -610,13 +618,13 @@ export HOME if test -z "$TEST_NO_CREATE_REPO" then - test_create_repo "$test" + test_create_repo "$TRASH_DIRECTORY" else - mkdir -p "$test" + mkdir -p "$TRASH_DIRECTORY" fi # Use -P to resolve symlinks in our working directory so that the cwd # in subprocesses like git equals our $PWD (for pathname comparisons). -cd -P "$test" || exit 1 +cd -P "$TRASH_DIRECTORY" || exit 1 this_test=${0##*/} this_test=${this_test%%-*} @@ -727,6 +735,11 @@ test_i18ngrep () { fi } +test_lazy_prereq PIPE ' + # test whether the filesystem supports FIFOs + rm -f testfifo && mkfifo testfifo +' + test_lazy_prereq SYMLINKS ' # test whether the filesystem supports symbolic links ln -s x y && test -h y diff --git a/t/valgrind/valgrind.sh b/t/valgrind/valgrind.sh index 582b4dca94..6b87c91b60 100755 --- a/t/valgrind/valgrind.sh +++ b/t/valgrind/valgrind.sh @@ -2,20 +2,27 @@ base=$(basename "$0") -TRACK_ORIGINS= +TOOL_OPTIONS='--leak-check=no' -VALGRIND_VERSION=$(valgrind --version) -VALGRIND_MAJOR=$(expr "$VALGRIND_VERSION" : '[^0-9]*\([0-9]*\)') -VALGRIND_MINOR=$(expr "$VALGRIND_VERSION" : '[^0-9]*[0-9]*\.\([0-9]*\)') -test 3 -gt "$VALGRIND_MAJOR" || -test 3 -eq "$VALGRIND_MAJOR" -a 4 -gt "$VALGRIND_MINOR" || -TRACK_ORIGINS=--track-origins=yes +case "$GIT_VALGRIND_MODE" in +memcheck-fast) + ;; +memcheck) + VALGRIND_VERSION=$(valgrind --version) + VALGRIND_MAJOR=$(expr "$VALGRIND_VERSION" : '[^0-9]*\([0-9]*\)') + VALGRIND_MINOR=$(expr "$VALGRIND_VERSION" : '[^0-9]*[0-9]*\.\([0-9]*\)') + test 3 -gt "$VALGRIND_MAJOR" || + test 3 -eq "$VALGRIND_MAJOR" -a 4 -gt "$VALGRIND_MINOR" || + TOOL_OPTIONS="$TOOL_OPTIONS --track-origins=yes" + ;; +*) + TOOL_OPTIONS="--tool=$GIT_VALGRIND_MODE" +esac exec valgrind -q --error-exitcode=126 \ - --leak-check=no \ - --suppressions="$GIT_VALGRIND/default.supp" \ --gen-suppressions=all \ - $TRACK_ORIGINS \ + --suppressions="$GIT_VALGRIND/default.supp" \ + $TOOL_OPTIONS \ --log-fd=4 \ --input-fd=4 \ $GIT_VALGRIND_OPTIONS \ diff --git a/test-svn-fe.c b/test-svn-fe.c index 0f2d9a4a3d..120ec96b0d 100644 --- a/test-svn-fe.c +++ b/test-svn-fe.c @@ -24,7 +24,7 @@ static int apply_delta(int argc, char *argv[]) die_errno("cannot open preimage"); if (buffer_init(&delta, argv[3])) die_errno("cannot open delta"); - if (svndiff0_apply(&delta, (off_t) strtoull(argv[4], NULL, 0), + if (svndiff0_apply(&delta, (off_t) strtoumax(argv[4], NULL, 0), &preimage_view, stdout)) return 1; if (buffer_deinit(&preimage)) diff --git a/transport-helper.c b/transport-helper.c index cb3ef7d38e..dcd8d97411 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -991,7 +991,7 @@ struct unidirectional_transfer { int src_is_sock; /* Is destination socket? */ int dest_is_sock; - /* Transfer state (TRANSFERING/FLUSHING/FINISHED) */ + /* Transfer state (TRANSFERRING/FLUSHING/FINISHED) */ int state; /* Buffer. */ char buf[BUFFERSIZE]; diff --git a/transport.c b/transport.c index e6f9346c76..ba5d8afb1b 100644 --- a/transport.c +++ b/transport.c @@ -508,7 +508,7 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus struct ref *refs; connect_setup(transport, for_push, 0); - get_remote_heads(data->fd[0], &refs, + get_remote_heads(data->fd[0], NULL, 0, &refs, for_push ? REF_NORMAL : 0, &data->extra_have); data->got_remote_heads = 1; @@ -519,11 +519,9 @@ static int fetch_refs_via_pack(struct transport *transport, int nr_heads, struct ref **to_fetch) { struct git_transport_data *data = transport->data; - struct string_list sought = STRING_LIST_INIT_DUP; const struct ref *refs; char *dest = xstrdup(transport->url); struct fetch_pack_args args; - int i; struct ref *refs_tmp = NULL; memset(&args, 0, sizeof(args)); @@ -537,18 +535,16 @@ static int fetch_refs_via_pack(struct transport *transport, args.no_progress = !transport->progress; args.depth = data->options.depth; - for (i = 0; i < nr_heads; i++) - string_list_append(&sought, to_fetch[i]->name); - if (!data->got_remote_heads) { connect_setup(transport, 0, 0); - get_remote_heads(data->fd[0], &refs_tmp, 0, NULL); + get_remote_heads(data->fd[0], NULL, 0, &refs_tmp, 0, NULL); data->got_remote_heads = 1; } refs = fetch_pack(&args, data->fd, data->conn, refs_tmp ? refs_tmp : transport->remote_refs, - dest, &sought, &transport->pack_lockfile); + dest, to_fetch, nr_heads, + &transport->pack_lockfile); close(data->fd[0]); close(data->fd[1]); if (finish_connect(data->conn)) @@ -558,7 +554,6 @@ static int fetch_refs_via_pack(struct transport *transport, free_refs(refs_tmp); - string_list_clear(&sought, 0); free(dest); return (refs ? 0 : -1); } @@ -800,7 +795,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re struct ref *tmp_refs; connect_setup(transport, 1, 0); - get_remote_heads(data->fd[0], &tmp_refs, REF_NORMAL, NULL); + get_remote_heads(data->fd[0], NULL, 0, &tmp_refs, REF_NORMAL, NULL); data->got_remote_heads = 1; } @@ -1133,6 +1128,8 @@ int transport_push(struct transport *transport, match_flags |= MATCH_REFS_MIRROR; if (flags & TRANSPORT_PUSH_PRUNE) match_flags |= MATCH_REFS_PRUNE; + if (flags & TRANSPORT_PUSH_FOLLOW_TAGS) + match_flags |= MATCH_REFS_FOLLOW_TAGS; if (match_push_refs(local_refs, &remote_refs, refspec_nr, refspec, match_flags)) { diff --git a/transport.h b/transport.h index a3450e97c0..fcb1d25d96 100644 --- a/transport.h +++ b/transport.h @@ -74,7 +74,7 @@ struct transport { const char *executable, int fd[2]); /** get_refs_list(), fetch(), and push_refs() can keep - * resources (such as a connection) reserved for futher + * resources (such as a connection) reserved for further * use. disconnect() releases these resources. **/ int (*disconnect)(struct transport *connection); @@ -105,6 +105,7 @@ struct transport { #define TRANSPORT_PUSH_PRUNE 128 #define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND 256 #define TRANSPORT_PUSH_NO_HOOK 512 +#define TRANSPORT_PUSH_FOLLOW_TAGS 1024 #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3) #define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x) diff --git a/upload-pack.c b/upload-pack.c index 948cfffe13..bfa6279cc4 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -26,6 +26,7 @@ static const char upload_pack_usage[] = "git upload-pack [--strict] [--timeout=< #define SHALLOW (1u << 16) #define NOT_SHALLOW (1u << 17) #define CLIENT_SHALLOW (1u << 18) +#define HIDDEN_REF (1u << 19) static unsigned long oldest_have; @@ -33,6 +34,7 @@ static int multi_ack; static int no_done; static int use_thin_pack, use_ofs_delta, use_include_tag; static int no_progress, daemon_mode; +static int allow_tip_sha1_in_want; static int shallow_nr; static struct object_array have_obj; static struct object_array want_obj; @@ -42,7 +44,6 @@ static unsigned int timeout; * otherwise maximum packet size (up to 65520 bytes). */ static int use_sideband; -static int debug_fd; static int advertise_refs; static int stateless_rpc; @@ -51,13 +52,6 @@ static void reset_timeout(void) alarm(timeout); } -static int strip(char *line, int len) -{ - if (len && line[len-1] == '\n') - line[--len] = 0; - return len; -} - static ssize_t send_client_data(int fd, const char *data, ssize_t sz) { if (use_sideband) @@ -70,7 +64,8 @@ static ssize_t send_client_data(int fd, const char *data, ssize_t sz) xwrite(fd, data, sz); return sz; } - return safe_write(fd, data, sz); + write_or_die(fd, data, sz); + return sz; } static FILE *pack_pipe = NULL; @@ -413,7 +408,6 @@ static int ok_to_give_up(void) static int get_common_commits(void) { - static char line[1000]; unsigned char sha1[20]; char last_hex[41]; int got_common = 0; @@ -423,10 +417,10 @@ static int get_common_commits(void) save_commit_buffer = 0; for (;;) { - int len = packet_read_line(0, line, sizeof(line)); + char *line = packet_read_line(0, NULL); reset_timeout(); - if (!len) { + if (!line) { if (multi_ack == 2 && got_common && !got_other && ok_to_give_up()) { sent_ready = 1; @@ -445,7 +439,6 @@ static int get_common_commits(void) got_other = 0; continue; } - strip(line, len); if (!prefixcmp(line, "have ")) { switch (got_sha1(line+5, sha1)) { case -1: /* they have what we do not */ @@ -485,6 +478,12 @@ static int get_common_commits(void) } } +static int is_our_ref(struct object *o) +{ + return o->flags & + ((allow_tip_sha1_in_want ? HIDDEN_REF : 0) | OUR_REF); +} + static void check_non_tip(void) { static const char *argv[] = { @@ -521,7 +520,7 @@ static void check_non_tip(void) o = get_indexed_object(--i); if (!o) continue; - if (!(o->flags & OUR_REF)) + if (!is_our_ref(o)) continue; memcpy(namebuf + 1, sha1_to_hex(o->sha1), 40); if (write_in_full(cmd.in, namebuf, 42) < 0) @@ -530,7 +529,7 @@ static void check_non_tip(void) namebuf[40] = '\n'; for (i = 0; i < want_obj.nr; i++) { o = want_obj.objects[i].item; - if (o->flags & OUR_REF) + if (is_our_ref(o)) continue; memcpy(namebuf, sha1_to_hex(o->sha1), 40); if (write_in_full(cmd.in, namebuf, 41) < 0) @@ -564,7 +563,7 @@ error: /* Pick one of them (we know there at least is one) */ for (i = 0; i < want_obj.nr; i++) { o = want_obj.objects[i].item; - if (!(o->flags & OUR_REF)) + if (!is_our_ref(o)) die("git upload-pack: not our ref %s", sha1_to_hex(o->sha1)); } @@ -573,36 +572,33 @@ error: static void receive_needs(void) { struct object_array shallows = OBJECT_ARRAY_INIT; - static char line[1000]; - int len, depth = 0; + int depth = 0; int has_non_tip = 0; shallow_nr = 0; - if (debug_fd) - write_str_in_full(debug_fd, "#S\n"); for (;;) { struct object *o; const char *features; unsigned char sha1_buf[20]; - len = packet_read_line(0, line, sizeof(line)); + char *line = packet_read_line(0, NULL); reset_timeout(); - if (!len) + if (!line) break; - if (debug_fd) - write_in_full(debug_fd, line, len); if (!prefixcmp(line, "shallow ")) { unsigned char sha1[20]; struct object *object; - if (get_sha1(line + 8, sha1)) + if (get_sha1_hex(line + 8, sha1)) die("invalid shallow line: %s", line); object = parse_object(sha1); if (!object) die("did not find object for %s", line); if (object->type != OBJ_COMMIT) die("invalid shallow object %s", sha1_to_hex(sha1)); - object->flags |= CLIENT_SHALLOW; - add_object_array(object, NULL, &shallows); + if (!(object->flags & CLIENT_SHALLOW)) { + object->flags |= CLIENT_SHALLOW; + add_object_array(object, NULL, &shallows); + } continue; } if (!prefixcmp(line, "deepen ")) { @@ -644,13 +640,11 @@ static void receive_needs(void) sha1_to_hex(sha1_buf)); if (!(o->flags & WANTED)) { o->flags |= WANTED; - if (!(o->flags & OUR_REF)) + if (!is_our_ref(o)) has_non_tip = 1; add_object_array(o, NULL, &want_obj); } } - if (debug_fd) - write_str_in_full(debug_fd, "#E\n"); /* * We have sent all our refs already, and the other end @@ -730,8 +724,10 @@ static int mark_our_ref(const char *refname, const unsigned char *sha1, int flag { struct object *o = lookup_unknown_object(sha1); - if (ref_is_hidden(refname)) + if (ref_is_hidden(refname)) { + o->flags |= HIDDEN_REF; return 1; + } if (!o) die("git upload-pack: cannot find object %s:", sha1_to_hex(sha1)); o->flags |= OUR_REF; @@ -750,9 +746,10 @@ static int send_ref(const char *refname, const unsigned char *sha1, int flag, vo return 0; if (capabilities) - packet_write(1, "%s %s%c%s%s agent=%s\n", + packet_write(1, "%s %s%c%s%s%s agent=%s\n", sha1_to_hex(sha1), refname_nons, 0, capabilities, + allow_tip_sha1_in_want ? " allow-tip-sha1-in-want" : "", stateless_rpc ? " no-done" : "", git_user_agent_sanitized()); else @@ -786,6 +783,8 @@ static void upload_pack(void) static int upload_pack_config(const char *var, const char *value, void *unused) { + if (!strcmp("uploadpack.allowtipsha1inwant", var)) + allow_tip_sha1_in_want = git_config_bool(var, value); return parse_hide_refs_config(var, value, "uploadpack"); } @@ -841,8 +840,6 @@ int main(int argc, char **argv) if (is_repository_shallow()) die("attempt to fetch/clone from a shallow repository"); git_config(upload_pack_config, NULL); - if (getenv("GIT_DEBUG_SEND_PACK")) - debug_fd = atoi(getenv("GIT_DEBUG_SEND_PACK")); upload_pack(); return 0; } @@ -6,8 +6,6 @@ #include "git-compat-util.h" #include "cache.h" -static int dying; - void vreportf(const char *prefix, const char *err, va_list params) { char msg[4096]; @@ -49,12 +47,19 @@ static void warn_builtin(const char *warn, va_list params) vreportf("warning: ", warn, params); } +static int die_is_recursing_builtin(void) +{ + static int dying; + return dying++; +} + /* If we are in a dlopen()ed .so write to a global variable would segfault * (ugh), so keep things static. */ static NORETURN_PTR void (*usage_routine)(const char *err, va_list params) = usage_builtin; static NORETURN_PTR void (*die_routine)(const char *err, va_list params) = die_builtin; static void (*error_routine)(const char *err, va_list params) = error_builtin; static void (*warn_routine)(const char *err, va_list params) = warn_builtin; +static int (*die_is_recursing)(void) = die_is_recursing_builtin; void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list params)) { @@ -66,6 +71,11 @@ void set_error_routine(void (*routine)(const char *err, va_list params)) error_routine = routine; } +void set_die_is_recursing_routine(int (*routine)(void)) +{ + die_is_recursing = routine; +} + void NORETURN usagef(const char *err, ...) { va_list params; @@ -84,11 +94,10 @@ void NORETURN die(const char *err, ...) { va_list params; - if (dying) { + if (die_is_recursing()) { fputs("fatal: recursion detected in die handler\n", stderr); exit(128); } - dying = 1; va_start(params, err); die_routine(err, params); @@ -102,12 +111,11 @@ void NORETURN die_errno(const char *fmt, ...) char str_error[256], *err; int i, j; - if (dying) { + if (die_is_recursing()) { fputs("fatal: recursion detected in die_errno handler\n", stderr); exit(128); } - dying = 1; err = strerror(errno); for (i = j = 0; err[i] && j < sizeof(str_error) - 1; ) { diff --git a/write_or_die.c b/write_or_die.c index 960f448cff..b50f99a936 100644 --- a/write_or_die.c +++ b/write_or_die.c @@ -1,5 +1,15 @@ #include "cache.h" +static void check_pipe(int err) +{ + if (err == EPIPE) { + signal(SIGPIPE, SIG_DFL); + raise(SIGPIPE); + /* Should never happen, but just in case... */ + exit(141); + } +} + /* * Some cases use stdio, but want to flush after the write * to get error handling (and to get better interactive @@ -34,8 +44,7 @@ void maybe_flush_or_die(FILE *f, const char *desc) return; } if (fflush(f)) { - if (errno == EPIPE) - exit(0); + check_pipe(errno); die_errno("write failure on '%s'", desc); } } @@ -50,8 +59,7 @@ void fsync_or_die(int fd, const char *msg) void write_or_die(int fd, const void *buf, size_t count) { if (write_in_full(fd, buf, count) < 0) { - if (errno == EPIPE) - exit(0); + check_pipe(errno); die_errno("write error"); } } @@ -59,8 +67,7 @@ void write_or_die(int fd, const void *buf, size_t count) int write_or_whine_pipe(int fd, const void *buf, size_t count, const char *msg) { if (write_in_full(fd, buf, count) < 0) { - if (errno == EPIPE) - exit(0); + check_pipe(errno); fprintf(stderr, "%s: write error (%s)\n", msg, strerror(errno)); return 0; diff --git a/wt-status.c b/wt-status.c index 54f4391f9c..ec5f27c599 100644 --- a/wt-status.c +++ b/wt-status.c @@ -965,13 +965,32 @@ static void show_cherry_pick_in_progress(struct wt_status *s, wt_status_print_trailer(s); } +static void show_revert_in_progress(struct wt_status *s, + struct wt_status_state *state, + const char *color) +{ + status_printf_ln(s, color, _("You are currently reverting commit %s."), + find_unique_abbrev(state->revert_head_sha1, DEFAULT_ABBREV)); + if (advice_status_hints) { + if (has_unmerged(s)) + status_printf_ln(s, color, + _(" (fix conflicts and run \"git revert --continue\")")); + else + status_printf_ln(s, color, + _(" (all conflicts fixed: run \"git revert --continue\")")); + status_printf_ln(s, color, + _(" (use \"git revert --abort\" to cancel the revert operation)")); + } + wt_status_print_trailer(s); +} + static void show_bisect_in_progress(struct wt_status *s, struct wt_status_state *state, const char *color) { if (state->branch) status_printf_ln(s, color, - _("You are currently bisecting branch '%s'."), + _("You are currently bisecting, started from branch '%s'."), state->branch); else status_printf_ln(s, color, @@ -985,96 +1004,172 @@ static void show_bisect_in_progress(struct wt_status *s, /* * Extract branch information from rebase/bisect */ -static void read_and_strip_branch(struct strbuf *sb, - const char **branch, - const char *path) +static char *read_and_strip_branch(const char *path) { + struct strbuf sb = STRBUF_INIT; unsigned char sha1[20]; - strbuf_reset(sb); - if (strbuf_read_file(sb, git_path("%s", path), 0) <= 0) - return; + if (strbuf_read_file(&sb, git_path("%s", path), 0) <= 0) + goto got_nothing; - while (sb->len && sb->buf[sb->len - 1] == '\n') - strbuf_setlen(sb, sb->len - 1); - if (!sb->len) - return; - if (!prefixcmp(sb->buf, "refs/heads/")) - *branch = sb->buf + strlen("refs/heads/"); - else if (!prefixcmp(sb->buf, "refs/")) - *branch = sb->buf; - else if (!get_sha1_hex(sb->buf, sha1)) { + while (&sb.len && sb.buf[sb.len - 1] == '\n') + strbuf_setlen(&sb, sb.len - 1); + if (!sb.len) + goto got_nothing; + if (!prefixcmp(sb.buf, "refs/heads/")) + strbuf_remove(&sb,0, strlen("refs/heads/")); + else if (!prefixcmp(sb.buf, "refs/")) + ; + else if (!get_sha1_hex(sb.buf, sha1)) { const char *abbrev; abbrev = find_unique_abbrev(sha1, DEFAULT_ABBREV); - strbuf_reset(sb); - strbuf_addstr(sb, abbrev); - *branch = sb->buf; - } else if (!strcmp(sb->buf, "detached HEAD")) /* rebase */ - ; + strbuf_reset(&sb); + strbuf_addstr(&sb, abbrev); + } else if (!strcmp(sb.buf, "detached HEAD")) /* rebase */ + goto got_nothing; else /* bisect */ - *branch = sb->buf; + ; + return strbuf_detach(&sb, NULL); + +got_nothing: + strbuf_release(&sb); + return NULL; } -static void wt_status_print_state(struct wt_status *s) +struct grab_1st_switch_cbdata { + int found; + struct strbuf buf; + unsigned char nsha1[20]; +}; + +static int grab_1st_switch(unsigned char *osha1, unsigned char *nsha1, + const char *email, unsigned long timestamp, int tz, + const char *message, void *cb_data) { - const char *state_color = color(WT_STATUS_HEADER, s); - struct strbuf branch = STRBUF_INIT; - struct strbuf onto = STRBUF_INIT; - struct wt_status_state state; - struct stat st; + struct grab_1st_switch_cbdata *cb = cb_data; + const char *target = NULL, *end; - memset(&state, 0, sizeof(state)); + if (prefixcmp(message, "checkout: moving from ")) + return 0; + message += strlen("checkout: moving from "); + target = strstr(message, " to "); + if (!target) + return 0; + target += strlen(" to "); + strbuf_reset(&cb->buf); + hashcpy(cb->nsha1, nsha1); + for (end = target; *end && *end != '\n'; end++) + ; + strbuf_add(&cb->buf, target, end - target); + cb->found = 1; + return 1; +} + +static void wt_status_get_detached_from(struct wt_status_state *state) +{ + struct grab_1st_switch_cbdata cb; + struct commit *commit; + unsigned char sha1[20]; + char *ref = NULL; + + strbuf_init(&cb.buf, 0); + if (for_each_reflog_ent_reverse("HEAD", grab_1st_switch, &cb) <= 0) { + strbuf_release(&cb.buf); + return; + } + + if (dwim_ref(cb.buf.buf, cb.buf.len, sha1, &ref) == 1 && + /* sha1 is a commit? match without further lookup */ + (!hashcmp(cb.nsha1, sha1) || + /* perhaps sha1 is a tag, try to dereference to a commit */ + ((commit = lookup_commit_reference_gently(sha1, 1)) != NULL && + !hashcmp(cb.nsha1, commit->object.sha1)))) { + int ofs; + if (!prefixcmp(ref, "refs/tags/")) + ofs = strlen("refs/tags/"); + else if (!prefixcmp(ref, "refs/remotes/")) + ofs = strlen("refs/remotes/"); + else + ofs = 0; + state->detached_from = xstrdup(ref + ofs); + } else + state->detached_from = + xstrdup(find_unique_abbrev(cb.nsha1, DEFAULT_ABBREV)); + hashcpy(state->detached_sha1, cb.nsha1); + + free(ref); + strbuf_release(&cb.buf); +} + +void wt_status_get_state(struct wt_status_state *state, + int get_detached_from) +{ + struct stat st; + unsigned char sha1[20]; if (!stat(git_path("MERGE_HEAD"), &st)) { - state.merge_in_progress = 1; + state->merge_in_progress = 1; } else if (!stat(git_path("rebase-apply"), &st)) { if (!stat(git_path("rebase-apply/applying"), &st)) { - state.am_in_progress = 1; + state->am_in_progress = 1; if (!stat(git_path("rebase-apply/patch"), &st) && !st.st_size) - state.am_empty_patch = 1; + state->am_empty_patch = 1; } else { - state.rebase_in_progress = 1; - read_and_strip_branch(&branch, &state.branch, - "rebase-apply/head-name"); - read_and_strip_branch(&onto, &state.onto, - "rebase-apply/onto"); + state->rebase_in_progress = 1; + state->branch = read_and_strip_branch("rebase-apply/head-name"); + state->onto = read_and_strip_branch("rebase-apply/onto"); } } else if (!stat(git_path("rebase-merge"), &st)) { if (!stat(git_path("rebase-merge/interactive"), &st)) - state.rebase_interactive_in_progress = 1; + state->rebase_interactive_in_progress = 1; else - state.rebase_in_progress = 1; - read_and_strip_branch(&branch, &state.branch, - "rebase-merge/head-name"); - read_and_strip_branch(&onto, &state.onto, - "rebase-merge/onto"); + state->rebase_in_progress = 1; + state->branch = read_and_strip_branch("rebase-merge/head-name"); + state->onto = read_and_strip_branch("rebase-merge/onto"); } else if (!stat(git_path("CHERRY_PICK_HEAD"), &st)) { - state.cherry_pick_in_progress = 1; + state->cherry_pick_in_progress = 1; } if (!stat(git_path("BISECT_LOG"), &st)) { - state.bisect_in_progress = 1; - read_and_strip_branch(&branch, &state.branch, - "BISECT_START"); + state->bisect_in_progress = 1; + state->branch = read_and_strip_branch("BISECT_START"); + } + if (!stat(git_path("REVERT_HEAD"), &st) && + !get_sha1("REVERT_HEAD", sha1)) { + state->revert_in_progress = 1; + hashcpy(state->revert_head_sha1, sha1); } - if (state.merge_in_progress) - show_merge_in_progress(s, &state, state_color); - else if (state.am_in_progress) - show_am_in_progress(s, &state, state_color); - else if (state.rebase_in_progress || state.rebase_interactive_in_progress) - show_rebase_in_progress(s, &state, state_color); - else if (state.cherry_pick_in_progress) - show_cherry_pick_in_progress(s, &state, state_color); - if (state.bisect_in_progress) - show_bisect_in_progress(s, &state, state_color); - strbuf_release(&branch); - strbuf_release(&onto); + if (get_detached_from) + wt_status_get_detached_from(state); +} + +static void wt_status_print_state(struct wt_status *s, + struct wt_status_state *state) +{ + const char *state_color = color(WT_STATUS_HEADER, s); + if (state->merge_in_progress) + show_merge_in_progress(s, state, state_color); + else if (state->am_in_progress) + show_am_in_progress(s, state, state_color); + else if (state->rebase_in_progress || state->rebase_interactive_in_progress) + show_rebase_in_progress(s, state, state_color); + else if (state->cherry_pick_in_progress) + show_cherry_pick_in_progress(s, state, state_color); + else if (state->revert_in_progress) + show_revert_in_progress(s, state, state_color); + if (state->bisect_in_progress) + show_bisect_in_progress(s, state, state_color); } void wt_status_print(struct wt_status *s) { const char *branch_color = color(WT_STATUS_ONBRANCH, s); const char *branch_status_color = color(WT_STATUS_HEADER, s); + struct wt_status_state state; + + memset(&state, 0, sizeof(state)); + wt_status_get_state(&state, + s->branch && !strcmp(s->branch, "HEAD")); if (s->branch) { const char *on_what = _("On branch "); @@ -1082,9 +1177,19 @@ void wt_status_print(struct wt_status *s) if (!prefixcmp(branch_name, "refs/heads/")) branch_name += 11; else if (!strcmp(branch_name, "HEAD")) { - branch_name = ""; branch_status_color = color(WT_STATUS_NOBRANCH, s); - on_what = _("Not currently on any branch."); + if (state.detached_from) { + unsigned char sha1[20]; + branch_name = state.detached_from; + if (!get_sha1("HEAD", sha1) && + !hashcmp(sha1, state.detached_sha1)) + on_what = _("HEAD detached at "); + else + on_what = _("HEAD detached from "); + } else { + branch_name = ""; + on_what = _("Not currently on any branch."); + } } status_printf(s, color(WT_STATUS_HEADER, s), ""); status_printf_more(s, branch_status_color, "%s", on_what); @@ -1093,7 +1198,11 @@ void wt_status_print(struct wt_status *s) wt_status_print_tracking(s); } - wt_status_print_state(s); + wt_status_print_state(s, &state); + free(state.branch); + free(state.onto); + free(state.detached_from); + if (s->is_initial) { status_printf_ln(s, color(WT_STATUS_HEADER, s), ""); status_printf_ln(s, color(WT_STATUS_HEADER, s), _("Initial commit")); @@ -1116,14 +1225,10 @@ void wt_status_print(struct wt_status *s) if (advice_status_u_option && 2000 < s->untracked_in_ms) { status_printf_ln(s, GIT_COLOR_NORMAL, ""); status_printf_ln(s, GIT_COLOR_NORMAL, - _("It took %.2f seconds to enumerate untracked files." - " 'status -uno'"), - s->untracked_in_ms / 1000.0); - status_printf_ln(s, GIT_COLOR_NORMAL, - _("may speed it up, but you have to be careful not" - " to forget to add")); - status_printf_ln(s, GIT_COLOR_NORMAL, - _("new files yourself (see 'git help status').")); + _("It took %.2f seconds to enumerate untracked files. 'status -uno'\n" + "may speed it up, but you have to be careful not to forget to add\n" + "new files yourself (see 'git help status')."), + s->untracked_in_ms / 1000.0); } } else if (s->commitable) status_printf_ln(s, GIT_COLOR_NORMAL, _("Untracked files not listed%s"), diff --git a/wt-status.h b/wt-status.h index 74208c06fd..4121bc208d 100644 --- a/wt-status.h +++ b/wt-status.h @@ -80,13 +80,18 @@ struct wt_status_state { int rebase_interactive_in_progress; int cherry_pick_in_progress; int bisect_in_progress; - const char *branch; - const char *onto; + int revert_in_progress; + char *branch; + char *onto; + char *detached_from; + unsigned char detached_sha1[20]; + unsigned char revert_head_sha1[20]; }; void wt_status_prepare(struct wt_status *s); void wt_status_print(struct wt_status *s); void wt_status_collect(struct wt_status *s); +void wt_status_get_state(struct wt_status_state *state, int get_detached_from); void wt_shortstatus_print(struct wt_status *s); void wt_porcelain_print(struct wt_status *s); diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c index 1b7012a119..b2eb6db2c8 100644 --- a/xdiff/xdiffi.c +++ b/xdiff/xdiffi.c @@ -490,7 +490,7 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { /* * Try to move back the possibly merged group of changes, to match - * the recorded postion in the other file. + * the recorded position in the other file. */ while (ixref < ix) { rchg[--ixs] = 1; diff --git a/xdiff/xhistogram.c b/xdiff/xhistogram.c index bf99787c3e..73210cb6f3 100644 --- a/xdiff/xhistogram.c +++ b/xdiff/xhistogram.c @@ -55,7 +55,7 @@ struct histindex { struct record { unsigned int ptr, cnt; struct record *next; - } **records, /* an ocurrence */ + } **records, /* an occurrence */ **line_map; /* map of line to record chain */ chastore_t rcha; unsigned int *next_ptrs; @@ -168,13 +168,8 @@ void git_deflate_init(git_zstream *strm, int level) strm->z.msg ? strm->z.msg : "no message"); } -void git_deflate_init_gzip(git_zstream *strm, int level) +static void do_git_deflate_init(git_zstream *strm, int level, int windowBits) { - /* - * Use default 15 bits, +16 is to generate gzip header/trailer - * instead of the zlib wrapper. - */ - const int windowBits = 15 + 16; int status; zlib_pre_call(strm); @@ -188,6 +183,24 @@ void git_deflate_init_gzip(git_zstream *strm, int level) strm->z.msg ? strm->z.msg : "no message"); } +void git_deflate_init_gzip(git_zstream *strm, int level) +{ + /* + * Use default 15 bits, +16 is to generate gzip header/trailer + * instead of the zlib wrapper. + */ + return do_git_deflate_init(strm, level, 15 + 16); +} + +void git_deflate_init_raw(git_zstream *strm, int level) +{ + /* + * Use default 15 bits, negate the value to get raw compressed + * data without zlib header and trailer. + */ + return do_git_deflate_init(strm, level, -15); +} + int git_deflate_abort(git_zstream *strm) { int status; |