diff options
201 files changed, 4714 insertions, 1781 deletions
diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines index 0f8cccf52d..7636199fe8 100644 --- a/Documentation/CodingGuidelines +++ b/Documentation/CodingGuidelines @@ -328,9 +328,14 @@ For C programs: - When you come up with an API, document it. - - The first #include in C files, except in platform specific - compat/ implementations, should be git-compat-util.h or another - header file that includes it, such as cache.h or builtin.h. + - The first #include in C files, except in platform specific compat/ + implementations, must be either "git-compat-util.h", "cache.h" or + "builtin.h". You do not have to include more than one of these. + + - A C file must directly include the header files that declare the + functions and the types it uses, except for the functions and types + that are made available to it by including one of the header files + it must include by the previous rule. - If you are planning a new command, consider writing it in shell or perl first, so that changes in semantics can be easily @@ -464,6 +469,10 @@ Writing Documentation: --sort=<key> --abbrev[=<n>] + If a placeholder has multiple words, they are separated by dashes: + <new-branch-name> + --template=<template-directory> + Possibility of multiple occurrences is indicated by three dots: <file>... (One or more of <file>.) @@ -480,12 +489,12 @@ Writing Documentation: (Zero or more of <patch>. Note that the dots are inside, not outside the brackets.) - Multiple alternatives are indicated with vertical bar: + Multiple alternatives are indicated with vertical bars: [-q | --quiet] [--utf8 | --no-utf8] Parentheses are used for grouping: - [(<rev>|<range>)...] + [(<rev> | <range>)...] (Any number of either <rev> or <range>. Parens are needed to make it clear that "..." pertains to both <rev> and <range>.) diff --git a/Documentation/RelNotes/2.3.3.txt b/Documentation/RelNotes/2.3.3.txt new file mode 100644 index 0000000000..5ef12644c2 --- /dev/null +++ b/Documentation/RelNotes/2.3.3.txt @@ -0,0 +1,39 @@ +Git v2.3.3 Release Notes +======================== + +Fixes since v2.3.2 +------------------ + + * A corrupt input to "git diff -M" used cause us to segfault. + + * The borrowed code in kwset API did not follow our usual convention + to use "unsigned char" to store values that range from 0-255. + + * Description given by "grep -h" for its --exclude-standard option + was phrased poorly. + + * Documentaton for "git remote add" mentioned "--tags" and + "--no-tags" and it was not clear that fetch from the remote in + the future will use the default behaviour when neither is given + to override it. + + * "git diff --shortstat --dirstat=changes" showed a dirstat based on + lines that was never asked by the end user in addition to the + dirstat that the user asked for. + + * The interaction between "git submodule update" and the + submodule.*.update configuration was not clearly documented. + + * "git apply" was not very careful about reading from, removing, + updating and creating paths outside the working tree (under + --index/--cached) or the current directory (when used as a + replacement for GNU patch). + + * "git daemon" looked up the hostname even when "%CH" and "%IP" + interpolations are not requested, which was unnecessary. + + * The "interpolated-path" option of "git daemon" inserted any string + client declared on the "host=" capability request without checking. + Sanitize and limit %H and %CH to a saner and a valid DNS name. + +Also contains typofixes, documentation updates and trivial code clean-ups. diff --git a/Documentation/RelNotes/2.4.0.txt b/Documentation/RelNotes/2.4.0.txt new file mode 100644 index 0000000000..072309eaa6 --- /dev/null +++ b/Documentation/RelNotes/2.4.0.txt @@ -0,0 +1,319 @@ +Git 2.4 Release Notes +===================== + +Updates since v2.3 +------------------ + +Ports + + * Our default I/O size (8 MiB) for large files was too large for some + platforms with smaller SSIZE_MAX, leading to read(2)/write(2) + failures. + + * We did not check the curl library version before using + CURLOPT_PROXYAUTH feature that may not exist. + + +UI, Workflows & Features + + * The command usage info strings given by "git cmd -h" and in + documentation have been tweaked for consistency. + + * The "sync" subcommand of "git p4" now allows users to exclude + subdirectories like its "clone" subcommand does. + + * "git log --invert-grep --grep=WIP" will show only commits that do + not have the string "WIP" in their messages. + + * "git push" has been taught a "--atomic" option that makes push to + update more than one ref an "all-or-none" affair. + + * Extending the "push to deploy" added in 2.3, the behaviour of "git + push" when updating the branch that is checked out can now be + tweaked by push-to-checkout hook. + + * Using environment variable LANGUAGE and friends on the client side, + HTTP-based transports now send Accept-Language when making requests. + + * "git send-email" used to accept a mistaken "y" (or "yes") as an + answer to "What encoding do you want to use [UTF-8]? " without + questioning. Now it asks for confirmation when the answer looks + too short to be a valid encoding name. + + * When "git apply --whitespace=fix" fixed whitespace errors in the + common context lines, the command reports that it did so. + + * "git status" now allows the "-v" to be given twice to show the + differences that are left in the working tree not to be committed. + + * "git cherry-pick" used to clean-up the log message even when it is + merely replaying an existing commit. It now replays the message + verbatim unless you are editing the message of resulting commits. + + * "git archive" can now be told to set the 'text' attribute in the + resulting zip archive. + + +Performance, Internal Implementation, Development Support etc. + + * Implementation of N_() macro has been updated slightly to help us + detect mistakes. + + * Implementation of "reflog expire" has been restructured to fit the + reflogs better with the recently updated ref API. + + * The transport-helper did not give transport options such as + verbosity, progress, cloning, etc. to import and export based + helpers, like it did for fetch and push based helpers, robbing them + the chance to honor the wish of the end-users better. + + * The tests that wanted to see that file becomes unreadable after + running "chmod a-r file", and the tests that wanted to make sure it + is not run as root, we used "can we write into the / directory?" as + a cheap substitute, but on some platforms that is not a good + heuristics. The tests and their prerequisites have been updated to + check what they really require. + (merge f400e51 jk/sanity later to maint). + + * Various issues around "reflog expire", e.g. using --updateref when + expiring a reflog for a symbolic reference, have been corrected + and/or made saner. + + * The strbuf API was explained between the API documentation and in + the header file. Move missing bits to strbuf.h so that programmers + can check only one place for all necessary information. + + * The error handling functions and conventions are now documented in + the API manual. + + * Optimize attribute look-up, mostly useful in "git grep" on a + project that does not use many attributes, by avoiding it when we + (should) know that the attributes are not defined in the first + place. + + * Typofix in comments. + (merge ef2956a ak/git-pm-typofix later to maint). + + * Code clean-up. + (merge 0b868f0 sb/hex-object-name-is-at-most-41-bytes-long later to maint). + (merge 5d30851 dp/remove-duplicated-header-inclusion later to maint). + + * Simplify the ref transaction API around how "the ref should be + pointing at this object" is specified. + + +Also contains various documentation updates and code clean-ups. + + +Fixes since v2.3 +---------------- + +Unless otherwise noted, all the fixes since v2.3 in the maintenance +track are contained in this release (see the maintenance releases' +notes for details). + + * "git blame HEAD -- missing" failed to correctly say "HEAD" when it + tried to say "No such path 'missing' in HEAD". + (merge a46442f jk/blame-commit-label later to maint). + + * "git rerere" (invoked internally from many mergy operations) did + not correctly signal errors when told to update the working tree + files and failed to do so for whatever reason. + (merge 89ea903 jn/rerere-fail-on-auto-update-failure later to maint). + + * Setting diff.submodule to 'log' made "git format-patch" produce + broken patches. + (merge 339de50 dk/format-patch-ignore-diff-submodule later to maint). + + * After attempting and failing a password-less authentication + (e.g. kerberos), libcURL refuses to fall back to password based + Basic authentication without a bit of help/encouragement. + (merge 4dbe664 bc/http-fallback-to-password-after-krb-fails later to maint). + + * The "git push" documentation made the "--repo=<there>" option + easily misunderstood. + (merge 57b92a7 mg/push-repo-option-doc later to maint). + + * Code to read branch name from various files in .git/ directory + would have misbehaved if the code to write them left an empty file. + (merge 66ec904 jk/status-read-branch-name-fix later to maint). + + * A misspelled conditional that is always true has been fixed. + (merge 94ee8e2 jk/remote-curl-an-array-in-struct-cannot-be-null later to maint). + + * The documentation incorrectly said that C(opy) and R(ename) are the + only ones that can be followed by the score number in the output in + the --raw format. + (merge ac1c2d9 jc/diff-format-doc later to maint). + + * A broken pack .idx file in the receiving repository prevented the + dumb http transport from fetching a good copy of it from the other + side. + (merge 8b9c2dd jk/dumb-http-idx-fetch-fix later to maint). + + * The error message from "git commit", when a non-existing author + name was given as value to the "--author=" parameter, has been + reworded to avoid misunderstanding. + (merge 1044b1f mg/commit-author-no-match-malformed-message later to maint). + + * "git log --help" used to show rev-list options that are irrelevant + to the "log" command. + (merge 3cab02d jc/doc-log-rev-list-options later to maint). + + * "git apply --whitespace=fix" used to under-allocate the memory when + the fix resulted in a longer text than the original patch. + (merge 407a792 jc/apply-ws-fix-expands later to maint). + + * The interactive "show a list and let the user choose from it" + interface "add -i" used showed and prompted to the user even when + the candidate list was empty, against which the only "choice" the + user could have made was to choose nothing. + (merge a9c4641 ak/add-i-empty-candidates later to maint). + + * The insn sheet "git rebase -i" creates did not fully honor + core.abbrev settings. + (merge edb72d5 ks/rebase-i-abbrev later to maint). + + * "git fetch" over a remote-helper that cannot respond to "list" + command could not fetch from a symbolic reference e.g. HEAD. + (merge 33cae54 mh/deref-symref-over-helper-transport later to maint). + + * "git push --signed" gave an incorrectly worded error message when + the other side did not support the capability. + (merge 45917f0 jc/push-cert later to maint). + + * We didn't format an integer that wouldn't fit in "int" but in + "uintmax_t" correctly. + (merge d306f3d jk/decimal-width-for-uintmax later to maint). + + * Reading configuration from a blob object, when it ends with a lone + CR, use to confuse the configuration parser. + (merge 1d0655c jk/config-no-ungetc-eof later to maint). + + * The pack bitmap support did not build with older versions of GCC. + (merge bd4e882 jk/pack-bitmap later to maint). + + * The documentation wasn't clear that "remote.<nick>.pushURL" and + "remote.<nick>.URL" are there to name the same repository accessed + via different transports, not two separate repositories. + (merge 697f652 jc/remote-set-url-doc later to maint). + + * Older GnuPG implementations may not correctly import the keyring + material we prepare for the tests to use. + (merge 1f985d6 ch/new-gpg-drops-rfc-1991 later to maint). + + * The credential helper for Windows (in contrib/) used to mishandle + a user name with an at-sign in it. + (merge 13d261e av/wincred-with-at-in-username-fix later to maint). + + * Longstanding configuration variable naming rules has been added to + the documentation. + (merge 35840a3 jc/conf-var-doc later to maint). + + * An earlier workaround to squelch unhelpful deprecation warnings + from the complier on Mac OSX unnecessarily set minimum required + version of the OS, which the user might want to raise (or lower) + for other reasons. + (merge 88c03eb es/squelch-openssl-warnings-on-macosx later to maint). + + * Certain older vintages of cURL give irregular output from + "curl-config --vernum", which confused our build system. + (merge 3af6792 tc/curl-vernum-output-broken-in-7.11 later to maint). + + * In v2.2.0, we broke "git prune" that runs in a repository that + borrows from an alternate object store. + (merge b0a4264 jk/prune-mtime later to maint). + + * "git submodule add" failed to squash "path/to/././submodule" to + "path/to/submodule". + (merge 8196e72 ps/submodule-sanitize-path-upon-add later to maint). + + * "git merge-file" did not work correctly in a subdirectory. + (merge 204a8ff ab/merge-file-prefix later to maint). + + * "git blame" died, trying to free an uninitialized piece of memory. + (merge e600592 es/blame-commit-info-fix later to maint). + + * "git fast-import" used to crash when it could not close and + conclude the resulting packfile cleanly. + (merge 5e915f3 jk/fast-import-die-nicely-fix later to maint). + + * "update-index --refresh" used to leak when an entry cannot be + refreshed for whatever reason. + (merge bc1c2ca sb/plug-leak-in-make-cache-entry later to maint). + + * The "interpolated-path" option of "git daemon" inserted any string + client declared on the "host=" capability request without checking. + Sanitize and limit %H and %CH to a saner and a valid DNS name. + (merge b485373 jk/daemon-interpolate later to maint). + + * "git daemon" looked up the hostname even when "%CH" and "%IP" + interpolations are not requested, which was unnecessary. + (merge dc8edc8 rs/daemon-interpolate later to maint). + + * Even though we officially haven't dropped Perl 5.8 support, the + Getopt::Long package that came with it does not support "--no-" + prefix to negate a boolean option; manually add support to help + people with older Getopt::Long package. + (merge f471494 km/send-email-getopt-long-workarounds later to maint). + + * "git apply" was not very careful about reading from, removing, + updating and creating paths outside the working tree (under + --index/--cached) or the current directory (when used as a + replacement for GNU patch). + (merge e0d201b jc/apply-beyond-symlink later to maint). + + * A breakage to git-svn around v2.2 era that triggers premature + closing of FileHandle has been corrected. + (merge e426311 ew/svn-maint-fixes later to maint). + + * We did not parse username followed by literal IPv6 address in SSH + transport URLs, e.g. ssh://user@[2001:db8::1]:22/repo.git + correctly. + (merge 3f55cca tb/connect-ipv6-parse-fix later to maint). + + * The configuration variable 'mailinfo.scissors' was hard to + discover in the documentation. + (merge afb5de7 mm/am-c-doc later to maint). + + * The interaction between "git submodule update" and the + submodule.*.update configuration was not clearly documented. + (merge 5c31acf ms/submodule-update-config-doc later to maint). + + * "git diff --shortstat --dirstat=changes" showed a dirstat based on + lines that was never asked by the end user in addition to the + dirstat that the user asked for. + (merge ab27389 mk/diff-shortstat-dirstat-fix later to maint). + + * "git remote add" mentioned "--tags" and "--no-tags" and was not + clear that fetch from the remote in the future will use the default + behaviour when neither is given to override it. + (merge aaba0ab mg/doc-remote-tags-or-not later to maint). + + * Description given by "grep -h" for its --exclude-standard option + was phrased poorly. + (merge 77fdb8a nd/grep-exclude-standard-help-fix later to maint). + + * "git rebase -i" recently started to include the number of + commits in the insn sheet to be processed, but on a platform + that prepends leading whitespaces to "wc -l" output, the numbers + are shown with extra whitespaces that aren't necessary. + (merge 2185d3b es/rebase-i-count-todo later to maint). + + * The borrowed code in kwset API did not follow our usual convention + to use "unsigned char" to store values that range from 0-255. + (merge 189c860 bw/kwset-use-unsigned later to maint). + + * A corrupt input to "git diff -M" used to cause it to segfault. + (merge 4d6be03 jk/diffcore-rename-duplicate later to maint). + + * Code cleanups and documentaiton updates. + (merge 2ce63e9 rs/simple-cleanups later to maint). + (merge 33baa69 rj/no-xopen-source-for-cygwin later to maint). + (merge 817d03e jc/diff-test-updates later to maint). + (merge eb32c66 ak/t5516-typofix later to maint). + (merge bcd57cb mr/doc-clean-f-f later to maint). + (merge 0d6accc mg/doc-status-color-slot later to maint). + (merge 53e53c7 sg/completion-remote later to maint). + (merge 8fa7975 ak/git-done-help-cleanup later to maint). + (merge 9a6f128 rs/deflate-init-cleanup later to maint). diff --git a/Documentation/config.txt b/Documentation/config.txt index e18f712a13..5666d27b4a 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -154,9 +154,8 @@ color:: `white`; the attributes are `bold`, `dim`, `ul`, `blink` and `reverse`. The first color given is the foreground; the second is the background. The position of the attribute, if - any, doesn't matter. Attributes may be turned off - specifically by prefixing them with `no` (e.g., `noreverse`, - `noul`, etc). + any, doesn't matter. Attributes may be turned off specifically + by prefixing them with `no` (e.g., `noreverse`, `noul`, etc). + Colors (foreground and background) may also be given as numbers between 0 and 255; these use ANSI 256-color mode (but note that not all @@ -983,9 +982,10 @@ color.status.<slot>:: `added` or `updated` (files which are added but not committed), `changed` (files which are changed but not added in the index), `untracked` (files which are not tracked by Git), - `branch` (the current branch), or + `branch` (the current branch), `nobranch` (the color the 'no branch' warning is shown in, defaulting - to red). + to red), or + `unmerged` (files which have unmerged changes). color.ui:: This variable determines the default value for variables such @@ -2126,6 +2126,11 @@ rebase.autostash:: successful rebase might result in non-trivial conflicts. Defaults to false. +receive.advertiseatomic:: + By default, git-receive-pack will advertise the atomic push + capability to its clients. If you don't want to this capability + to be advertised, set this variable to false. + receive.autogc:: By default, git-receive-pack will run "git-gc --auto" after receiving data from git-push and updating refs. You can stop @@ -2185,11 +2190,15 @@ receive.denyCurrentBranch:: message. Defaults to "refuse". + Another option is "updateInstead" which will update the working -directory (must be clean) if pushing into the current branch. This option is +tree if pushing into the current branch. This option is intended for synchronizing working directories when one side is not easily accessible via interactive ssh (e.g. a live web site, hence the requirement that the working directory be clean). This mode also comes in handy when developing inside a VM to test and fix code on different Operating Systems. ++ +By default, "updateInstead" will refuse the push if the working tree or +the index have any difference from the HEAD, but the `push-to-checkout` +hook can be used to customize this. See linkgit:githooks[5]. receive.denyNonFastForwards:: If set to true, git-receive-pack will deny a ref update which is @@ -2434,12 +2443,16 @@ status.submodulesummary:: submodule.<name>.path:: submodule.<name>.url:: + The path within this project and URL for a submodule. These + variables are initially populated by 'git submodule init'. See + linkgit:git-submodule[1] and linkgit:gitmodules[5] for + details. + submodule.<name>.update:: - The path within this project, URL, and the updating strategy - for a submodule. These variables are initially populated - by 'git submodule init'; edit them to override the - URL and other values found in the `.gitmodules` file. See - linkgit:git-submodule[1] and linkgit:gitmodules[5] for details. + The default update procedure for a submodule. This variable + is populated by `git submodule init` from the + linkgit:gitmodules[5] file. See description of 'update' + command in linkgit:git-submodule[1]. submodule.<name>.branch:: The remote branch name for a submodule, used by `git submodule @@ -2571,6 +2584,18 @@ user.signingkey:: This option is passed unchanged to gpg's --local-user parameter, so you may specify a key using any method that gpg supports. +versionsort.prereleaseSuffix:: + When version sort is used in linkgit:git-tag[1], prerelease + tags (e.g. "1.0-rc1") may appear after the main release + "1.0". By specifying the suffix "-rc" in this variable, + "1.0-rc1" will appear before "1.0". ++ +This variable can be specified multiple times, once per suffix. The +order of suffixes in the config file determines the sorting order +(e.g. if "-pre" appears before "-rc" in the config file then 1.0-preXX +is sorted before 1.0-rcXX). The sorting order between different +suffixes is undefined if they are in multiple config files. + web.browser:: Specify a web browser that may be used by some commands. Currently only linkgit:git-instaweb[1] and linkgit:git-help[1] diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index f605327946..9489664cfa 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -16,7 +16,7 @@ SYNOPSIS [--ignore-space-change | --ignore-whitespace ] [--whitespace=(nowarn|warn|fix|error|error-all)] [--exclude=<path>] [--include=<path>] [--directory=<root>] - [--verbose] [<patch>...] + [--verbose] [--unsafe-paths] [<patch>...] DESCRIPTION ----------- @@ -229,6 +229,16 @@ For example, a patch that talks about updating `a/git-gui.sh` to `b/git-gui.sh` can be applied to the file in the working tree `modules/git-gui/git-gui.sh` by running `git apply --directory=modules/git-gui`. +--unsafe-paths:: + By default, a patch that affects outside the working area + (either a Git controlled working tree, or the current working + directory when "git apply" is used as a replacement of GNU + patch) is rejected as a mistake (or a mischief). ++ +When `git apply` is used as a "better GNU patch", the user can pass +the `--unsafe-paths` option to override this safety check. This option +has no effect when `--index` or `--cached` is in use. + Configuration ------------- diff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt index 94b6d19cf2..641681f61a 100644 --- a/Documentation/git-clean.txt +++ b/Documentation/git-clean.txt @@ -34,8 +34,12 @@ OPTIONS -f:: --force:: If the Git configuration variable clean.requireForce is not set - to false, 'git clean' will refuse to run unless given -f, -n or - -i. + to false, 'git clean' will refuse to delete files or directories + unless given -f, -n or -i. Git will refuse to delete directories + with .git sub directory or file unless a second -f + is given. This affects also git submodules where the storage area + of the removed submodule under .git/modules/ is not removed until + -f is given twice. -i:: --interactive:: diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 1e74b75d38..f14d2ececd 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -284,6 +284,10 @@ configuration variable documented in linkgit:git-config[1]. would be committed at the bottom of the commit message template. Note that this diff output doesn't have its lines prefixed with '#'. ++ +If specified twice, show in addition the unified diff between +what would be committed and the worktree files, i.e. the unstaged +changes to tracked files. -q:: --quiet:: diff --git a/Documentation/git-imap-send.txt b/Documentation/git-imap-send.txt index 77aacf1309..5d1e4c80cd 100644 --- a/Documentation/git-imap-send.txt +++ b/Documentation/git-imap-send.txt @@ -44,7 +44,8 @@ OPTIONS --no-curl:: Talk to the IMAP server using git's own IMAP routines instead of - using libcurl. + using libcurl. Ignored if Git was built with the NO_OPENSSL option + set. CONFIGURATION diff --git a/Documentation/git-p4.txt b/Documentation/git-p4.txt index 6ab5f9497a..a1664b9f68 100644 --- a/Documentation/git-p4.txt +++ b/Documentation/git-p4.txt @@ -241,6 +241,9 @@ Git repository: Use a client spec to find the list of interesting files in p4. See the "CLIENT SPEC" section below. +-/ <path>:: + Exclude selected depot paths when cloning or syncing. + Clone options ~~~~~~~~~~~~~ These options can be used in an initial 'clone', along with the 'sync' @@ -254,9 +257,6 @@ options described above. --bare:: Perform a bare clone. See linkgit:git-clone[1]. --/ <path>:: - Exclude selected depot paths when cloning. - Submit options ~~~~~~~~~~~~~~ These options can be used to modify 'git p4 submit' behavior. diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 58cc59f20c..e1a46a7958 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] [--follow-tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>] +'git push' [--all | --mirror | --tags] [--follow-tags] [--atomic] [-n | --dry-run] [--receive-pack=<git-receive-pack>] [--repo=<repository>] [-f | --force] [--prune] [-v | --verbose] [-u | --set-upstream] [--signed] [--force-with-lease[=<refname>[:<expect>]]] @@ -136,6 +136,11 @@ already exists on the remote side. logged. See linkgit:git-receive-pack[1] for the details on the receiving end. +--[no-]atomic:: + Use an atomic transaction on the remote side if available. + Either all refs are updated, or on error, no refs are updated. + If the server does not support atomic pushes the push will fail. + --receive-pack=<git-receive-pack>:: --exec=<git-receive-pack>:: Path to the 'git-receive-pack' program on the remote diff --git a/Documentation/git-reflog.txt b/Documentation/git-reflog.txt index 70791b9fd8..5e7908e4f7 100644 --- a/Documentation/git-reflog.txt +++ b/Documentation/git-reflog.txt @@ -17,85 +17,113 @@ The command takes various subcommands, and different options depending on the subcommand: [verse] -'git reflog expire' [--dry-run] [--stale-fix] [--verbose] - [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>... -'git reflog delete' ref@\{specifier\}... 'git reflog' ['show'] [log-options] [<ref>] +'git reflog expire' [--expire=<time>] [--expire-unreachable=<time>] + [--rewrite] [--updateref] [--stale-fix] + [--dry-run] [--verbose] [--all | <refs>...] +'git reflog delete' [--rewrite] [--updateref] + [--dry-run] [--verbose] ref@\{specifier\}... + +Reference logs, or "reflogs", record when the tips of branches and +other references were updated in the local repository. Reflogs are +useful in various Git commands, to specify the old value of a +reference. For example, `HEAD@{2}` means "where HEAD used to be two +moves ago", `master@{one.week.ago}` means "where master used to point +to one week ago in this local repository", and so on. See +linkgit:gitrevisions[7] for more details. + +This command manages the information recorded in the reflogs. + +The "show" subcommand (which is also the default, in the absence of +any subcommands) shows the log of the reference provided in the +command-line (or `HEAD`, by default). The reflog covers all recent +actions, and in addition the `HEAD` reflog records branch switching. +`git reflog show` is an alias for `git log -g --abbrev-commit +--pretty=oneline`; see linkgit:git-log[1] for more information. + +The "expire" subcommand prunes older reflog entries. Entries older +than `expire` time, or entries older than `expire-unreachable` time +and not reachable from the current tip, are removed from the reflog. +This is typically not used directly by end users -- instead, see +linkgit:git-gc[1]. + +The "delete" subcommand deletes single entries from the reflog. Its +argument must be an _exact_ entry (e.g. "`git reflog delete +master@{2}`"). This subcommand is also typically not used directly by +end users. -Reflog is a mechanism to record when the tip of branches are -updated. This command is to manage the information recorded in it. -The subcommand "expire" is used to prune older reflog entries. -Entries older than `expire` time, or entries older than -`expire-unreachable` time and not reachable from the current -tip, are removed from the reflog. This is typically not used -directly by the end users -- instead, see linkgit:git-gc[1]. - -The subcommand "show" (which is also the default, in the absence of any -subcommands) will take all the normal log options, and show the log of -the reference provided in the command-line (or `HEAD`, by default). -The reflog will cover all recent actions (HEAD reflog records branch switching -as well). It is an alias for `git log -g --abbrev-commit --pretty=oneline`; -see linkgit:git-log[1]. +OPTIONS +------- -The reflog is useful in various Git commands, to specify the old value -of a reference. For example, `HEAD@{2}` means "where HEAD used to be -two moves ago", `master@{one.week.ago}` means "where master used to -point to one week ago", and so on. See linkgit:gitrevisions[7] for -more details. +Options for `show` +~~~~~~~~~~~~~~~~~~ -To delete single entries from the reflog, use the subcommand "delete" -and specify the _exact_ entry (e.g. "`git reflog delete master@{2}`"). +`git reflog show` accepts any of the options accepted by `git log`. -OPTIONS -------- +Options for `expire` +~~~~~~~~~~~~~~~~~~~~ ---stale-fix:: - This revamps the logic -- the definition of "broken commit" - becomes: a commit that is not reachable from any of the refs and - there is a missing object among the commit, tree, or blob - objects reachable from it that is not reachable from any of the - refs. -+ -This computation involves traversing all the reachable objects, i.e. it -has the same cost as 'git prune'. Fortunately, once this is run, we -should not have to ever worry about missing objects, because the current -prune and pack-objects know about reflogs and protect objects referred by -them. +--all:: + Process the reflogs of all references. --expire=<time>:: - Entries older than this time are pruned. Without the - option it is taken from configuration `gc.reflogExpire`, - which in turn defaults to 90 days. --expire=all prunes - entries regardless of their age; --expire=never turns off - pruning of reachable entries (but see --expire-unreachable). + Prune entries older than the specified time. If this option is + not specified, the expiration time is taken from the + configuration setting `gc.reflogExpire`, which in turn + defaults to 90 days. `--expire=all` prunes entries regardless + of their age; `--expire=never` turns off pruning of reachable + entries (but see `--expire-unreachable`). --expire-unreachable=<time>:: - Entries older than this time and not reachable from - the current tip of the branch are pruned. Without the - option it is taken from configuration - `gc.reflogExpireUnreachable`, which in turn defaults to - 30 days. --expire-unreachable=all prunes unreachable - entries regardless of their age; --expire-unreachable=never + Prune entries older than `<time>` that are not reachable from + the current tip of the branch. If this option is not + specified, the expiration time is taken from the configuration + setting `gc.reflogExpireUnreachable`, which in turn defaults + to 30 days. `--expire-unreachable=all` prunes unreachable + entries regardless of their age; `--expire-unreachable=never` turns off early pruning of unreachable entries (but see - --expire). - ---all:: - Instead of listing <refs> explicitly, prune all refs. + `--expire`). --updateref:: - Update the ref with the sha1 of the top reflog entry (i.e. - <ref>@\{0\}) after expiring or deleting. + Update the reference to the value of the top reflog entry (i.e. + <ref>@\{0\}) if the previous top entry was pruned. (This + option is ignored for symbolic references.) --rewrite:: - While expiring or deleting, adjust each reflog entry to ensure - that the `old` sha1 field points to the `new` sha1 field of the - previous entry. + If a reflog entry's predecessor is pruned, adjust its "old" + SHA-1 to be equal to the "new" SHA-1 field of the entry that + now precedes it. + +--stale-fix:: + Prune any reflog entries that point to "broken commits". A + broken commit is a commit that is not reachable from any of + the reference tips and that refers, directly or indirectly, to + a missing commit, tree, or blob object. ++ +This computation involves traversing all the reachable objects, i.e. it +has the same cost as 'git prune'. It is primarily intended to fix +corruption caused by garbage collecting using older versions of Git, +which didn't protect objects referred to by reflogs. + +-n:: +--dry-run:: + Do not actually prune any entries; just show what would have + been pruned. --verbose:: Print extra information on screen. + +Options for `delete` +~~~~~~~~~~~~~~~~~~~~ + +`git reflog delete` accepts options `--updateref`, `--rewrite`, `-n`, +`--dry-run`, and `--verbose`, with the same meanings as when they are +used with `expire`. + + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index a77607b852..4c6d6de7b7 100644 --- a/Documentation/git-remote.txt +++ b/Documentation/git-remote.txt @@ -58,6 +58,9 @@ remote repository. With `--no-tags` option, `git fetch <name>` does not import tags from the remote repository. + +By default, only tags on fetched branches are imported +(see linkgit:git-fetch[1]). ++ With `-t <branch>` option, instead of the default glob refspec for the remote to track all branches under the `refs/remotes/<name>/` namespace, a refspec to track only `<branch>` diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt index 2a0de42a75..45c7725dc3 100644 --- a/Documentation/git-send-pack.txt +++ b/Documentation/git-send-pack.txt @@ -9,7 +9,7 @@ git-send-pack - Push objects over Git protocol to another repository SYNOPSIS -------- [verse] -'git send-pack' [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...] +'git send-pack' [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [--atomic] [<host>:]<directory> [<ref>...] DESCRIPTION ----------- @@ -62,6 +62,11 @@ be in a separate packet, and the list must end with a flush packet. Send a "thin" pack, which records objects in deltified form based on objects not included in the pack to reduce network traffic. +--atomic:: + Use an atomic transaction for updating the refs. If any of the refs + fails to update then the entire push will fail without changing any + refs. + <host>:: A remote host to house the repository. When this part is specified, 'git-receive-pack' is invoked via diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index 8e6af65da0..2c25916f8f 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -154,27 +154,51 @@ 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. - This will make the submodules HEAD be detached unless `--rebase` or - `--merge` is specified or the key `submodule.$name.update` is set to - `rebase`, `merge` or `none`. `none` can be overridden by specifying - `--checkout`. Setting the key `submodule.$name.update` to `!command` - will cause `command` to be run. `command` can be any arbitrary shell - command that takes a single argument, namely the sha1 to update to. + +-- +Update the registered submodules to match what the superproject +expects by cloning missing submodules and updating the working tree of +the submodules. The "updating" can be done in several ways depending +on command line options and the value of `submodule.<name>.update` +configuration variable. Supported update procedures are: + + checkout;; the commit recorded in the superproject will be + checked out in the submodule on a detached HEAD. This is + done when `--checkout` option is given, or no option is + given, and `submodule.<name>.update` is unset, or if it is + set to 'checkout'. ++ +If `--force` is specified, the submodule will be checked out (using +`git checkout --force` if appropriate), even if the commit specified +in the index of the containing repository already matches the commit +checked out in the submodule. + + rebase;; the current branch of the submodule will be rebased + onto the commit recorded in the superproject. This is done + when `--rebase` option is given, or no option is given, and + `submodule.<name>.update` is set to 'rebase'. + + merge;; the commit recorded in the superproject will be merged + into the current branch in the submodule. This is done + when `--merge` option is given, or no option is given, and + `submodule.<name>.update` is set to 'merge'. + + custom command;; arbitrary shell command that takes a single + argument (the sha1 of the commit recorded in the + superproject) is executed. This is done when no option is + given, and `submodule.<name>.update` has the form of + '!command'. + +When no option is given and `submodule.<name>.update` is set to 'none', +the submodule is not updated. + If the submodule is not yet initialized, and you just want to use the setting as stored in .gitmodules, you can automatically initialize the submodule with the `--init` option. -+ + If `--recursive` is specified, this command will recurse into the registered submodules, and update any nested submodules within. -+ -If `--force` is specified, the submodule will be checked out (using -`git checkout --force` if appropriate), even if the commit specified in the -index of the containing repository already matches the commit checked out in -the submodule. - +-- summary:: Show commit summary between the given commit (defaults to HEAD) and working tree/index. For a submodule in question, a series of commits @@ -238,10 +262,12 @@ OPTIONS 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 - containing repository matches the commit checked out in the submodule. + When running update (only effective with the checkout procedure), + 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 + containing repository matches the commit checked out in the + submodule. --cached:: This option is only valid for status and summary commands. These @@ -302,7 +328,7 @@ the submodule itself. Checkout the commit recorded in the superproject on a detached HEAD in the submodule. This is the default behavior, the main use of this option is to override `submodule.$name.update` when set to - `merge`, `rebase` or `none`. + a value other than `checkout`. If the key `submodule.$name.update` is either not explicitly set or set to `checkout`, this option is implicit. diff --git a/Documentation/git.txt b/Documentation/git.txt index 52c4d2a7c9..af306209e9 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,9 +43,10 @@ unreleased) version of Git, that is available from the 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v2.3.2/git.html[documentation for release 2.3.2] +* link:v2.3.3/git.html[documentation for release 2.3.3] * release notes for + link:RelNotes/2.3.3.txt[2.3.3], link:RelNotes/2.3.2.txt[2.3.2], link:RelNotes/2.3.1.txt[2.3.1], link:RelNotes/2.3.0.txt[2.3]. diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt index 9ef2469373..7ba0ac965d 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -341,6 +341,36 @@ Both standard output and standard error output are forwarded to 'git send-pack' on the other end, so you can simply `echo` messages for the user. +push-to-checkout +~~~~~~~~~~~~~~~~ + +This hook is invoked by 'git-receive-pack' on the remote repository, +which happens when a 'git push' is done on a local repository, when +the push tries to update the branch that is currently checked out +and the `receive.denyCurrentBranch` configuration variable is set to +`updateInstead`. Such a push by default is refused if the working +tree and the index of the remote repository has any difference from +the currently checked out commit; when both the working tree and the +index match the current commit, they are updated to match the newly +pushed tip of the branch. This hook is to be used to override the +default behaviour. + +The hook receives the commit with which the tip of the current +branch is going to be updated. It can exit with a non-zero status +to refuse the push (when it does so, it must not modify the index or +the working tree). Or it can make any necessary changes to the +working tree and to the index to bring them to the desired state +when the tip of the current branch is updated to the new commit, and +exit with a zero status. + +For example, the hook can simply run `git read-tree -u -m HEAD "$1"` +in order to emulate 'git fetch' that is run in the reverse direction +with `git push`, as the two-tree form of `read-tree -u -m` is +essentially the same as `git checkout` that switches branches while +keeping the local changes in the working tree that do not interfere +with the difference between the branches. + + pre-auto-gc ~~~~~~~~~~~ diff --git a/Documentation/gitmodules.txt b/Documentation/gitmodules.txt index f6c0dfd029..ac70eca321 100644 --- a/Documentation/gitmodules.txt +++ b/Documentation/gitmodules.txt @@ -38,18 +38,15 @@ submodule.<name>.url:: In addition, there are a number of optional keys: submodule.<name>.update:: - Defines what to do when the submodule is updated by the superproject. - If 'checkout' (the default), the new commit specified in the - superproject will be checked out in the submodule on a detached HEAD. - If 'rebase', the current branch of the submodule will be rebased onto - the commit specified in the superproject. If 'merge', the commit - specified in the superproject will be merged into the current branch - in the submodule. - If 'none', the submodule with name `$name` will not be updated - by default. - - This config option is overridden if 'git submodule update' is given - the '--merge', '--rebase' or '--checkout' options. + Defines the default update procedure for the named submodule, + i.e. how the submodule is updated by "git submodule update" + command in the superproject. This is only used by `git + submodule init` to initialize the configuration variable of + the same name. Allowed values here are 'checkout', 'rebase', + 'merge' or 'none'. See description of 'update' command in + linkgit:git-submodule[1] for their meaning. Note that the + '!command' form is intentionally ignored here for security + reasons. submodule.<name>.branch:: A remote branch name for tracking updates in the upstream submodule. diff --git a/Documentation/pretty-options.txt b/Documentation/pretty-options.txt index 8569e29d08..74aa01a0ec 100644 --- a/Documentation/pretty-options.txt +++ b/Documentation/pretty-options.txt @@ -3,9 +3,13 @@ Pretty-print the contents of the commit logs in a given format, where '<format>' can be one of 'oneline', 'short', 'medium', - 'full', 'fuller', 'email', 'raw' and 'format:<string>'. See - the "PRETTY FORMATS" section for some additional details for each - format. When omitted, the format defaults to 'medium'. + 'full', 'fuller', 'email', 'raw', 'format:<string>' + and 'tformat:<string>'. When '<format>' is none of the above, + and has '%placeholder' in it, it acts as if + '--pretty=tformat:<format>' were given. ++ +See the "PRETTY FORMATS" section for some additional details for each +format. When '=<format>' part is omitted, it defaults to 'medium'. + Note: you can specify the default pretty format in the repository configuration (see linkgit:git-config[1]). diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index 97ef2e8e71..4ed8587c84 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -66,6 +66,10 @@ if it is part of the log message. Limit the commits output to ones that match all given `--grep`, instead of ones that match at least one. +--invert-grep:: + Limit the commits output to ones with log message that do not + match the pattern specified with `--grep=<pattern>`. + -i:: --regexp-ignore-case:: Match the regular expression limiting patterns without regard to letter diff --git a/Documentation/technical/api-error-handling.txt b/Documentation/technical/api-error-handling.txt new file mode 100644 index 0000000000..fc68db126e --- /dev/null +++ b/Documentation/technical/api-error-handling.txt @@ -0,0 +1,75 @@ +Error reporting in git +====================== + +`die`, `usage`, `error`, and `warning` report errors of various +kinds. + +- `die` is for fatal application errors. It prints a message to + the user and exits with status 128. + +- `usage` is for errors in command line usage. After printing its + message, it exits with status 129. (See also `usage_with_options` + in the link:api-parse-options.html[parse-options API].) + +- `error` is for non-fatal library errors. It prints a message + to the user and returns -1 for convenience in signaling the error + to the caller. + +- `warning` is for reporting situations that probably should not + occur but which the user (and Git) can continue to work around + without running into too many problems. Like `error`, it + returns -1 after reporting the situation to the caller. + +Customizable error handlers +--------------------------- + +The default behavior of `die` and `error` is to write a message to +stderr and then exit or return as appropriate. This behavior can be +overridden using `set_die_routine` and `set_error_routine`. For +example, "git daemon" uses set_die_routine to write the reason `die` +was called to syslog before exiting. + +Library errors +-------------- + +Functions return a negative integer on error. Details beyond that +vary from function to function: + +- Some functions return -1 for all errors. Others return a more + specific value depending on how the caller might want to react + to the error. + +- Some functions report the error to stderr with `error`, + while others leave that for the caller to do. + +- errno is not meaningful on return from most functions (except + for thin wrappers for system calls). + +Check the function's API documentation to be sure. + +Caller-handled errors +--------------------- + +An increasing number of functions take a parameter 'struct strbuf *err'. +On error, such functions append a message about what went wrong to the +'err' strbuf. The message is meant to be complete enough to be passed +to `die` or `error` as-is. For example: + + if (ref_transaction_commit(transaction, &err)) + die("%s", err.buf); + +The 'err' parameter will be untouched if no error occured, so multiple +function calls can be chained: + + t = ref_transaction_begin(&err); + if (!t || + ref_transaction_update(t, "HEAD", ..., &err) || + ret_transaction_commit(t, &err)) + die("%s", err.buf); + +The 'err' parameter must be a pointer to a valid strbuf. To silence +a message, pass a strbuf that is explicitly ignored: + + if (thing_that_can_fail_in_an_ignorable_way(..., &err)) + /* This failure is okay. */ + strbuf_reset(&err); diff --git a/Documentation/technical/api-strbuf.txt b/Documentation/technical/api-strbuf.txt deleted file mode 100644 index cca6543234..0000000000 --- a/Documentation/technical/api-strbuf.txt +++ /dev/null @@ -1,351 +0,0 @@ -strbuf API -========== - -strbuf's are meant to be used with all the usual C string and memory -APIs. Given that the length of the buffer is known, it's often better to -use the mem* functions than a str* one (memchr vs. strchr e.g.). -Though, one has to be careful about the fact that str* functions often -stop on NULs and that strbufs may have embedded NULs. - -A strbuf is NUL terminated for convenience, but no function in the -strbuf API actually relies on the string being free of NULs. - -strbufs have some invariants that are very important to keep in mind: - -. The `buf` member is never NULL, so it can be used in any usual C -string operations safely. strbuf's _have_ to be initialized either by -`strbuf_init()` or by `= STRBUF_INIT` before the invariants, though. -+ -Do *not* assume anything on what `buf` really is (e.g. if it is -allocated memory or not), use `strbuf_detach()` to unwrap a memory -buffer from its strbuf shell in a safe way. That is the sole supported -way. This will give you a malloced buffer that you can later `free()`. -+ -However, it is totally safe to modify anything in the string pointed by -the `buf` member, between the indices `0` and `len-1` (inclusive). - -. The `buf` member is a byte array that has at least `len + 1` bytes - allocated. The extra byte is used to store a `'\0'`, allowing the - `buf` member to be a valid C-string. Every strbuf function ensure this - invariant is preserved. -+ -NOTE: It is OK to "play" with the buffer directly if you work it this - way: -+ ----- -strbuf_grow(sb, SOME_SIZE); <1> -strbuf_setlen(sb, sb->len + SOME_OTHER_SIZE); ----- -<1> Here, the memory array starting at `sb->buf`, and of length -`strbuf_avail(sb)` is all yours, and you can be sure that -`strbuf_avail(sb)` is at least `SOME_SIZE`. -+ -NOTE: `SOME_OTHER_SIZE` must be smaller or equal to `strbuf_avail(sb)`. -+ -Doing so is safe, though if it has to be done in many places, adding the -missing API to the strbuf module is the way to go. -+ -WARNING: Do _not_ assume that the area that is yours is of size `alloc -- 1` even if it's true in the current implementation. Alloc is somehow a -"private" member that should not be messed with. Use `strbuf_avail()` -instead. - -Data structures ---------------- - -* `struct strbuf` - -This is the string buffer structure. The `len` member can be used to -determine the current length of the string, and `buf` member provides -access to the string itself. - -Functions ---------- - -* Life cycle - -`strbuf_init`:: - - Initialize the structure. The second parameter can be zero or a bigger - number to allocate memory, in case you want to prevent further reallocs. - -`strbuf_release`:: - - Release a string buffer and the memory it used. You should not use the - string buffer after using this function, unless you initialize it again. - -`strbuf_detach`:: - - Detach the string from the strbuf and returns it; you now own the - storage the string occupies and it is your responsibility from then on - to release it with `free(3)` when you are done with it. - -`strbuf_attach`:: - - Attach a string to a buffer. You should specify the string to attach, - the current length of the string and the amount of allocated memory. - The amount must be larger than the string length, because the string you - pass is supposed to be a NUL-terminated string. This string _must_ be - malloc()ed, and after attaching, the pointer cannot be relied upon - anymore, and neither be free()d directly. - -`strbuf_swap`:: - - Swap the contents of two string buffers. - -* Related to the size of the buffer - -`strbuf_avail`:: - - Determine the amount of allocated but unused memory. - -`strbuf_grow`:: - - Ensure that at least this amount of unused memory is available after - `len`. This is used when you know a typical size for what you will add - and want to avoid repetitive automatic resizing of the underlying buffer. - This is never a needed operation, but can be critical for performance in - some cases. - -`strbuf_setlen`:: - - Set the length of the buffer to a given value. This function does *not* - allocate new memory, so you should not perform a `strbuf_setlen()` to a - length that is larger than `len + strbuf_avail()`. `strbuf_setlen()` is - just meant as a 'please fix invariants from this strbuf I just messed - with'. - -`strbuf_reset`:: - - Empty the buffer by setting the size of it to zero. - -* Related to the contents of the buffer - -`strbuf_trim`:: - - Strip whitespace from the beginning and end of a string. - Equivalent to performing `strbuf_rtrim()` followed by `strbuf_ltrim()`. - -`strbuf_rtrim`:: - - Strip whitespace from the end of a string. - -`strbuf_ltrim`:: - - Strip whitespace from the beginning of a string. - -`strbuf_reencode`:: - - Replace the contents of the strbuf with a reencoded form. Returns -1 - on error, 0 on success. - -`strbuf_tolower`:: - - Lowercase each character in the buffer using `tolower`. - -`strbuf_cmp`:: - - Compare two buffers. Returns an integer less than, equal to, or greater - than zero if the first buffer is found, respectively, to be less than, - to match, or be greater than the second buffer. - -* Adding data to the buffer - -NOTE: All of the functions in this section will grow the buffer as necessary. -If they fail for some reason other than memory shortage and the buffer hadn't -been allocated before (i.e. the `struct strbuf` was set to `STRBUF_INIT`), -then they will free() it. - -`strbuf_addch`:: - - Add a single character to the buffer. - -`strbuf_addchars`:: - - Add a character the specified number of times to the buffer. - -`strbuf_insert`:: - - Insert data to the given position of the buffer. The remaining contents - will be shifted, not overwritten. - -`strbuf_remove`:: - - Remove given amount of data from a given position of the buffer. - -`strbuf_splice`:: - - Remove the bytes between `pos..pos+len` and replace it with the given - data. - -`strbuf_add_commented_lines`:: - - Add a NUL-terminated string to the buffer. Each line will be prepended - by a comment character and a blank. - -`strbuf_add`:: - - Add data of given length to the buffer. - -`strbuf_addstr`:: - -Add a NUL-terminated string to the buffer. -+ -NOTE: This function will *always* be implemented as an inline or a macro -that expands to: -+ ----- -strbuf_add(..., s, strlen(s)); ----- -+ -Meaning that this is efficient to write things like: -+ ----- -strbuf_addstr(sb, "immediate string"); ----- - -`strbuf_addbuf`:: - - Copy the contents of another buffer at the end of the current one. - -`strbuf_adddup`:: - - Copy part of the buffer from a given position till a given length to the - end of the buffer. - -`strbuf_expand`:: - - This function can be used to expand a format string containing - placeholders. To that end, it parses the string and calls the specified - function for every percent sign found. -+ -The callback function is given a pointer to the character after the `%` -and a pointer to the struct strbuf. It is expected to add the expanded -version of the placeholder to the strbuf, e.g. to add a newline -character if the letter `n` appears after a `%`. The function returns -the length of the placeholder recognized and `strbuf_expand()` skips -over it. -+ -The format `%%` is automatically expanded to a single `%` as a quoting -mechanism; callers do not need to handle the `%` placeholder themselves, -and the callback function will not be invoked for this placeholder. -+ -All other characters (non-percent and not skipped ones) are copied -verbatim to the strbuf. If the callback returned zero, meaning that the -placeholder is unknown, then the percent sign is copied, too. -+ -In order to facilitate caching and to make it possible to give -parameters to the callback, `strbuf_expand()` passes a context pointer, -which can be used by the programmer of the callback as she sees fit. - -`strbuf_expand_dict_cb`:: - - Used as callback for `strbuf_expand()`, expects an array of - struct strbuf_expand_dict_entry as context, i.e. pairs of - placeholder and replacement string. The array needs to be - terminated by an entry with placeholder set to NULL. - -`strbuf_addbuf_percentquote`:: - - Append the contents of one strbuf to another, quoting any - percent signs ("%") into double-percents ("%%") in the - 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. - -`strbuf_commented_addf`:: - - Add a formatted string prepended by a comment character and a - blank to the buffer. - -`strbuf_fread`:: - - Read a given size of data from a FILE* pointer to the buffer. -+ -NOTE: The buffer is rewound if the read fails. If -1 is returned, -`errno` must be consulted, like you would do for `read(3)`. -`strbuf_read()`, `strbuf_read_file()` and `strbuf_getline()` has the -same behaviour as well. - -`strbuf_read`:: - - Read the contents of a given file descriptor. The third argument can be - used to give a hint about the file size, to avoid reallocs. - -`strbuf_read_file`:: - - Read the contents of a file, specified by its path. The third argument - can be used to give a hint about the file size, to avoid reallocs. - -`strbuf_readlink`:: - - Read the target of a symbolic link, specified by its path. The third - argument can be used to give a hint about the size, to avoid reallocs. - -`strbuf_getline`:: - - Read a line from a FILE *, overwriting the existing contents - of the strbuf. The second argument specifies the line - terminator character, typically `'\n'`. - Reading stops after the terminator or at EOF. The terminator - is removed from the buffer before returning. Returns 0 unless - there was nothing left before EOF, in which case it returns `EOF`. - -`strbuf_getwholeline`:: - - Like `strbuf_getline`, but keeps the trailing terminator (if - any) in the buffer. - -`strbuf_getwholeline_fd`:: - - Like `strbuf_getwholeline`, but operates on a file descriptor. - It reads one character at a time, so it is very slow. Do not - use it unless you need the correct position in the file - descriptor. - -`strbuf_getcwd`:: - - Set the buffer to the path of the current working directory. - -`strbuf_add_absolute_path` - - Add a path to a buffer, converting a relative path to an - absolute one in the process. Symbolic links are not - resolved. - -`stripspace`:: - - Strip whitespace from a buffer. The second parameter controls if - comments are considered contents to be removed or not. - -`strbuf_split_buf`:: -`strbuf_split_str`:: -`strbuf_split_max`:: -`strbuf_split`:: - - Split a string or strbuf into a list of strbufs at a specified - terminator character. The returned substrings include the - terminator characters. Some of these functions take a `max` - parameter, which, if positive, limits the output to that - number of substrings. - -`strbuf_list_free`:: - - Free a list of strbufs (for example, the return values of the - `strbuf_split()` functions). - -`launch_editor`:: - - Launch the user preferred editor to edit a file and fill the buffer - with the file's contents upon the user completing their editing. The - third argument can be used to set the environment which the editor is - run in. If the buffer is NULL the editor is launched as usual but the - file's contents are not read into the buffer upon completion. diff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt index 6d5424c1bd..4f8a7bfb4c 100644 --- a/Documentation/technical/protocol-capabilities.txt +++ b/Documentation/technical/protocol-capabilities.txt @@ -18,8 +18,9 @@ was sent. Server MUST NOT ignore capabilities that client requested and server advertised. As a consequence of these rules, server MUST NOT advertise capabilities it does not understand. -The 'report-status', 'delete-refs', 'quiet', and 'push-cert' capabilities -are sent and recognized by the receive-pack (push to server) process. +The 'atomic', 'report-status', 'delete-refs', 'quiet', and 'push-cert' +capabilities are sent and recognized by the receive-pack (push to server) +process. The 'ofs-delta' and 'side-band-64k' capabilities are sent and recognized by both upload-pack and receive-pack protocols. The 'agent' capability @@ -244,6 +245,14 @@ respond with the 'quiet' capability to suppress server-side progress reporting if the local progress reporting is also being suppressed (e.g., via `push -q`, or if stderr does not go to a tty). +atomic +------ + +If the server sends the 'atomic' capability it is capable of accepting +atomic pushes. If the pushing client requests this capability, the server +will update the refs in one atomic transaction. Either all refs are +updated or none. + allow-tip-sha1-in-want ---------------------- diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 50078659fc..734fa9b434 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.3.2 +DEF_VER=v2.3.3.GIT LF=' ' @@ -348,6 +348,17 @@ all:: # # Define NO_HMAC_CTX_CLEANUP if your OpenSSL is version 0.9.6b or earlier to # cleanup the HMAC context with the older HMAC_cleanup function. +# +# Define USE_PARENS_AROUND_GETTEXT_N to "yes" if your compiler happily +# compiles the following initialization: +# +# static const char s[] = ("FOO"); +# +# and define it to "no" if you need to remove the parentheses () around the +# constant. The default is "auto", which means to use parentheses if your +# compiler is detected to support it. +# +# Define HAVE_BSD_SYSCTL if your platform has a BSD-compatible sysctl function. GIT-VERSION-FILE: FORCE @$(SHELL_PATH) ./GIT-VERSION-GEN @@ -955,6 +966,14 @@ ifneq (,$(SOCKLEN_T)) BASIC_CFLAGS += -Dsocklen_t=$(SOCKLEN_T) endif +ifeq (yes,$(USE_PARENS_AROUND_GETTEXT_N)) + BASIC_CFLAGS += -DUSE_PARENS_AROUND_GETTEXT_N=1 +else +ifeq (no,$(USE_PARENS_AROUND_GETTEXT_N)) + BASIC_CFLAGS += -DUSE_PARENS_AROUND_GETTEXT_N=0 +endif +endif + ifeq ($(uname_S),Darwin) ifndef NO_FINK ifeq ($(shell test -d /sw/lib && echo y),y) @@ -1414,6 +1433,10 @@ ifdef HAVE_CLOCK_MONOTONIC BASIC_CFLAGS += -DHAVE_CLOCK_MONOTONIC endif +ifdef HAVE_BSD_SYSCTL + BASIC_CFLAGS += -DHAVE_BSD_SYSCTL +endif + ifeq ($(TCLTK_PATH),) NO_TCLTK = NoThanks endif @@ -1 +1 @@ -Documentation/RelNotes/2.3.2.txt
\ No newline at end of file +Documentation/RelNotes/2.4.0.txt
\ No newline at end of file @@ -105,7 +105,7 @@ void detach_advice(const char *new_name) "state without impacting any branches by performing another checkout.\n\n" "If you want to create a new branch to retain commits you create, you may\n" "do so (now or later) by using -b with the checkout command again. Example:\n\n" - " git checkout -b new_branch_name\n\n"; + " git checkout -b <new-branch-name>\n\n"; fprintf(stderr, fmt, new_name); } diff --git a/archive-zip.c b/archive-zip.c index 4bde019bce..ffb3535e93 100644 --- a/archive-zip.c +++ b/archive-zip.c @@ -5,6 +5,8 @@ #include "archive.h" #include "streaming.h" #include "utf8.h" +#include "userdiff.h" +#include "xdiff-interface.h" static int zip_date; static int zip_time; @@ -120,7 +122,6 @@ static void *zlib_deflate_raw(void *data, unsigned long size, void *buffer; int result; - memset(&stream, 0, sizeof(stream)); git_deflate_init_raw(&stream, compression_level); maxsize = git_deflate_bound(&stream, size); buffer = xmalloc(maxsize); @@ -189,6 +190,16 @@ static int has_only_ascii(const char *s) } } +static int entry_is_binary(const char *path, const void *buffer, size_t size) +{ + struct userdiff_driver *driver = userdiff_find_by_path(path); + if (!driver) + driver = userdiff_find_by_name("default"); + if (driver->binary != -1) + return driver->binary; + return buffer_is_binary(buffer, size); +} + #define STREAM_BUFFER_SIZE (1024 * 16) static int write_zip_entry(struct archiver_args *args, @@ -210,6 +221,8 @@ static int write_zip_entry(struct archiver_args *args, struct git_istream *stream = NULL; unsigned long flags = 0; unsigned long size; + int is_binary = -1; + const char *path_without_prefix = path + args->baselen; crc = crc32(0, NULL, 0); @@ -256,6 +269,8 @@ static int write_zip_entry(struct archiver_args *args, return error("cannot read %s", sha1_to_hex(sha1)); crc = crc32(crc, buffer, size); + is_binary = entry_is_binary(path_without_prefix, + buffer, size); out = buffer; } compressed_size = (method == 0) ? size : 0; @@ -300,7 +315,6 @@ static int write_zip_entry(struct archiver_args *args, copy_le16(dirent.extra_length, ZIP_EXTRA_MTIME_SIZE); copy_le16(dirent.comment_length, 0); copy_le16(dirent.disk, 0); - copy_le16(dirent.attr1, 0); copy_le32(dirent.attr2, attr2); copy_le32(dirent.offset, zip_offset); @@ -328,6 +342,9 @@ static int write_zip_entry(struct archiver_args *args, if (readlen <= 0) break; crc = crc32(crc, buf, readlen); + if (is_binary == -1) + is_binary = entry_is_binary(path_without_prefix, + buf, readlen); write_or_die(1, buf, readlen); } close_istream(stream); @@ -349,7 +366,6 @@ static int write_zip_entry(struct archiver_args *args, size_t out_len; unsigned char compressed[STREAM_BUFFER_SIZE * 2]; - memset(&zstream, 0, sizeof(zstream)); git_deflate_init_raw(&zstream, args->compression_level); compressed_size = 0; @@ -361,6 +377,9 @@ static int write_zip_entry(struct archiver_args *args, if (readlen <= 0) break; crc = crc32(crc, buf, readlen); + if (is_binary == -1) + is_binary = entry_is_binary(path_without_prefix, + buf, readlen); zstream.next_in = buf; zstream.avail_in = readlen; @@ -405,6 +424,8 @@ static int write_zip_entry(struct archiver_args *args, free(deflated); free(buffer); + copy_le16(dirent.attr1, !is_binary); + memcpy(zip_dir + zip_dir_offset, &dirent, ZIP_DIR_HEADER_SIZE); zip_dir_offset += ZIP_DIR_HEADER_SIZE; memcpy(zip_dir + zip_dir_offset, path, pathlen); @@ -8,9 +8,9 @@ #include "dir.h" static char const * const archive_usage[] = { - N_("git archive [options] <tree-ish> [<path>...]"), + N_("git archive [<options>] <tree-ish> [<path>...]"), N_("git archive --list"), - N_("git archive --remote <repo> [--exec <cmd>] [options] <tree-ish> [<path>...]"), + N_("git archive --remote <repo> [--exec <cmd>] [<options>] <tree-ish> [<path>...]"), N_("git archive --remote <repo> [--exec <cmd>] --list"), NULL }; @@ -32,9 +32,12 @@ struct git_attr { struct git_attr *next; unsigned h; int attr_nr; + int maybe_macro; + int maybe_real; char name[FLEX_ARRAY]; }; static int attr_nr; +static int cannot_trust_maybe_real; static struct git_attr_check *check_all_attr; static struct git_attr *(git_attr_hash[HASHSIZE]); @@ -95,6 +98,8 @@ static struct git_attr *git_attr_internal(const char *name, int len) a->h = hval; a->next = git_attr_hash[pos]; a->attr_nr = attr_nr++; + a->maybe_macro = 0; + a->maybe_real = 0; git_attr_hash[pos] = a; REALLOC_ARRAY(check_all_attr, attr_nr); @@ -244,9 +249,10 @@ static struct match_attr *parse_attr_line(const char *line, const char *src, sizeof(*res) + sizeof(struct attr_state) * num_attr + (is_macro ? 0 : namelen + 1)); - if (is_macro) + if (is_macro) { res->u.attr = git_attr_internal(name, namelen); - else { + res->u.attr->maybe_macro = 1; + } else { char *p = (char *)&(res->state[num_attr]); memcpy(p, name, namelen); res->u.pat.pattern = p; @@ -266,6 +272,10 @@ static struct match_attr *parse_attr_line(const char *line, const char *src, /* Second pass to fill the attr_states */ for (cp = states, i = 0; *cp; i++) { cp = parse_attr(src, lineno, cp, &(res->state[i])); + if (!is_macro) + res->state[i].attr->maybe_real = 1; + if (res->state[i].attr->maybe_macro) + cannot_trust_maybe_real = 1; } return res; @@ -681,13 +691,14 @@ static int fill(const char *path, int pathlen, int basename_offset, return rem; } -static int macroexpand_one(int attr_nr, int rem) +static int macroexpand_one(int nr, int rem) { struct attr_stack *stk; struct match_attr *a = NULL; int i; - if (check_all_attr[attr_nr].value != ATTR__TRUE) + if (check_all_attr[nr].value != ATTR__TRUE || + !check_all_attr[nr].attr->maybe_macro) return rem; for (stk = attr_stack; !a && stk; stk = stk->prev) @@ -695,7 +706,7 @@ static int macroexpand_one(int attr_nr, int rem) struct match_attr *ma = stk->attrs[i]; if (!ma->is_macro) continue; - if (ma->u.attr->attr_nr == attr_nr) + if (ma->u.attr->attr_nr == nr) a = ma; } @@ -706,10 +717,13 @@ static int macroexpand_one(int attr_nr, int rem) } /* - * Collect all attributes for path into the array pointed to by - * check_all_attr. + * Collect attributes for path into the array pointed to by + * check_all_attr. If num is non-zero, only attributes in check[] are + * collected. Otherwise all attributes are collected. */ -static void collect_all_attrs(const char *path) +static void collect_some_attrs(const char *path, int num, + struct git_attr_check *check) + { struct attr_stack *stk; int i, pathlen, rem, dirlen; @@ -732,6 +746,19 @@ static void collect_all_attrs(const char *path) prepare_attr_stack(path, dirlen); for (i = 0; i < attr_nr; i++) check_all_attr[i].value = ATTR__UNKNOWN; + if (num && !cannot_trust_maybe_real) { + rem = 0; + for (i = 0; i < num; i++) { + if (!check[i].attr->maybe_real) { + struct git_attr_check *c; + c = check_all_attr + check[i].attr->attr_nr; + c->value = ATTR__UNSET; + rem++; + } + } + if (rem == num) + return; + } rem = attr_nr; for (stk = attr_stack; 0 < rem && stk; stk = stk->prev) @@ -742,7 +769,7 @@ int git_check_attr(const char *path, int num, struct git_attr_check *check) { int i; - collect_all_attrs(path); + collect_some_attrs(path, num, check); for (i = 0; i < num; i++) { const char *value = check_all_attr[check[i].attr->attr_nr].value; @@ -758,7 +785,7 @@ int git_all_attrs(const char *path, int *num, struct git_attr_check **check) { int i, count, j; - collect_all_attrs(path); + collect_some_attrs(path, 0, NULL); /* Count the number of attributes that are set. */ count = 0; @@ -284,8 +284,9 @@ void create_branch(const char *head, transaction = ref_transaction_begin(&err); if (!transaction || - ref_transaction_update(transaction, ref.buf, sha1, - null_sha1, 0, !forcing, msg, &err) || + ref_transaction_update(transaction, ref.buf, + sha1, forcing ? NULL : null_sha1, + 0, msg, &err) || ref_transaction_commit(transaction, &err)) die("%s", err.buf); ref_transaction_free(transaction); diff --git a/builtin/add.c b/builtin/add.c index 1074e32349..3390933d68 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -19,7 +19,7 @@ #include "argv-array.h" static const char * const builtin_add_usage[] = { - N_("git add [options] [--] <pathspec>..."), + N_("git add [<options>] [--] <pathspec>..."), NULL }; static int patch_interactive, add_interactive, edit_interactive; diff --git a/builtin/apply.c b/builtin/apply.c index c484b53f55..65b97eee69 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -51,11 +51,12 @@ static int apply_verbosely; static int allow_overlap; static int no_add; static int threeway; +static int unsafe_paths; static const char *fake_ancestor; static int line_termination = '\n'; static unsigned int p_context = UINT_MAX; static const char * const apply_usage[] = { - N_("git apply [options] [<patch>...]"), + N_("git apply [<options>] [<patch>...]"), NULL }; @@ -1600,6 +1601,9 @@ static int parse_fragment(const char *line, unsigned long size, if (!deleted && !added) leading++; trailing++; + if (!apply_in_reverse && + ws_error_action == correct_ws_error) + check_whitespace(line, len, patch->ws_rule); break; case '-': if (apply_in_reverse && @@ -3221,7 +3225,7 @@ static int load_patch_target(struct strbuf *buf, const char *name, unsigned expected_mode) { - if (cached) { + if (cached || check_index) { if (read_file_or_gitlink(ce, buf)) return error(_("read of %s failed"), name); } else if (name) { @@ -3230,6 +3234,8 @@ static int load_patch_target(struct strbuf *buf, return read_file_or_gitlink(ce, buf); else return SUBMODULE_PATCH_WITHOUT_INDEX; + } else if (has_symlink_leading_path(name, strlen(name))) { + return error(_("reading from '%s' beyond a symbolic link"), name); } else { if (read_old_data(st, name, buf)) return error(_("read of %s failed"), name); @@ -3570,6 +3576,121 @@ static int check_to_create(const char *new_name, int ok_if_exists) } /* + * We need to keep track of how symlinks in the preimage are + * manipulated by the patches. A patch to add a/b/c where a/b + * is a symlink should not be allowed to affect the directory + * the symlink points at, but if the same patch removes a/b, + * it is perfectly fine, as the patch removes a/b to make room + * to create a directory a/b so that a/b/c can be created. + */ +static struct string_list symlink_changes; +#define SYMLINK_GOES_AWAY 01 +#define SYMLINK_IN_RESULT 02 + +static uintptr_t register_symlink_changes(const char *path, uintptr_t what) +{ + struct string_list_item *ent; + + ent = string_list_lookup(&symlink_changes, path); + if (!ent) { + ent = string_list_insert(&symlink_changes, path); + ent->util = (void *)0; + } + ent->util = (void *)(what | ((uintptr_t)ent->util)); + return (uintptr_t)ent->util; +} + +static uintptr_t check_symlink_changes(const char *path) +{ + struct string_list_item *ent; + + ent = string_list_lookup(&symlink_changes, path); + if (!ent) + return 0; + return (uintptr_t)ent->util; +} + +static void prepare_symlink_changes(struct patch *patch) +{ + for ( ; patch; patch = patch->next) { + if ((patch->old_name && S_ISLNK(patch->old_mode)) && + (patch->is_rename || patch->is_delete)) + /* the symlink at patch->old_name is removed */ + register_symlink_changes(patch->old_name, SYMLINK_GOES_AWAY); + + if (patch->new_name && S_ISLNK(patch->new_mode)) + /* the symlink at patch->new_name is created or remains */ + register_symlink_changes(patch->new_name, SYMLINK_IN_RESULT); + } +} + +static int path_is_beyond_symlink_1(struct strbuf *name) +{ + do { + unsigned int change; + + while (--name->len && name->buf[name->len] != '/') + ; /* scan backwards */ + if (!name->len) + break; + name->buf[name->len] = '\0'; + change = check_symlink_changes(name->buf); + if (change & SYMLINK_IN_RESULT) + return 1; + if (change & SYMLINK_GOES_AWAY) + /* + * This cannot be "return 0", because we may + * see a new one created at a higher level. + */ + continue; + + /* otherwise, check the preimage */ + if (check_index) { + struct cache_entry *ce; + + ce = cache_file_exists(name->buf, name->len, ignore_case); + if (ce && S_ISLNK(ce->ce_mode)) + return 1; + } else { + struct stat st; + if (!lstat(name->buf, &st) && S_ISLNK(st.st_mode)) + return 1; + } + } while (1); + return 0; +} + +static int path_is_beyond_symlink(const char *name_) +{ + int ret; + struct strbuf name = STRBUF_INIT; + + assert(*name_ != '\0'); + strbuf_addstr(&name, name_); + ret = path_is_beyond_symlink_1(&name); + strbuf_release(&name); + + return ret; +} + +static void die_on_unsafe_path(struct patch *patch) +{ + const char *old_name = NULL; + const char *new_name = NULL; + if (patch->is_delete) + old_name = patch->old_name; + else if (!patch->is_new && !patch->is_copy) + old_name = patch->old_name; + if (!patch->is_delete) + new_name = patch->new_name; + + if (old_name && !verify_path(old_name)) + die(_("invalid path '%s'"), old_name); + if (new_name && !verify_path(new_name)) + die(_("invalid path '%s'"), new_name); +} + +/* * Check and apply the patch in-core; leave the result in patch->result * for the caller to write it out to the final destination. */ @@ -3656,6 +3777,22 @@ static int check_patch(struct patch *patch) } } + if (!unsafe_paths) + die_on_unsafe_path(patch); + + /* + * An attempt to read from or delete a path that is beyond a + * symbolic link will be prevented by load_patch_target() that + * is called at the beginning of apply_data() so we do not + * have to worry about a patch marked with "is_delete" bit + * here. We however need to make sure that the patch result + * is not deposited to a path that is beyond a symbolic link + * here. + */ + if (!patch->is_delete && path_is_beyond_symlink(patch->new_name)) + return error(_("affected file '%s' is beyond a symbolic link"), + patch->new_name); + if (apply_data(patch, &st, ce) < 0) return error(_("%s: patch does not apply"), name); patch->rejected = 0; @@ -3666,6 +3803,7 @@ static int check_patch_list(struct patch *patch) { int err = 0; + prepare_symlink_changes(patch); prepare_fn_table(patch); while (patch) { if (apply_verbosely) @@ -4404,6 +4542,8 @@ int cmd_apply(int argc, const char **argv, const char *prefix_) N_("make sure the patch is applicable to the current index")), OPT_BOOL(0, "cached", &cached, N_("apply a patch without touching the working tree")), + OPT_BOOL(0, "unsafe-paths", &unsafe_paths, + N_("accept a patch that touches outside the working area")), OPT_BOOL(0, "apply", &force_apply, N_("also apply the patch (use with --stat/--summary/--check)")), OPT_BOOL('3', "3way", &threeway, @@ -4476,6 +4616,9 @@ int cmd_apply(int argc, const char **argv, const char *prefix_) die(_("--cached outside a repository")); check_index = 1; } + if (check_index) + unsafe_paths = 0; + for (i = 0; i < argc; i++) { const char *arg = argv[i]; int fd; diff --git a/builtin/blame.c b/builtin/blame.c index 2b1f9dd6cd..06484c2e0e 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -27,12 +27,12 @@ #include "line-range.h" #include "line-log.h" -static char blame_usage[] = N_("git blame [options] [rev-opts] [rev] [--] file"); +static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] file"); static const char *blame_opt_usage[] = { blame_usage, "", - N_("[rev-opts] are documented in git-rev-list(1)"), + N_("<rev-opts> are documented in git-rev-list(1)"), NULL }; diff --git a/builtin/branch.c b/builtin/branch.c index dc6f0b266c..1d150378e9 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -21,10 +21,10 @@ #include "wt-status.h" static const char * const builtin_branch_usage[] = { - N_("git branch [options] [-r | -a] [--merged | --no-merged]"), - N_("git branch [options] [-l] [-f] <branchname> [<start-point>]"), - N_("git branch [options] [-r] (-d | -D) <branchname>..."), - N_("git branch [options] (-m | -M) [<oldbranch>] <newbranch>"), + N_("git branch [<options>] [-r | -a] [--merged | --no-merged]"), + N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"), + N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."), + N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"), NULL }; @@ -589,9 +589,16 @@ static char *get_head_description(void) 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 if (state.detached_from) { + /* TRANSLATORS: make sure these match _("HEAD detached at ") + and _("HEAD detached from ") in wt-status.c */ + if (state.detached_at) + strbuf_addf(&desc, _("(HEAD detached at %s)"), + state.detached_from); + else + strbuf_addf(&desc, _("(HEAD detached from %s)"), + state.detached_from); + } else strbuf_addstr(&desc, _("(no branch)")); free(state.branch); diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 31b133b357..df99df4db1 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -323,8 +323,8 @@ static int batch_objects(struct batch_options *opt) } static const char * const cat_file_usage[] = { - N_("git cat-file (-t|-s|-e|-p|<type>|--textconv) <object>"), - N_("git cat-file (--batch|--batch-check) < <list_of_objects>"), + N_("git cat-file (-t | -s | -e | -p | <type> | --textconv) <object>"), + N_("git cat-file (--batch | --batch-check) < <list-of-objects>"), NULL }; diff --git a/builtin/check-attr.c b/builtin/check-attr.c index 5600ec3f61..21d2bedcc9 100644 --- a/builtin/check-attr.c +++ b/builtin/check-attr.c @@ -8,8 +8,8 @@ static int all_attrs; static int cached_attrs; static int stdin_paths; static const char * const check_attr_usage[] = { -N_("git check-attr [-a | --all | attr...] [--] pathname..."), -N_("git check-attr --stdin [-z] [-a | --all | attr...] < <list-of-paths>"), +N_("git check-attr [-a | --all | <attr>...] [--] <pathname>..."), +N_("git check-attr --stdin [-z] [-a | --all | <attr>...] < <list-of-paths>"), NULL }; diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c index 594463a11b..dc8d97c56c 100644 --- a/builtin/check-ignore.c +++ b/builtin/check-ignore.c @@ -7,8 +7,8 @@ static int quiet, verbose, stdin_paths, show_non_matching, no_index; static const char * const check_ignore_usage[] = { -"git check-ignore [options] pathname...", -"git check-ignore [options] --stdin < <list-of-paths>", +"git check-ignore [<options>] <pathname>...", +"git check-ignore [<options>] --stdin < <list-of-paths>", NULL }; diff --git a/builtin/check-mailmap.c b/builtin/check-mailmap.c index 8f4d809bd8..eaaea546d3 100644 --- a/builtin/check-mailmap.c +++ b/builtin/check-mailmap.c @@ -5,7 +5,7 @@ static int use_stdin; static const char * const check_mailmap_usage[] = { -N_("git check-mailmap [options] <contact>..."), +N_("git check-mailmap [<options>] <contact>..."), NULL }; diff --git a/builtin/check-ref-format.c b/builtin/check-ref-format.c index 28a7320271..fd915d5984 100644 --- a/builtin/check-ref-format.c +++ b/builtin/check-ref-format.c @@ -8,7 +8,7 @@ #include "strbuf.h" static const char builtin_check_ref_format_usage[] = -"git check-ref-format [--normalize] [options] <refname>\n" +"git check-ref-format [--normalize] [<options>] <refname>\n" " or: git check-ref-format --branch <branchname-shorthand>"; /* diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c index 031780f49e..9ca2da1583 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -123,7 +123,7 @@ static void checkout_all(const char *prefix, int prefix_length) } static const char * const builtin_checkout_index_usage[] = { - N_("git checkout-index [options] [--] [<file>...]"), + N_("git checkout-index [<options>] [--] [<file>...]"), NULL }; diff --git a/builtin/checkout.c b/builtin/checkout.c index 52d6cbb0a8..3e141fc149 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -22,8 +22,8 @@ #include "argv-array.h" static const char * const checkout_usage[] = { - N_("git checkout [options] <branch>"), - N_("git checkout [options] [<branch>] -- <file>..."), + N_("git checkout [<options>] <branch>"), + N_("git checkout [<options>] [<branch>] -- <file>..."), NULL, }; @@ -746,7 +746,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"), + " git branch <new-branch-name> %s\n\n"), find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); } @@ -1127,7 +1127,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "ignore-skip-worktree-bits", &opts.ignore_skipworktree, N_("do not limit pathspecs to sparse entries only")), OPT_HIDDEN_BOOL(0, "guess", &dwim_new_local_branch, - N_("second guess 'git checkout no-such-branch'")), + N_("second guess 'git checkout <no-such-branch>'")), OPT_END(), }; diff --git a/builtin/clean.c b/builtin/clean.c index 7e7fdcfe54..98c103fa8b 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -754,7 +754,8 @@ static int ask_each_cmd(void) /* Ctrl-D should stop removing files */ if (!eof) { qname = quote_path_relative(item->string, NULL, &buf); - printf(_("remove %s? "), qname); + /* TRANSLATORS: Make sure to keep [y/N] as is */ + printf(_("Remove %s [y/N]? "), qname); if (strbuf_getline(&confirm, stdin, '\n') != EOF) { strbuf_trim(&confirm); } else { diff --git a/builtin/clone.c b/builtin/clone.c index 316c75d0b3..957246723e 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -34,7 +34,7 @@ * */ static const char * const builtin_clone_usage[] = { - N_("git clone [options] [--] <repo> [<dir>]"), + N_("git clone [<options>] [--] <repo> [<dir>]"), NULL }; diff --git a/builtin/column.c b/builtin/column.c index 75818520e1..449413c8a8 100644 --- a/builtin/column.c +++ b/builtin/column.c @@ -6,7 +6,7 @@ #include "column.h" static const char * const builtin_column_usage[] = { - N_("git column [options]"), + N_("git column [<options>]"), NULL }; static unsigned int colopts; diff --git a/builtin/commit.c b/builtin/commit.c index 714638c5d6..961e467242 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -34,12 +34,12 @@ #include "mailmap.h" static const char * const builtin_commit_usage[] = { - N_("git commit [options] [--] <pathspec>..."), + N_("git commit [<options>] [--] <pathspec>..."), NULL }; static const char * const builtin_status_usage[] = { - N_("git status [options] [--] <pathspec>..."), + N_("git status [<options>] [--] <pathspec>..."), NULL }; @@ -1766,8 +1766,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (!transaction || ref_transaction_update(transaction, "HEAD", sha1, current_head - ? current_head->object.sha1 : NULL, - 0, !!current_head, sb.buf, &err) || + ? current_head->object.sha1 : null_sha1, + 0, sb.buf, &err) || ref_transaction_commit(transaction, &err)) { rollback_index_files(); die("%s", err.buf); diff --git a/builtin/config.c b/builtin/config.c index 15a7bea936..d32c5327e5 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -5,7 +5,7 @@ #include "urlmatch.h" static const char *const builtin_config_usage[] = { - N_("git config [options]"), + N_("git config [<options>]"), NULL }; diff --git a/builtin/describe.c b/builtin/describe.c index 9103193b4f..e00a75b121 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -14,8 +14,8 @@ #define MAX_TAGS (FLAG_BITS - 1) static const char * const describe_usage[] = { - N_("git describe [options] <commit-ish>*"), - N_("git describe [options] --dirty"), + N_("git describe [<options>] [<commit-ish>...]"), + N_("git describe [<options>] --dirty"), NULL }; diff --git a/builtin/diff-files.c b/builtin/diff-files.c index 9200069363..8ed2eb8813 100644 --- a/builtin/diff-files.c +++ b/builtin/diff-files.c @@ -11,7 +11,7 @@ #include "submodule.h" static const char diff_files_usage[] = -"git diff-files [-q] [-0/-1/2/3 |-c|--cc] [<common diff options>] [<path>...]" +"git diff-files [-q] [-0 | -1 | -2 | -3 | -c | --cc] [<common-diff-options>] [<path>...]" COMMON_DIFF_OPTIONS_HELP; int cmd_diff_files(int argc, const char **argv, const char *prefix) diff --git a/builtin/diff-index.c b/builtin/diff-index.c index ce15b23042..d979824f93 100644 --- a/builtin/diff-index.c +++ b/builtin/diff-index.c @@ -7,7 +7,7 @@ static const char diff_cache_usage[] = "git diff-index [-m] [--cached] " -"[<common diff options>] <tree-ish> [<path>...]" +"[<common-diff-options>] <tree-ish> [<path>...]" COMMON_DIFF_OPTIONS_HELP; int cmd_diff_index(int argc, const char **argv, const char *prefix) diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c index 1c4ad6223e..12b683d021 100644 --- a/builtin/diff-tree.c +++ b/builtin/diff-tree.c @@ -82,7 +82,7 @@ static int diff_tree_stdin(char *line) static const char diff_tree_usage[] = "git diff-tree [--stdin] [-m] [-c] [--cc] [-s] [-v] [--pretty] [-t] [-r] [--root] " -"[<common diff options>] <tree-ish> [<tree-ish>] [<path>...]\n" +"[<common-diff-options>] <tree-ish> [<tree-ish>] [<path>...]\n" " -r diff recursively\n" " --root include the initial commit as diff against /dev/null\n" COMMON_DIFF_OPTIONS_HELP; diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index 1262b405f8..4a6b340ab6 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -6,7 +6,7 @@ #include "sha1-array.h" static const char fetch_pack_usage[] = -"git fetch-pack [--all] [--stdin] [--quiet|-q] [--keep|-k] [--thin] " +"git fetch-pack [--all] [--stdin] [--quiet | -q] [--keep | -k] [--thin] " "[--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] " "[--no-progress] [--diag-url] [-v] [<host>:]<directory> [<refs>...]"; diff --git a/builtin/fetch.c b/builtin/fetch.c index 75a55e590b..f9512652cf 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -415,8 +415,10 @@ static int s_update_ref(const char *action, transaction = ref_transaction_begin(&err); if (!transaction || - ref_transaction_update(transaction, ref->name, ref->new_sha1, - ref->old_sha1, 0, check_old, msg, &err)) + ref_transaction_update(transaction, ref->name, + ref->new_sha1, + check_old ? ref->old_sha1 : NULL, + 0, msg, &err)) goto fail; ret = ref_transaction_commit(transaction, &err); diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index af7919e51e..1d962dc569 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -10,7 +10,7 @@ #include "gpg-interface.h" static const char * const fmt_merge_msg_usage[] = { - N_("git fmt-merge-msg [-m <message>] [--log[=<n>]|--no-log] [--file <file>]"), + N_("git fmt-merge-msg [-m <message>] [--log[=<n>] | --no-log] [--file <file>]"), NULL }; diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 008513c2f1..83f9cf9163 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -1060,7 +1060,7 @@ static int opt_parse_sort(const struct option *opt, const char *arg, int unset) } static char const * const for_each_ref_usage[] = { - N_("git for-each-ref [options] [<pattern>]"), + N_("git for-each-ref [<options>] [<pattern>]"), NULL }; diff --git a/builtin/fsck.c b/builtin/fsck.c index a27515aeaa..0c757862e8 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -600,7 +600,7 @@ static int fsck_cache_tree(struct cache_tree *it) } static char const * const fsck_usage[] = { - N_("git fsck [options] [<object>...]"), + N_("git fsck [<options>] [<object>...]"), NULL }; diff --git a/builtin/gc.c b/builtin/gc.c index 005adbebea..5c634afc00 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -21,7 +21,7 @@ #define FAILED_RUN "failed to run %s" static const char * const builtin_gc_usage[] = { - N_("git gc [options]"), + N_("git gc [<options>]"), NULL }; diff --git a/builtin/grep.c b/builtin/grep.c index 4063882f06..335f25d0ad 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -20,7 +20,7 @@ #include "pathspec.h" static char const * const grep_usage[] = { - N_("git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]"), + N_("git grep [<options>] [-e] <pattern> [<rev>...] [[--] <path>...]"), NULL }; @@ -641,7 +641,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "untracked", &untracked, N_("search in both tracked and untracked files")), OPT_SET_INT(0, "exclude-standard", &opt_exclude, - N_("search also in ignored files"), 1), + N_("ignore files specified via '.gitignore'"), 1), OPT_GROUP(""), OPT_BOOL('v', "invert-match", &opt.invert, N_("show non-matching lines")), diff --git a/builtin/hash-object.c b/builtin/hash-object.c index 6158363318..207b90c7b1 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -79,7 +79,7 @@ static void hash_stdin_paths(const char *type, int no_filters, unsigned flags, int cmd_hash_object(int argc, const char **argv, const char *prefix) { static const char * const hash_object_usage[] = { - N_("git hash-object [-t <type>] [-w] [--path=<file>|--no-filters] [--stdin] [--] <file>..."), + N_("git hash-object [-t <type>] [-w] [--path=<file> | --no-filters] [--stdin] [--] <file>..."), N_("git hash-object --stdin-paths < <list-of-paths>"), NULL }; diff --git a/builtin/help.c b/builtin/help.c index e78c135e01..3422e73079 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -49,7 +49,7 @@ static struct option builtin_help_options[] = { }; static const char * const builtin_help_usage[] = { - N_("git help [--all] [--guides] [--man|--web|--info] [command]"), + N_("git help [--all] [--guides] [--man | --web | --info] [<command>]"), NULL }; @@ -171,7 +171,7 @@ static void exec_man_cmd(const char *cmd, const char *page) { struct strbuf shell_cmd = STRBUF_INIT; strbuf_addf(&shell_cmd, "%s %s", cmd, page); - execl("/bin/sh", "sh", "-c", shell_cmd.buf, (char *)NULL); + execl(SHELL_PATH, SHELL_PATH, "-c", shell_cmd.buf, (char *)NULL); warning(_("failed to exec '%s': %s"), cmd, strerror(errno)); } @@ -456,7 +456,7 @@ static void list_common_guides_help(void) int cmd_help(int argc, const char **argv, const char *prefix) { int nongit; - const char *alias; + char *alias; enum help_format parsed_help_format; argc = parse_options(argc, argv, prefix, builtin_help_options, @@ -499,6 +499,7 @@ int cmd_help(int argc, const char **argv, const char *prefix) alias = alias_lookup(argv[0]); if (alias && !is_git_command(argv[0])) { printf_ln(_("`git %s' is aliased to `%s'"), argv[0], alias); + free(alias); return 0; } diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 4632117671..cf654df09b 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -1204,7 +1204,6 @@ static int write_compressed(struct sha1file *f, void *in, unsigned int size) int status; unsigned char outbuf[4096]; - memset(&stream, 0, sizeof(stream)); git_deflate_init(&stream, zlib_compression_level); stream.next_in = in; stream.avail_in = size; diff --git a/builtin/init-db.c b/builtin/init-db.c index 9966522b4a..6723d39c3b 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -472,7 +472,7 @@ static int shared_callback(const struct option *opt, const char *arg, int unset) } static const char *const init_db_usage[] = { - N_("git init [-q | --quiet] [--bare] [--template=<template-directory>] [--shared[=<permissions>]] [directory]"), + N_("git init [-q | --quiet] [--bare] [--template=<template-directory>] [--shared[=<permissions>]] [<directory>]"), NULL }; diff --git a/builtin/log.c b/builtin/log.c index a131992c52..dd8f3fcfc4 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -39,7 +39,7 @@ static const char *fmt_pretty; static const char * const builtin_log_usage[] = { N_("git log [<options>] [<revision range>] [[--] <path>...]"), - N_("git show [options] <object>..."), + N_("git show [<options>] <object>..."), NULL }; @@ -1023,7 +1023,7 @@ static const char *set_outdir(const char *prefix, const char *output_directory) } static const char * const builtin_format_patch_usage[] = { - N_("git format-patch [options] [<since> | <revision range>]"), + N_("git format-patch [<options>] [<since> | <revision-range>]"), NULL }; diff --git a/builtin/ls-files.c b/builtin/ls-files.c index 99cee20fb0..914054d367 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -398,7 +398,7 @@ int report_path_error(const char *ps_matched, } static const char * const ls_files_usage[] = { - N_("git ls-files [options] [<file>...]"), + N_("git ls-files [<options>] [<file>...]"), NULL }; diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index b2a4b92992..4554dbc8a9 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -5,7 +5,7 @@ static const char ls_remote_usage[] = "git ls-remote [--heads] [--tags] [-u <exec> | --upload-pack <exec>]\n" -" [-q|--quiet] [--exit-code] [--get-url] [<repository> [<refs>...]]"; +" [-q | --quiet] [--exit-code] [--get-url] [<repository> [<refs>...]]"; /* * Is there one among the list of patterns that match the tail part diff --git a/builtin/mailinfo.c b/builtin/mailinfo.c index c8a47c173d..999a5250fb 100644 --- a/builtin/mailinfo.c +++ b/builtin/mailinfo.c @@ -1031,7 +1031,7 @@ static int git_mailinfo_config(const char *var, const char *value, void *unused) } static const char mailinfo_usage[] = - "git mailinfo [-k|-b] [-m | --message-id] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] msg patch < mail >info"; + "git mailinfo [-k | -b] [-m | --message-id] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] <msg> <patch> < mail >info"; int cmd_mailinfo(int argc, const char **argv, const char *prefix) { diff --git a/builtin/merge-base.c b/builtin/merge-base.c index fdebef6fa1..08a8217890 100644 --- a/builtin/merge-base.c +++ b/builtin/merge-base.c @@ -26,8 +26,8 @@ static int show_merge_base(struct commit **rev, int rev_nr, int show_all) } static const char * const merge_base_usage[] = { - N_("git merge-base [-a|--all] <commit> <commit>..."), - N_("git merge-base [-a|--all] --octopus <commit>..."), + N_("git merge-base [-a | --all] <commit> <commit>..."), + N_("git merge-base [-a | --all] --octopus <commit>..."), N_("git merge-base --independent <commit>..."), N_("git merge-base --is-ancestor <commit> <commit>"), N_("git merge-base --fork-point <ref> [<commit>]"), diff --git a/builtin/merge-file.c b/builtin/merge-file.c index 232b76857c..ea8093f676 100644 --- a/builtin/merge-file.c +++ b/builtin/merge-file.c @@ -5,7 +5,7 @@ #include "parse-options.h" static const char *const merge_file_usage[] = { - N_("git merge-file [options] [-L name1 [-L orig [-L name2]]] file1 orig_file file2"), + N_("git merge-file [<options>] [-L <name1> [-L <orig> [-L <name2>]]] <file1> <orig-file> <file2>"), NULL }; @@ -42,7 +42,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) N_("for conflicts, use this marker size")), OPT__QUIET(&quiet, N_("do not warn about conflicts")), OPT_CALLBACK('L', NULL, names, N_("name"), - N_("set labels for file1/orig_file/file2"), &label_cb), + N_("set labels for file1/orig-file/file2"), &label_cb), OPT_END(), }; diff --git a/builtin/merge-index.c b/builtin/merge-index.c index b416d92849..1a1eafa6fd 100644 --- a/builtin/merge-index.c +++ b/builtin/merge-index.c @@ -75,7 +75,7 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix) signal(SIGCHLD, SIG_DFL); if (argc < 3) - usage("git merge-index [-o] [-q] <merge-program> (-a | [--] <filename>*)"); + usage("git merge-index [-o] [-q] <merge-program> (-a | [--] [<filename>...])"); read_cache(); diff --git a/builtin/merge.c b/builtin/merge.c index c638fd5a9a..3b0f8f96d4 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -42,8 +42,8 @@ struct strategy { }; static const char * const builtin_merge_usage[] = { - N_("git merge [options] [<commit>...]"), - N_("git merge [options] <msg> HEAD <commit>"), + N_("git merge [<options>] [<commit>...]"), + N_("git merge [<options>] <msg> HEAD <commit>"), N_("git merge --abort"), NULL }; diff --git a/builtin/mv.c b/builtin/mv.c index 563d05ba1a..d1d43168ae 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -12,7 +12,7 @@ #include "submodule.h" static const char * const builtin_mv_usage[] = { - N_("git mv [options] <source>... <destination>"), + N_("git mv [<options>] <source>... <destination>"), NULL }; diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 3c8f319be6..9736d4452f 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -252,9 +252,9 @@ static void show_name(const struct object *obj, } static char const * const name_rev_usage[] = { - N_("git name-rev [options] <commit>..."), - N_("git name-rev [options] --all"), - N_("git name-rev [options] --stdin"), + N_("git name-rev [<options>] <commit>..."), + N_("git name-rev [<options>] --all"), + N_("git name-rev [<options>] --stdin"), NULL }; diff --git a/builtin/notes.c b/builtin/notes.c index a9f37d0456..63f95fc554 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -21,18 +21,18 @@ #include "notes-utils.h" static const char * const git_notes_usage[] = { - N_("git notes [--ref <notes_ref>] [list [<object>]]"), - N_("git notes [--ref <notes_ref>] add [-f] [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"), - N_("git notes [--ref <notes_ref>] copy [-f] <from-object> <to-object>"), - N_("git notes [--ref <notes_ref>] append [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"), - N_("git notes [--ref <notes_ref>] edit [--allow-empty] [<object>]"), - N_("git notes [--ref <notes_ref>] show [<object>]"), - N_("git notes [--ref <notes_ref>] merge [-v | -q] [-s <strategy> ] <notes_ref>"), + N_("git notes [--ref <notes-ref>] [list [<object>]]"), + N_("git notes [--ref <notes-ref>] add [-f] [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"), + N_("git notes [--ref <notes-ref>] copy [-f] <from-object> <to-object>"), + N_("git notes [--ref <notes-ref>] append [--allow-empty] [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]"), + N_("git notes [--ref <notes-ref>] edit [--allow-empty] [<object>]"), + N_("git notes [--ref <notes-ref>] show [<object>]"), + N_("git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>"), N_("git notes merge --commit [-v | -q]"), N_("git notes merge --abort [-v | -q]"), - N_("git notes [--ref <notes_ref>] remove [<object>...]"), - N_("git notes [--ref <notes_ref>] prune [-n | -v]"), - N_("git notes [--ref <notes_ref>] get-ref"), + N_("git notes [--ref <notes-ref>] remove [<object>...]"), + N_("git notes [--ref <notes-ref>] prune [-n | -v]"), + N_("git notes [--ref <notes-ref>] get-ref"), NULL }; @@ -68,7 +68,7 @@ static const char * const git_notes_show_usage[] = { }; static const char * const git_notes_merge_usage[] = { - N_("git notes merge [<options>] <notes_ref>"), + N_("git notes merge [<options>] <notes-ref>"), N_("git notes merge --commit [<options>]"), N_("git notes merge --abort [<options>]"), NULL @@ -951,7 +951,7 @@ int cmd_notes(int argc, const char **argv, const char *prefix) const char *override_notes_ref = NULL; struct option options[] = { OPT_STRING(0, "ref", &override_notes_ref, N_("notes-ref"), - N_("use notes from <notes_ref>")), + N_("use notes from <notes-ref>")), OPT_END() }; diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index d8165878e1..c3a75166bd 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -125,7 +125,6 @@ static unsigned long do_compress(void **pptr, unsigned long size) void *in, *out; unsigned long maxsize; - memset(&stream, 0, sizeof(stream)); git_deflate_init(&stream, pack_compression_level); maxsize = git_deflate_bound(&stream, size); @@ -153,7 +152,6 @@ static unsigned long write_large_blob_data(struct git_istream *st, struct sha1fi unsigned char obuf[1024 * 16]; unsigned long olen = 0; - memset(&stream, 0, sizeof(stream)); git_deflate_init(&stream, pack_compression_level); for (;;) { diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c index 649c3aaa93..d0532f66b1 100644 --- a/builtin/pack-redundant.c +++ b/builtin/pack-redundant.c @@ -11,7 +11,7 @@ #define BLKSIZE 512 static const char pack_redundant_usage[] = -"git pack-redundant [ --verbose ] [ --alt-odb ] < --all | <.pack filename> ...>"; +"git pack-redundant [--verbose] [--alt-odb] (--all | <filename.pack>...)"; static int load_all_packs, verbose, alt_odb; diff --git a/builtin/pack-refs.c b/builtin/pack-refs.c index b20b1ec4c1..39f9a55d16 100644 --- a/builtin/pack-refs.c +++ b/builtin/pack-refs.c @@ -3,7 +3,7 @@ #include "refs.h" static char const * const pack_refs_usage[] = { - N_("git pack-refs [options]"), + N_("git pack-refs [<options>]"), NULL }; diff --git a/builtin/prune-packed.c b/builtin/prune-packed.c index f24a2c2bdc..7cf900ea07 100644 --- a/builtin/prune-packed.c +++ b/builtin/prune-packed.c @@ -4,7 +4,7 @@ #include "parse-options.h" static const char * const prune_packed_usage[] = { - N_("git prune-packed [-n|--dry-run] [-q|--quiet]"), + N_("git prune-packed [-n | --dry-run] [-q | --quiet]"), NULL }; diff --git a/builtin/push.c b/builtin/push.c index 12f5e69393..fc771a9f6f 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -487,6 +487,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) int flags = 0; int tags = 0; int rc; + int atomic = 0; const char *repo = NULL; /* default repository */ struct option options[] = { OPT__VERBOSITY(&verbosity), @@ -518,6 +519,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant tags"), TRANSPORT_PUSH_FOLLOW_TAGS), OPT_BIT(0, "signed", &flags, N_("GPG sign the push"), TRANSPORT_PUSH_CERT), + OPT_BOOL(0, "atomic", &atomic, N_("request atomic transaction on remote side")), OPT_END() }; @@ -533,6 +535,9 @@ int cmd_push(int argc, const char **argv, const char *prefix) if (tags) add_refspec("refs/tags/*"); + if (atomic) + flags |= TRANSPORT_PUSH_ATOMIC; + if (argc > 0) { repo = argv[0]; set_refspecs(argv + 1, argc - 1, repo); diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 8266c1fccf..70e9ce5f96 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -38,9 +38,11 @@ static int receive_fsck_objects = -1; static int transfer_fsck_objects = -1; static int receive_unpack_limit = -1; static int transfer_unpack_limit = -1; +static int advertise_atomic_push = 1; static int unpack_limit = 100; static int report_status; static int use_sideband; +static int use_atomic; static int quiet; static int prefer_ofs_delta = 1; static int auto_update_server_info; @@ -67,6 +69,7 @@ static const char *NONCE_SLOP = "SLOP"; static const char *nonce_status; static long nonce_stamp_slop; static unsigned long nonce_stamp_slop_limit; +static struct ref_transaction *transaction; static enum deny_action parse_deny_action(const char *var, const char *value) { @@ -160,6 +163,11 @@ static int receive_pack_config(const char *var, const char *value, void *cb) return 0; } + if (strcmp(var, "receive.advertiseatomic") == 0) { + advertise_atomic_push = git_config_bool(var, value); + return 0; + } + return git_default_config(var, value, cb); } @@ -175,6 +183,8 @@ static void show_ref(const char *path, const unsigned char *sha1) strbuf_addstr(&cap, "report-status delete-refs side-band-64k quiet"); + if (advertise_atomic_push) + strbuf_addstr(&cap, " atomic"); if (prefer_ofs_delta) strbuf_addstr(&cap, " ofs-delta"); if (push_cert_nonce) @@ -733,7 +743,9 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si) return 0; } -static const char *update_worktree(unsigned char *sha1) +static const char *push_to_deploy(unsigned char *sha1, + struct argv_array *env, + const char *work_tree) { const char *update_refresh[] = { "update-index", "-q", "--ignore-submodules", "--refresh", NULL @@ -748,69 +760,87 @@ static const char *update_worktree(unsigned char *sha1) const char *read_tree[] = { "read-tree", "-u", "-m", NULL, NULL }; - const char *work_tree = git_work_tree_cfg ? git_work_tree_cfg : ".."; - struct argv_array env = ARGV_ARRAY_INIT; struct child_process child = CHILD_PROCESS_INIT; - if (is_bare_repository()) - return "denyCurrentBranch = updateInstead needs a worktree"; - - argv_array_pushf(&env, "GIT_DIR=%s", absolute_path(get_git_dir())); - child.argv = update_refresh; - child.env = env.argv; + child.env = env->argv; child.dir = work_tree; child.no_stdin = 1; child.stdout_to_stderr = 1; child.git_cmd = 1; - if (run_command(&child)) { - argv_array_clear(&env); + if (run_command(&child)) return "Up-to-date check failed"; - } /* run_command() does not clean up completely; reinitialize */ child_process_init(&child); child.argv = diff_files; - child.env = env.argv; + child.env = env->argv; child.dir = work_tree; child.no_stdin = 1; child.stdout_to_stderr = 1; child.git_cmd = 1; - if (run_command(&child)) { - argv_array_clear(&env); + if (run_command(&child)) return "Working directory has unstaged changes"; - } child_process_init(&child); child.argv = diff_index; - child.env = env.argv; + child.env = env->argv; child.no_stdin = 1; child.no_stdout = 1; child.stdout_to_stderr = 0; child.git_cmd = 1; - if (run_command(&child)) { - argv_array_clear(&env); + if (run_command(&child)) return "Working directory has staged changes"; - } read_tree[3] = sha1_to_hex(sha1); child_process_init(&child); child.argv = read_tree; - child.env = env.argv; + child.env = env->argv; child.dir = work_tree; child.no_stdin = 1; child.no_stdout = 1; child.stdout_to_stderr = 0; child.git_cmd = 1; - if (run_command(&child)) { - argv_array_clear(&env); + if (run_command(&child)) return "Could not update working tree to new HEAD"; - } - argv_array_clear(&env); return NULL; } +static const char *push_to_checkout_hook = "push-to-checkout"; + +static const char *push_to_checkout(unsigned char *sha1, + struct argv_array *env, + const char *work_tree) +{ + argv_array_pushf(env, "GIT_WORK_TREE=%s", absolute_path(work_tree)); + if (run_hook_le(env->argv, push_to_checkout_hook, + sha1_to_hex(sha1), NULL)) + return "push-to-checkout hook declined"; + else + return NULL; +} + +static const char *update_worktree(unsigned char *sha1) +{ + const char *retval; + const char *work_tree = git_work_tree_cfg ? git_work_tree_cfg : ".."; + struct argv_array env = ARGV_ARRAY_INIT; + + if (is_bare_repository()) + return "denyCurrentBranch = updateInstead needs a worktree"; + + argv_array_pushf(&env, "GIT_DIR=%s", absolute_path(get_git_dir())); + + if (!find_hook(push_to_checkout_hook)) + retval = push_to_deploy(sha1, &env, work_tree); + else + retval = push_to_checkout(sha1, &env, work_tree); + + argv_array_clear(&env); + return retval; +} + static const char *update(struct command *cmd, struct shallow_info *si) { const char *name = cmd->ref_name; @@ -910,6 +940,7 @@ static const char *update(struct command *cmd, struct shallow_info *si) } if (is_null_sha1(new_sha1)) { + struct strbuf err = STRBUF_INIT; if (!parse_object(old_sha1)) { old_sha1 = NULL; if (ref_exists(name)) { @@ -919,35 +950,35 @@ static const char *update(struct command *cmd, struct shallow_info *si) cmd->did_not_exist = 1; } } - if (delete_ref(namespaced_name, old_sha1, 0)) { - rp_error("failed to delete %s", name); + if (ref_transaction_delete(transaction, + namespaced_name, + old_sha1, + 0, "push", &err)) { + rp_error("%s", err.buf); + strbuf_release(&err); return "failed to delete"; } + strbuf_release(&err); return NULL; /* good */ } else { struct strbuf err = STRBUF_INIT; - struct ref_transaction *transaction; - if (shallow_update && si->shallow_ref[cmd->index] && update_shallow_ref(cmd, si)) return "shallow error"; - transaction = ref_transaction_begin(&err); - if (!transaction || - ref_transaction_update(transaction, namespaced_name, - new_sha1, old_sha1, 0, 1, "push", - &err) || - ref_transaction_commit(transaction, &err)) { - ref_transaction_free(transaction); - + if (ref_transaction_update(transaction, + namespaced_name, + new_sha1, old_sha1, + 0, "push", + &err)) { rp_error("%s", err.buf); strbuf_release(&err); + return "failed to update ref"; } - - ref_transaction_free(transaction); strbuf_release(&err); + return NULL; /* good */ } } @@ -1131,11 +1162,105 @@ static void reject_updates_to_hidden(struct command *commands) } } +static int should_process_cmd(struct command *cmd) +{ + return !cmd->error_string && !cmd->skip_update; +} + +static void warn_if_skipped_connectivity_check(struct command *commands, + struct shallow_info *si) +{ + struct command *cmd; + int checked_connectivity = 1; + + for (cmd = commands; cmd; cmd = cmd->next) { + if (should_process_cmd(cmd) && si->shallow_ref[cmd->index]) { + error("BUG: connectivity check has not been run on ref %s", + cmd->ref_name); + checked_connectivity = 0; + } + } + if (!checked_connectivity) + die("BUG: connectivity check skipped???"); +} + +static void execute_commands_non_atomic(struct command *commands, + struct shallow_info *si) +{ + struct command *cmd; + struct strbuf err = STRBUF_INIT; + + for (cmd = commands; cmd; cmd = cmd->next) { + if (!should_process_cmd(cmd)) + continue; + + transaction = ref_transaction_begin(&err); + if (!transaction) { + rp_error("%s", err.buf); + strbuf_reset(&err); + cmd->error_string = "transaction failed to start"; + continue; + } + + cmd->error_string = update(cmd, si); + + if (!cmd->error_string + && ref_transaction_commit(transaction, &err)) { + rp_error("%s", err.buf); + strbuf_reset(&err); + cmd->error_string = "failed to update ref"; + } + ref_transaction_free(transaction); + } + strbuf_release(&err); +} + +static void execute_commands_atomic(struct command *commands, + struct shallow_info *si) +{ + struct command *cmd; + struct strbuf err = STRBUF_INIT; + const char *reported_error = "atomic push failure"; + + transaction = ref_transaction_begin(&err); + if (!transaction) { + rp_error("%s", err.buf); + strbuf_reset(&err); + reported_error = "transaction failed to start"; + goto failure; + } + + for (cmd = commands; cmd; cmd = cmd->next) { + if (!should_process_cmd(cmd)) + continue; + + cmd->error_string = update(cmd, si); + + if (cmd->error_string) + goto failure; + } + + if (ref_transaction_commit(transaction, &err)) { + rp_error("%s", err.buf); + reported_error = "atomic transaction failed"; + goto failure; + } + goto cleanup; + +failure: + for (cmd = commands; cmd; cmd = cmd->next) + if (!cmd->error_string) + cmd->error_string = reported_error; + +cleanup: + ref_transaction_free(transaction); + strbuf_release(&err); +} + static void execute_commands(struct command *commands, const char *unpacker_error, struct shallow_info *si) { - int checked_connectivity; struct command *cmd; unsigned char sha1[20]; struct iterate_data data; @@ -1166,27 +1291,13 @@ static void execute_commands(struct command *commands, free(head_name_to_free); head_name = head_name_to_free = resolve_refdup("HEAD", 0, sha1, NULL); - checked_connectivity = 1; - for (cmd = commands; cmd; cmd = cmd->next) { - if (cmd->error_string) - continue; - - if (cmd->skip_update) - continue; - - cmd->error_string = update(cmd, si); - if (shallow_update && !cmd->error_string && - si->shallow_ref[cmd->index]) { - error("BUG: connectivity check has not been run on ref %s", - cmd->ref_name); - checked_connectivity = 0; - } - } + if (use_atomic) + execute_commands_atomic(commands, si); + else + execute_commands_non_atomic(commands, si); - if (shallow_update && !checked_connectivity) - error("BUG: run 'git fsck' for safety.\n" - "If there are errors, try to remove " - "the reported refs above"); + if (shallow_update) + warn_if_skipped_connectivity_check(commands, si); } static struct command **queue_command(struct command **tail, @@ -1268,6 +1379,9 @@ static struct command *read_head_info(struct sha1_array *shallow) use_sideband = LARGE_PACKET_MAX; if (parse_feature_request(feature_list, "quiet")) quiet = 1; + if (advertise_atomic_push + && parse_feature_request(feature_list, "atomic")) + use_atomic = 1; } if (!strcmp(line, "push-cert")) { diff --git a/builtin/reflog.c b/builtin/reflog.c index 2d85d260ca..8182b648b9 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -8,32 +8,24 @@ #include "revision.h" #include "reachable.h" -/* - * reflog expire - */ - +/* NEEDSWORK: switch to using parse_options */ static const char reflog_expire_usage[] = -"git reflog expire [--verbose] [--dry-run] [--stale-fix] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>..."; +"git reflog expire [--expire=<time>] [--expire-unreachable=<time>] [--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] [--verbose] [--all] <refs>..."; static const char reflog_delete_usage[] = -"git reflog delete [--verbose] [--dry-run] [--rewrite] [--updateref] <refs>..."; +"git reflog delete [--rewrite] [--updateref] [--dry-run | -n] [--verbose] <refs>..."; static unsigned long default_reflog_expire; static unsigned long default_reflog_expire_unreachable; struct cmd_reflog_expire_cb { struct rev_info revs; - int dry_run; int stalefix; - int rewrite; - int updateref; - int verbose; unsigned long expire_total; unsigned long expire_unreachable; int recno; }; -struct expire_reflog_cb { - FILE *newlog; +struct expire_reflog_policy_cb { enum { UE_NORMAL, UE_ALWAYS, @@ -41,14 +33,16 @@ struct expire_reflog_cb { } unreachable_expire_kind; struct commit_list *mark_list; unsigned long mark_limit; - struct cmd_reflog_expire_cb *cmd; - unsigned char last_kept_sha1[20]; + struct cmd_reflog_expire_cb cmd; + struct commit *tip_commit; + struct commit_list *tips; }; struct collected_reflog { unsigned char sha1[20]; char reflog[FLEX_ARRAY]; }; + struct collect_reflog_cb { struct collected_reflog **e; int alloc; @@ -220,7 +214,7 @@ static int keep_entry(struct commit **it, unsigned char *sha1) * the expire_limit and queue them back, so that the caller can call * us again to restart the traversal with longer expire_limit. */ -static void mark_reachable(struct expire_reflog_cb *cb) +static void mark_reachable(struct expire_reflog_policy_cb *cb) { struct commit *commit; struct commit_list *pending; @@ -259,7 +253,7 @@ static void mark_reachable(struct expire_reflog_cb *cb) cb->mark_list = leftover; } -static int unreachable(struct expire_reflog_cb *cb, struct commit *commit, unsigned char *sha1) +static int unreachable(struct expire_reflog_policy_cb *cb, struct commit *commit, unsigned char *sha1) { /* * We may or may not have the commit yet - if not, look it @@ -288,55 +282,39 @@ static int unreachable(struct expire_reflog_cb *cb, struct commit *commit, unsig return !(commit->object.flags & REACHABLE); } -static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, - const char *email, unsigned long timestamp, int tz, - const char *message, void *cb_data) +/* + * Return true iff the specified reflog entry should be expired. + */ +static int should_expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, + const char *email, unsigned long timestamp, int tz, + const char *message, void *cb_data) { - struct expire_reflog_cb *cb = cb_data; + struct expire_reflog_policy_cb *cb = cb_data; struct commit *old, *new; - if (timestamp < cb->cmd->expire_total) - goto prune; - - if (cb->cmd->rewrite) - osha1 = cb->last_kept_sha1; + if (timestamp < cb->cmd.expire_total) + return 1; old = new = NULL; - if (cb->cmd->stalefix && + if (cb->cmd.stalefix && (!keep_entry(&old, osha1) || !keep_entry(&new, nsha1))) - goto prune; + return 1; - if (timestamp < cb->cmd->expire_unreachable) { + if (timestamp < cb->cmd.expire_unreachable) { if (cb->unreachable_expire_kind == UE_ALWAYS) - goto prune; + return 1; if (unreachable(cb, old, osha1) || unreachable(cb, new, nsha1)) - goto prune; + return 1; } - if (cb->cmd->recno && --(cb->cmd->recno) == 0) - goto prune; - - if (cb->newlog) { - char sign = (tz < 0) ? '-' : '+'; - int zone = (tz < 0) ? (-tz) : tz; - fprintf(cb->newlog, "%s %s %s %lu %c%04d\t%s", - sha1_to_hex(osha1), sha1_to_hex(nsha1), - email, timestamp, sign, zone, - message); - hashcpy(cb->last_kept_sha1, nsha1); - } - if (cb->cmd->verbose) - printf("keep %s", message); - return 0; - prune: - if (!cb->newlog) - printf("would prune %s", message); - else if (cb->cmd->verbose) - printf("prune %s", message); + if (cb->cmd.recno && --(cb->cmd.recno) == 0) + return 1; + return 0; } -static int push_tip_to_list(const char *refname, const unsigned char *sha1, int flags, void *cb_data) +static int push_tip_to_list(const char *refname, const unsigned char *sha1, + int flags, void *cb_data) { struct commit_list **list = cb_data; struct commit *tip_commit; @@ -349,104 +327,56 @@ static int push_tip_to_list(const char *refname, const unsigned char *sha1, int return 0; } -static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data) +static void reflog_expiry_prepare(const char *refname, + const unsigned char *sha1, + void *cb_data) { - struct cmd_reflog_expire_cb *cmd = cb_data; - struct expire_reflog_cb cb; - struct ref_lock *lock; - char *log_file, *newlog_path = NULL; - struct commit *tip_commit; - struct commit_list *tips; - int status = 0; - - memset(&cb, 0, sizeof(cb)); - - /* - * we take the lock for the ref itself to prevent it from - * getting updated. - */ - lock = lock_any_ref_for_update(ref, sha1, 0, NULL); - if (!lock) - return error("cannot lock ref '%s'", ref); - log_file = git_pathdup("logs/%s", ref); - if (!reflog_exists(ref)) - goto finish; - if (!cmd->dry_run) { - newlog_path = git_pathdup("logs/%s.lock", ref); - cb.newlog = fopen(newlog_path, "w"); - } + struct expire_reflog_policy_cb *cb = cb_data; - cb.cmd = cmd; - - if (!cmd->expire_unreachable || !strcmp(ref, "HEAD")) { - tip_commit = NULL; - cb.unreachable_expire_kind = UE_HEAD; + if (!cb->cmd.expire_unreachable || !strcmp(refname, "HEAD")) { + cb->tip_commit = NULL; + cb->unreachable_expire_kind = UE_HEAD; } else { - tip_commit = lookup_commit_reference_gently(sha1, 1); - if (!tip_commit) - cb.unreachable_expire_kind = UE_ALWAYS; + cb->tip_commit = lookup_commit_reference_gently(sha1, 1); + if (!cb->tip_commit) + cb->unreachable_expire_kind = UE_ALWAYS; else - cb.unreachable_expire_kind = UE_NORMAL; + cb->unreachable_expire_kind = UE_NORMAL; } - if (cmd->expire_unreachable <= cmd->expire_total) - cb.unreachable_expire_kind = UE_ALWAYS; + if (cb->cmd.expire_unreachable <= cb->cmd.expire_total) + cb->unreachable_expire_kind = UE_ALWAYS; - cb.mark_list = NULL; - tips = NULL; - if (cb.unreachable_expire_kind != UE_ALWAYS) { - if (cb.unreachable_expire_kind == UE_HEAD) { + cb->mark_list = NULL; + cb->tips = NULL; + if (cb->unreachable_expire_kind != UE_ALWAYS) { + if (cb->unreachable_expire_kind == UE_HEAD) { struct commit_list *elem; - for_each_ref(push_tip_to_list, &tips); - for (elem = tips; elem; elem = elem->next) - commit_list_insert(elem->item, &cb.mark_list); + for_each_ref(push_tip_to_list, &cb->tips); + for (elem = cb->tips; elem; elem = elem->next) + commit_list_insert(elem->item, &cb->mark_list); } else { - commit_list_insert(tip_commit, &cb.mark_list); + commit_list_insert(cb->tip_commit, &cb->mark_list); } - cb.mark_limit = cmd->expire_total; - mark_reachable(&cb); + cb->mark_limit = cb->cmd.expire_total; + mark_reachable(cb); } +} - for_each_reflog_ent(ref, expire_reflog_ent, &cb); +static void reflog_expiry_cleanup(void *cb_data) +{ + struct expire_reflog_policy_cb *cb = cb_data; - if (cb.unreachable_expire_kind != UE_ALWAYS) { - if (cb.unreachable_expire_kind == UE_HEAD) { + if (cb->unreachable_expire_kind != UE_ALWAYS) { + if (cb->unreachable_expire_kind == UE_HEAD) { struct commit_list *elem; - for (elem = tips; elem; elem = elem->next) + for (elem = cb->tips; elem; elem = elem->next) clear_commit_marks(elem->item, REACHABLE); - free_commit_list(tips); + free_commit_list(cb->tips); } else { - clear_commit_marks(tip_commit, REACHABLE); + clear_commit_marks(cb->tip_commit, REACHABLE); } } - finish: - if (cb.newlog) { - if (fclose(cb.newlog)) { - status |= error("%s: %s", strerror(errno), - newlog_path); - unlink(newlog_path); - } else if (cmd->updateref && - (write_in_full(lock->lock_fd, - sha1_to_hex(cb.last_kept_sha1), 40) != 40 || - write_str_in_full(lock->lock_fd, "\n") != 1 || - close_ref(lock) < 0)) { - status |= error("Couldn't write %s", - lock->lk->filename.buf); - unlink(newlog_path); - } else if (rename(newlog_path, log_file)) { - status |= error("cannot rename %s to %s", - newlog_path, log_file); - unlink(newlog_path); - } else if (cmd->updateref && commit_ref(lock)) { - status |= error("Couldn't set %s", lock->ref_name); - } else { - adjust_shared_perm(log_file); - } - } - free(newlog_path); - free(log_file); - unlock_ref(lock); - return status; } static int collect_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data) @@ -590,10 +520,11 @@ static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, int slot, c static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) { - struct cmd_reflog_expire_cb cb; + struct expire_reflog_policy_cb cb; unsigned long now = time(NULL); int i, status, do_all; int explicit_expiry = 0; + unsigned int flags = 0; default_reflog_expire_unreachable = now - 30 * 24 * 3600; default_reflog_expire = now - 90 * 24 * 3600; @@ -603,33 +534,33 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) do_all = status = 0; memset(&cb, 0, sizeof(cb)); - cb.expire_total = default_reflog_expire; - cb.expire_unreachable = default_reflog_expire_unreachable; + cb.cmd.expire_total = default_reflog_expire; + cb.cmd.expire_unreachable = default_reflog_expire_unreachable; for (i = 1; i < argc; i++) { const char *arg = argv[i]; if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n")) - cb.dry_run = 1; + flags |= EXPIRE_REFLOGS_DRY_RUN; else if (starts_with(arg, "--expire=")) { - if (parse_expiry_date(arg + 9, &cb.expire_total)) + if (parse_expiry_date(arg + 9, &cb.cmd.expire_total)) die(_("'%s' is not a valid timestamp"), arg); explicit_expiry |= EXPIRE_TOTAL; } else if (starts_with(arg, "--expire-unreachable=")) { - if (parse_expiry_date(arg + 21, &cb.expire_unreachable)) + if (parse_expiry_date(arg + 21, &cb.cmd.expire_unreachable)) die(_("'%s' is not a valid timestamp"), arg); explicit_expiry |= EXPIRE_UNREACH; } else if (!strcmp(arg, "--stale-fix")) - cb.stalefix = 1; + cb.cmd.stalefix = 1; else if (!strcmp(arg, "--rewrite")) - cb.rewrite = 1; + flags |= EXPIRE_REFLOGS_REWRITE; else if (!strcmp(arg, "--updateref")) - cb.updateref = 1; + flags |= EXPIRE_REFLOGS_UPDATE_REF; else if (!strcmp(arg, "--all")) do_all = 1; else if (!strcmp(arg, "--verbose")) - cb.verbose = 1; + flags |= EXPIRE_REFLOGS_VERBOSE; else if (!strcmp(arg, "--")) { i++; break; @@ -645,12 +576,12 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) * even in older repository. We cannot trust what's reachable * from reflog if the repository was pruned with older git. */ - if (cb.stalefix) { - init_revisions(&cb.revs, prefix); - if (cb.verbose) + if (cb.cmd.stalefix) { + init_revisions(&cb.cmd.revs, prefix); + if (flags & EXPIRE_REFLOGS_VERBOSE) printf("Marking reachable objects..."); - mark_reachable_objects(&cb.revs, 0, 0, NULL); - if (cb.verbose) + mark_reachable_objects(&cb.cmd.revs, 0, 0, NULL); + if (flags & EXPIRE_REFLOGS_VERBOSE) putchar('\n'); } @@ -662,8 +593,12 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) for_each_reflog(collect_reflog, &collected); for (i = 0; i < collected.nr; i++) { struct collected_reflog *e = collected.e[i]; - set_reflog_expiry_param(&cb, explicit_expiry, e->reflog); - status |= expire_reflog(e->reflog, e->sha1, 0, &cb); + set_reflog_expiry_param(&cb.cmd, explicit_expiry, e->reflog); + status |= reflog_expire(e->reflog, e->sha1, flags, + reflog_expiry_prepare, + should_expire_reflog_ent, + reflog_expiry_cleanup, + &cb); free(e); } free(collected.e); @@ -676,8 +611,12 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) status |= error("%s points nowhere!", argv[i]); continue; } - set_reflog_expiry_param(&cb, explicit_expiry, ref); - status |= expire_reflog(ref, sha1, 0, &cb); + set_reflog_expiry_param(&cb.cmd, explicit_expiry, ref); + status |= reflog_expire(ref, sha1, flags, + reflog_expiry_prepare, + should_expire_reflog_ent, + reflog_expiry_cleanup, + &cb); } return status; } @@ -686,29 +625,30 @@ static int count_reflog_ent(unsigned char *osha1, unsigned char *nsha1, const char *email, unsigned long timestamp, int tz, const char *message, void *cb_data) { - struct cmd_reflog_expire_cb *cb = cb_data; - if (!cb->expire_total || timestamp < cb->expire_total) - cb->recno++; + struct expire_reflog_policy_cb *cb = cb_data; + if (!cb->cmd.expire_total || timestamp < cb->cmd.expire_total) + cb->cmd.recno++; return 0; } static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) { - struct cmd_reflog_expire_cb cb; + struct expire_reflog_policy_cb cb; int i, status = 0; + unsigned int flags = 0; memset(&cb, 0, sizeof(cb)); for (i = 1; i < argc; i++) { const char *arg = argv[i]; if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n")) - cb.dry_run = 1; + flags |= EXPIRE_REFLOGS_DRY_RUN; else if (!strcmp(arg, "--rewrite")) - cb.rewrite = 1; + flags |= EXPIRE_REFLOGS_REWRITE; else if (!strcmp(arg, "--updateref")) - cb.updateref = 1; + flags |= EXPIRE_REFLOGS_UPDATE_REF; else if (!strcmp(arg, "--verbose")) - cb.verbose = 1; + flags |= EXPIRE_REFLOGS_VERBOSE; else if (!strcmp(arg, "--")) { i++; break; @@ -740,15 +680,19 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) recno = strtoul(spec + 2, &ep, 10); if (*ep == '}') { - cb.recno = -recno; + cb.cmd.recno = -recno; for_each_reflog_ent(ref, count_reflog_ent, &cb); } else { - cb.expire_total = approxidate(spec + 2); + cb.cmd.expire_total = approxidate(spec + 2); for_each_reflog_ent(ref, count_reflog_ent, &cb); - cb.expire_total = 0; + cb.cmd.expire_total = 0; } - status |= expire_reflog(ref, sha1, 0, &cb); + status |= reflog_expire(ref, sha1, flags, + reflog_expiry_prepare, + should_expire_reflog_ent, + reflog_expiry_cleanup, + &cb); free(ref); } return status; diff --git a/builtin/remote.c b/builtin/remote.c index b4ff468977..5d3ab906bc 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -10,10 +10,10 @@ static const char * const builtin_remote_usage[] = { N_("git remote [-v | --verbose]"), - N_("git remote add [-t <branch>] [-m <master>] [-f] [--tags|--no-tags] [--mirror=<fetch|push>] <name> <url>"), + N_("git remote add [-t <branch>] [-m <master>] [-f] [--tags | --no-tags] [--mirror=<fetch|push>] <name> <url>"), N_("git remote rename <old> <new>"), N_("git remote remove <name>"), - N_("git remote set-head <name> (-a | --auto | -d | --delete |<branch>)"), + N_("git remote set-head <name> (-a | --auto | -d | --delete | <branch>)"), N_("git remote [-v | --verbose] show [-n] <name>"), N_("git remote prune [-n | --dry-run] <name>"), N_("git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]"), diff --git a/builtin/repack.c b/builtin/repack.c index 3f852f35d1..28fbc7099a 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -14,7 +14,7 @@ static int write_bitmaps; static char *packdir, *packtmp; static const char *const git_repack_usage[] = { - N_("git repack [options]"), + N_("git repack [<options>]"), NULL }; diff --git a/builtin/replace.c b/builtin/replace.c index 85d39b58d8..54bf01acb4 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -172,7 +172,7 @@ static int replace_object_sha1(const char *object_ref, transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_update(transaction, ref, repl, prev, - 0, 1, NULL, &err) || + 0, NULL, &err) || ref_transaction_commit(transaction, &err)) die("%s", err.buf); diff --git a/builtin/rerere.c b/builtin/rerere.c index 98eb8c5404..7afadd2ead 100644 --- a/builtin/rerere.c +++ b/builtin/rerere.c @@ -9,7 +9,7 @@ #include "pathspec.h" static const char * const rerere_usage[] = { - N_("git rerere [clear | forget path... | status | remaining | diff | gc]"), + N_("git rerere [clear | forget <path>... | status | remaining | diff | gc]"), NULL, }; diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 95328b80d9..3626c61da6 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -358,7 +358,7 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) { static int keep_dashdash = 0, stop_at_non_option = 0; static char const * const parseopt_usage[] = { - N_("git rev-parse --parseopt [options] -- [<args>...]"), + N_("git rev-parse --parseopt [<options>] -- [<args>...]"), NULL }; static struct option parseopt_opts[] = { @@ -496,9 +496,9 @@ static void die_no_single_rev(int quiet) } static const char builtin_rev_parse_usage[] = -N_("git rev-parse --parseopt [options] -- [<args>...]\n" +N_("git rev-parse --parseopt [<options>] -- [<args>...]\n" " or: git rev-parse --sq-quote [<arg>...]\n" - " or: git rev-parse [options] [<arg>...]\n" + " or: git rev-parse [<options>] [<arg>...]\n" "\n" "Run \"git rev-parse --parseopt -h\" for more information on the first usage."); diff --git a/builtin/revert.c b/builtin/revert.c index f9ed5bd5d0..56a2c36669 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -19,13 +19,13 @@ */ static const char * const revert_usage[] = { - N_("git revert [options] <commit-ish>..."), + N_("git revert [<options>] <commit-ish>..."), N_("git revert <subcommand>"), NULL }; static const char * const cherry_pick_usage[] = { - N_("git cherry-pick [options] <commit-ish>..."), + N_("git cherry-pick [<options>] <commit-ish>..."), N_("git cherry-pick <subcommand>"), NULL }; diff --git a/builtin/rm.c b/builtin/rm.c index d8a9c86dd1..3304bff42a 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -14,7 +14,7 @@ #include "pathspec.h" static const char * const builtin_rm_usage[] = { - N_("git rm [options] [--] <file>..."), + N_("git rm [<options>] [--] <file>..."), NULL }; diff --git a/builtin/send-pack.c b/builtin/send-pack.c index b564a77845..b961e5ae78 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -13,7 +13,7 @@ #include "sha1-array.h" static const char send_pack_usage[] = -"git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n" +"git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [--atomic] [<host>:]<directory> [<ref>...]\n" " --all and explicit <ref> specification are mutually exclusive."; static struct send_pack_args args; @@ -170,6 +170,10 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) args.use_thin_pack = 1; continue; } + if (!strcmp(arg, "--atomic")) { + args.atomic = 1; + continue; + } if (!strcmp(arg, "--stateless-rpc")) { args.stateless_rpc = 1; continue; diff --git a/builtin/shortlog.c b/builtin/shortlog.c index 4b7e53623f..c0bab6aaa9 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -10,7 +10,7 @@ #include "parse-options.h" static char const * const shortlog_usage[] = { - N_("git shortlog [<options>] [<revision range>] [[--] [<path>...]]"), + N_("git shortlog [<options>] [<revision-range>] [[--] [<path>...]]"), NULL }; diff --git a/builtin/show-branch.c b/builtin/show-branch.c index 365228aa8d..f3fb5fb2bf 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -6,11 +6,11 @@ #include "parse-options.h" static const char* show_branch_usage[] = { - N_("git show-branch [-a|--all] [-r|--remotes] [--topo-order | --date-order]\n" + N_("git show-branch [-a | --all] [-r | --remotes] [--topo-order | --date-order]\n" " [--current] [--color[=<when>] | --no-color] [--sparse]\n" " [--more=<n> | --list | --independent | --merge-base]\n" " [--no-name | --sha1-name] [--topics] [(<rev> | <glob>)...]"), - N_("git show-branch (-g|--reflog)[=<n>[,<base>]] [--list] [<ref>]"), + N_("git show-branch (-g | --reflog)[=<n>[,<base>]] [--list] [<ref>]"), NULL }; diff --git a/builtin/show-ref.c b/builtin/show-ref.c index 5ba1f30838..afb10309d6 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -7,7 +7,7 @@ #include "parse-options.h" static const char * const show_ref_usage[] = { - N_("git show-ref [-q|--quiet] [--verify] [--head] [-d|--dereference] [-s|--hash[=<n>]] [--abbrev[=<n>]] [--tags] [--heads] [--] [pattern*] "), + N_("git show-ref [-q | --quiet] [--verify] [--head] [-d | --dereference] [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags] [--heads] [--] [<pattern>...]"), N_("git show-ref --exclude-existing[=pattern] < ref-list"), NULL }; diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c index 29fb3f1c20..ce0fde705c 100644 --- a/builtin/symbolic-ref.c +++ b/builtin/symbolic-ref.c @@ -4,8 +4,8 @@ #include "parse-options.h" static const char * const git_symbolic_ref_usage[] = { - N_("git symbolic-ref [options] name [ref]"), - N_("git symbolic-ref -d [-q] name"), + N_("git symbolic-ref [<options>] <name> [<ref>]"), + N_("git symbolic-ref -d [-q] <name>"), NULL }; diff --git a/builtin/tag.c b/builtin/tag.c index e633f4efdb..4194b9a711 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -19,9 +19,9 @@ #include "column.h" static const char * const git_tag_usage[] = { - N_("git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]"), + N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<head>]"), N_("git tag -d <tagname>..."), - N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>] " + N_("git tag -l [-n[<num>]] [--contains <commit>] [--points-at <object>]" "\n\t\t[<pattern>...]"), N_("git tag -v <tagname>..."), NULL @@ -733,7 +733,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_update(transaction, ref.buf, object, prev, - 0, 1, NULL, &err) || + 0, NULL, &err) || ref_transaction_commit(transaction, &err)) die("%s", err.buf); ref_transaction_free(transaction); diff --git a/builtin/update-index.c b/builtin/update-index.c index b0e3dc9105..587898624c 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -400,7 +400,7 @@ static void read_index_info(int line_termination) } static const char * const update_index_usage[] = { - N_("git update-index [options] [--] [<file>...]"), + N_("git update-index [<options>] [--] [<file>...]"), NULL }; diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 1993529521..3d79a46b03 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -6,9 +6,9 @@ #include "argv-array.h" static const char * const git_update_ref_usage[] = { - N_("git update-ref [options] -d <refname> [<oldval>]"), - N_("git update-ref [options] <refname> <newval> [<oldval>]"), - N_("git update-ref [options] --stdin [-z]"), + N_("git update-ref [<options>] -d <refname> [<old-val>]"), + N_("git update-ref [<options>] <refname> <new-val> [<old-val>]"), + N_("git update-ref [<options>] --stdin [-z]"), NULL }; @@ -198,8 +198,9 @@ static const char *parse_cmd_update(struct ref_transaction *transaction, if (*next != line_termination) die("update %s: extra input: %s", refname, next); - if (ref_transaction_update(transaction, refname, new_sha1, old_sha1, - update_flags, have_old, msg, &err)) + if (ref_transaction_update(transaction, refname, + new_sha1, have_old ? old_sha1 : NULL, + update_flags, msg, &err)) die("%s", err.buf); update_flags = 0; @@ -264,8 +265,9 @@ static const char *parse_cmd_delete(struct ref_transaction *transaction, if (*next != line_termination) die("delete %s: extra input: %s", refname, next); - if (ref_transaction_delete(transaction, refname, old_sha1, - update_flags, have_old, msg, &err)) + if (ref_transaction_delete(transaction, refname, + have_old ? old_sha1 : NULL, + update_flags, msg, &err)) die("%s", err.buf); update_flags = 0; @@ -280,7 +282,6 @@ static const char *parse_cmd_verify(struct ref_transaction *transaction, { struct strbuf err = STRBUF_INIT; char *refname; - unsigned char new_sha1[20]; unsigned char old_sha1[20]; refname = parse_refname(input, &next); @@ -291,13 +292,11 @@ static const char *parse_cmd_verify(struct ref_transaction *transaction, PARSE_SHA1_OLD)) hashclr(old_sha1); - hashcpy(new_sha1, old_sha1); - if (*next != line_termination) die("verify %s: extra input: %s", refname, next); - if (ref_transaction_update(transaction, refname, new_sha1, old_sha1, - update_flags, 1, msg, &err)) + if (ref_transaction_verify(transaction, refname, old_sha1, + update_flags, &err)) die("%s", err.buf); update_flags = 0; @@ -353,7 +352,8 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) { const char *refname, *oldval; unsigned char sha1[20], oldsha1[20]; - int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0, flags = 0; + int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0; + unsigned int flags = 0; struct option options[] = { OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")), OPT_BOOL('d', NULL, &delete, N_("delete the reference")), diff --git a/builtin/verify-commit.c b/builtin/verify-commit.c index b0f85042b2..ec0c4e3d83 100644 --- a/builtin/verify-commit.c +++ b/builtin/verify-commit.c @@ -14,7 +14,7 @@ #include "gpg-interface.h" static const char * const verify_commit_usage[] = { - N_("git verify-commit [-v|--verbose] <commit>..."), + N_("git verify-commit [-v | --verbose] <commit>..."), NULL }; diff --git a/builtin/verify-pack.c b/builtin/verify-pack.c index 7747537beb..c94e156932 100644 --- a/builtin/verify-pack.c +++ b/builtin/verify-pack.c @@ -51,7 +51,7 @@ static int verify_one_pack(const char *path, unsigned int flags) } static const char * const verify_pack_usage[] = { - N_("git verify-pack [-v|--verbose] [-s|--stat-only] <pack>..."), + N_("git verify-pack [-v | --verbose] [-s | --stat-only] <pack>..."), NULL }; diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c index 9cdf332333..53c68fce3a 100644 --- a/builtin/verify-tag.c +++ b/builtin/verify-tag.c @@ -14,7 +14,7 @@ #include "gpg-interface.h" static const char * const verify_tag_usage[] = { - N_("git verify-tag [-v|--verbose] <tag>..."), + N_("git verify-tag [-v | --verbose] <tag>..."), NULL }; diff --git a/bulk-checkin.c b/bulk-checkin.c index 0c4b8a7cad..8d157eba45 100644 --- a/bulk-checkin.c +++ b/bulk-checkin.c @@ -105,7 +105,6 @@ static int stream_to_pack(struct bulk_checkin_state *state, int write_object = (flags & HASH_WRITE_OBJECT); off_t offset = 0; - memset(&s, 0, sizeof(s)); git_deflate_init(&s, pack_compression_level); hdrlen = encode_in_pack_object_header(type, size, obuf); @@ -334,7 +334,7 @@ static int write_bundle_refs(int bundle_fd, struct rev_info *revs) if (e->item->flags & UNINTERESTING) continue; if (dwim_ref(e->name, strlen(e->name), sha1, &ref) != 1) - continue; + goto skip_write_ref; if (read_ref_full(e->name, RESOLVE_REF_READING, sha1, &flag)) flag = 0; display_ref = (flag & REF_ISSYMREF) ? e->name : ref; @@ -342,7 +342,7 @@ static int write_bundle_refs(int bundle_fd, struct rev_info *revs) if (e->item->type == OBJ_TAG && !is_tag_in_date_range(e->item, revs)) { e->item->flags |= UNINTERESTING; - continue; + goto skip_write_ref; } /* @@ -357,8 +357,7 @@ static int write_bundle_refs(int bundle_fd, struct rev_info *revs) if (!(e->item->flags & SHOWN) && e->item->type == OBJ_COMMIT) { warning(_("ref '%s' is excluded by the rev-list options"), e->name); - free(ref); - continue; + goto skip_write_ref; } /* * If you run "git bundle create bndl v1.0..v2.0", the @@ -388,8 +387,7 @@ static int write_bundle_refs(int bundle_fd, struct rev_info *revs) obj->flags |= SHOWN; add_pending_object(revs, obj, e->name); } - free(ref); - continue; + goto skip_write_ref; } ref_count++; @@ -397,6 +395,7 @@ static int write_bundle_refs(int bundle_fd, struct rev_info *revs) write_or_die(bundle_fd, " ", 1); write_or_die(bundle_fd, display_ref, strlen(display_ref)); write_or_die(bundle_fd, "\n", 1); + skip_write_ref: free(ref); } @@ -568,7 +568,7 @@ extern void update_index_if_able(struct index_state *, struct lock_file *); extern int hold_locked_index(struct lock_file *, int); extern void set_alternate_index_output(const char *); -extern int delete_ref(const char *, const unsigned char *sha1, int delopt); +extern int delete_ref(const char *, const unsigned char *sha1, unsigned int flags); /* Environment bits from configuration mechanism */ extern int trust_executable_bit; diff --git a/check-builtins.sh b/check-builtins.sh index 07cff69d8e..a0aaf3a347 100755 --- a/check-builtins.sh +++ b/check-builtins.sh @@ -3,7 +3,7 @@ { cat <<\EOF sayIt: - $(foreach b,$(BUILT_INS),echo XXX $b YYY;) + $(foreach b,$(BUILT_INS),echo XXX $(b:$X=) YYY;) EOF cat Makefile } | @@ -254,7 +254,6 @@ extern int for_each_commit_graft(each_commit_graft_fn, void *); extern int is_repository_shallow(void); extern struct commit_list *get_shallow_commits(struct object_array *heads, int depth, int shallow_flag, int not_shallow_flag); -extern void check_shallow_file_for_update(void); extern void set_alternate_shallow_file(const char *path, int override); extern int write_shallow_commits(struct strbuf *out, int use_pack_protocol, const struct sha1_array *extra); diff --git a/config.mak.uname b/config.mak.uname index b64b63c34c..f4e77cb9e5 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -107,6 +107,7 @@ ifeq ($(uname_S),Darwin) COMPAT_OBJS += compat/precompose_utf8.o BASIC_CFLAGS += -DPRECOMPOSE_UNICODE BASIC_CFLAGS += -DPROTECT_HFS_DEFAULT=1 + HAVE_BSD_SYSCTL = YesPlease endif ifeq ($(uname_S),SunOS) NEEDS_SOCKET = YesPlease @@ -199,6 +200,7 @@ ifeq ($(uname_S),FreeBSD) PYTHON_PATH = /usr/local/bin/python HAVE_PATHS_H = YesPlease GMTIME_UNRELIABLE_ERRORS = UnfortunatelyYes + HAVE_BSD_SYSCTL = YesPlease endif ifeq ($(uname_S),OpenBSD) NO_STRCASESTR = YesPlease @@ -208,6 +210,7 @@ ifeq ($(uname_S),OpenBSD) BASIC_CFLAGS += -I/usr/local/include BASIC_LDFLAGS += -L/usr/local/lib HAVE_PATHS_H = YesPlease + HAVE_BSD_SYSCTL = YesPlease endif ifeq ($(uname_S),MirBSD) NO_STRCASESTR = YesPlease @@ -215,6 +218,7 @@ ifeq ($(uname_S),MirBSD) USE_ST_TIMESPEC = YesPlease NEEDS_LIBICONV = YesPlease HAVE_PATHS_H = YesPlease + HAVE_BSD_SYSCTL = YesPlease endif ifeq ($(uname_S),NetBSD) ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2) @@ -225,6 +229,7 @@ ifeq ($(uname_S),NetBSD) USE_ST_TIMESPEC = YesPlease NO_MKSTEMPS = YesPlease HAVE_PATHS_H = YesPlease + HAVE_BSD_SYSCTL = YesPlease endif ifeq ($(uname_S),AIX) DEFAULT_PAGER = more diff --git a/configure.ac b/configure.ac index 55e5a9b3e6..bbdde85c3d 100644 --- a/configure.ac +++ b/configure.ac @@ -1046,6 +1046,29 @@ GIT_CONF_SUBST([NO_INITGROUPS]) # # Define NO_ICONV if your libc does not properly support iconv. +AC_DEFUN([BSD_SYSCTL_SRC], [ +AC_LANG_PROGRAM([[ +#include <stddef.h> +#include <sys/types.h> +#include <sys/sysctl.h> +]],[[ +int val, mib[2]; +size_t len; +mib[0] = CTL_HW; +mib[1] = 1; +len = sizeof(val); +return sysctl(mib, 2, &val, &len, NULL, 0) ? 1 : 0; +]])]) + +# +# Define HAVE_BSD_SYSCTL=YesPlease if a BSD-compatible sysctl function is available. +AC_MSG_CHECKING([for BSD sysctl]) +AC_COMPILE_IFELSE([BSD_SYSCTL_SRC], + [AC_MSG_RESULT([yes]) + HAVE_BSD_SYSCTL=YesPlease], + [AC_MSG_RESULT([no]) + HAVE_BSD_SYSCTL=]) +GIT_CONF_SUBST([HAVE_BSD_SYSCTL]) ## Other checks. # Define USE_PIC if you need the main git objects to be built with -fPIC @@ -273,28 +273,44 @@ static enum protocol get_protocol(const char *name) die("I don't handle protocol '%s'", name); } +static char *host_end(char **hoststart, int removebrackets) +{ + char *host = *hoststart; + char *end; + char *start = strstr(host, "@["); + if (start) + start++; /* Jump over '@' */ + else + start = host; + if (start[0] == '[') { + end = strchr(start + 1, ']'); + if (end) { + if (removebrackets) { + *end = 0; + memmove(start, start + 1, end - start); + end++; + } + } else + end = host; + } else + end = host; + return end; +} + #define STR_(s) # s #define STR(s) STR_(s) static void get_host_and_port(char **host, const char **port) { char *colon, *end; - - if (*host[0] == '[') { - end = strchr(*host + 1, ']'); - if (end) { - *end = 0; - end++; - (*host)++; - } else - end = *host; - } else - end = *host; + end = host_end(host, 1); colon = strchr(end, ':'); - if (colon) { - *colon = 0; - *port = colon + 1; + long portnr = strtol(colon + 1, &end, 10); + if (end != colon + 1 && *end == '\0' && 0 <= portnr && portnr < 65536) { + *colon = 0; + *port = colon + 1; + } } } @@ -546,13 +562,16 @@ static struct child_process *git_proxy_connect(int fd[2], char *host) return proxy; } -static const char *get_port_numeric(const char *p) +static char *get_port(char *host) { char *end; + char *p = strchr(host, ':'); + if (p) { long port = strtol(p + 1, &end, 10); if (end != p + 1 && *end == '\0' && 0 <= port && port < 65536) { - return p; + *p = '\0'; + return p+1; } } @@ -594,14 +613,7 @@ static enum protocol parse_connect_url(const char *url_orig, char **ret_host, * Don't do destructive transforms as protocol code does * '[]' unwrapping in get_host_and_port() */ - if (host[0] == '[') { - end = strchr(host + 1, ']'); - if (end) { - end++; - } else - end = host; - } else - end = host; + end = host_end(&host, 0); if (protocol == PROTO_LOCAL) path = end; @@ -662,17 +674,27 @@ struct child_process *git_connect(int fd[2], const char *url, signal(SIGCHLD, SIG_DFL); protocol = parse_connect_url(url, &hostandport, &path); - if (flags & CONNECT_DIAG_URL) { + if ((flags & CONNECT_DIAG_URL) && (protocol != PROTO_SSH)) { printf("Diag: url=%s\n", url ? url : "NULL"); printf("Diag: protocol=%s\n", prot_name(protocol)); printf("Diag: hostandport=%s\n", hostandport ? hostandport : "NULL"); printf("Diag: path=%s\n", path ? path : "NULL"); conn = NULL; } else if (protocol == PROTO_GIT) { + /* + * Set up virtual host information based on where we will + * connect, unless the user has overridden us in + * the environment. + */ + char *target_host = getenv("GIT_OVERRIDE_VIRTUAL_HOST"); + if (target_host) + target_host = xstrdup(target_host); + else + target_host = xstrdup(hostandport); + /* These underlying connection commands die() if they * cannot connect. */ - char *target_host = xstrdup(hostandport); if (git_use_proxy(hostandport)) conn = git_proxy_connect(fd, hostandport); else @@ -704,28 +726,43 @@ struct child_process *git_connect(int fd[2], const char *url, char *ssh_host = hostandport; const char *port = NULL; get_host_and_port(&ssh_host, &port); - port = get_port_numeric(port); - ssh = getenv("GIT_SSH_COMMAND"); - if (ssh) { - conn->use_shell = 1; - putty = 0; - } else { - ssh = getenv("GIT_SSH"); - if (!ssh) - ssh = "ssh"; - putty = !!strcasestr(ssh, "plink"); - } + if (!port) + port = get_port(ssh_host); + + if (flags & CONNECT_DIAG_URL) { + printf("Diag: url=%s\n", url ? url : "NULL"); + printf("Diag: protocol=%s\n", prot_name(protocol)); + printf("Diag: userandhost=%s\n", ssh_host ? ssh_host : "NULL"); + printf("Diag: port=%s\n", port ? port : "NONE"); + printf("Diag: path=%s\n", path ? path : "NULL"); - argv_array_push(&conn->args, ssh); - if (putty && !strcasestr(ssh, "tortoiseplink")) - argv_array_push(&conn->args, "-batch"); - if (port) { - /* P is for PuTTY, p is for OpenSSH */ - argv_array_push(&conn->args, putty ? "-P" : "-p"); - argv_array_push(&conn->args, port); + free(hostandport); + free(path); + free(conn); + return NULL; + } else { + ssh = getenv("GIT_SSH_COMMAND"); + if (ssh) { + conn->use_shell = 1; + putty = 0; + } else { + ssh = getenv("GIT_SSH"); + if (!ssh) + ssh = "ssh"; + putty = !!strcasestr(ssh, "plink"); + } + + argv_array_push(&conn->args, ssh); + if (putty && !strcasestr(ssh, "tortoiseplink")) + argv_array_push(&conn->args, "-batch"); + if (port) { + /* P is for PuTTY, p is for OpenSSH */ + argv_array_push(&conn->args, putty ? "-P" : "-p"); + argv_array_push(&conn->args, port); + } + argv_array_push(&conn->args, ssh_host); } - argv_array_push(&conn->args, ssh_host); } else { /* remove repo-local variables from the environment */ conn->env = local_repo_env; diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 8cfee95f88..731c28998f 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -411,12 +411,9 @@ __git_refs_remotes () __git_remotes () { - local i IFS=$'\n' d="$(__gitdir)" + local d="$(__gitdir)" test -d "$d/remotes" && ls -1 "$d/remotes" - for i in $(git --git-dir="$d" config --get-regexp 'remote\..*\.url' 2>/dev/null); do - i="${i#remote.}" - echo "${i/.url*/}" - done + git --git-dir="$d" remote } __git_list_merge_strategies () @@ -1425,7 +1422,7 @@ __git_log_gitk_options=" # Options that go well for log and shortlog (not gitk) __git_log_shortlog_options=" --author= --committer= --grep= - --all-match + --all-match --invert-grep " __git_log_pretty_formats="oneline short medium full fuller email raw format:" @@ -2014,6 +2011,7 @@ _git_config () color.status.changed color.status.header color.status.nobranch + color.status.unmerged color.status.untracked color.status.updated color.ui diff --git a/credential-store.c b/credential-store.c index d435514cbe..925d3f4024 100644 --- a/credential-store.c +++ b/credential-store.c @@ -118,7 +118,7 @@ static int lookup_credential(const char *fn, struct credential *c) int main(int argc, char **argv) { const char * const usage[] = { - "git credential-store [options] <action>", + "git credential-store [<options>] <action>", NULL }; const char *op; @@ -30,7 +30,7 @@ const unsigned char sane_ctype[256] = { }; /* For case-insensitive kwset */ -const char tolower_trans_tbl[256] = { +const unsigned char tolower_trans_tbl[256] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, @@ -43,9 +43,6 @@ static const char *base_path; static const char *interpolated_path; static int base_path_relaxed; -/* Flag indicating client sent extra args. */ -static int saw_extended_args; - /* If defined, ~user notation is allowed and the string is inserted * after ~user/. E.g. a request to git://host/~alice/frotz would * go to /home/alice/pub_git/frotz with --user-path=pub_git. @@ -56,10 +53,28 @@ static const char *user_path; static unsigned int timeout; static unsigned int init_timeout; -static char *hostname; -static char *canon_hostname; -static char *ip_address; -static char *tcp_port; +struct hostinfo { + struct strbuf hostname; + struct strbuf canon_hostname; + struct strbuf ip_address; + struct strbuf tcp_port; + unsigned int hostname_lookup_done:1; + unsigned int saw_extended_args:1; +}; + +static void lookup_hostname(struct hostinfo *hi); + +static const char *get_canon_hostname(struct hostinfo *hi) +{ + lookup_hostname(hi); + return hi->canon_hostname.buf; +} + +static const char *get_ip_address(struct hostinfo *hi) +{ + lookup_hostname(hi); + return hi->ip_address.buf; +} static void logreport(int priority, const char *err, va_list params) { @@ -106,7 +121,43 @@ static void NORETURN daemon_die(const char *err, va_list params) exit(1); } -static const char *path_ok(const char *directory) +struct expand_path_context { + const char *directory; + struct hostinfo *hostinfo; +}; + +static size_t expand_path(struct strbuf *sb, const char *placeholder, void *ctx) +{ + struct expand_path_context *context = ctx; + struct hostinfo *hi = context->hostinfo; + + switch (placeholder[0]) { + case 'H': + strbuf_addbuf(sb, &hi->hostname); + return 1; + case 'C': + if (placeholder[1] == 'H') { + strbuf_addstr(sb, get_canon_hostname(hi)); + return 2; + } + break; + case 'I': + if (placeholder[1] == 'P') { + strbuf_addstr(sb, get_ip_address(hi)); + return 2; + } + break; + case 'P': + strbuf_addbuf(sb, &hi->tcp_port); + return 1; + case 'D': + strbuf_addstr(sb, context->directory); + return 1; + } + return 0; +} + +static const char *path_ok(const char *directory, struct hostinfo *hi) { static char rpath[PATH_MAX]; static char interp_path[PATH_MAX]; @@ -142,16 +193,13 @@ static const char *path_ok(const char *directory) dir = rpath; } } - else if (interpolated_path && saw_extended_args) { + else if (interpolated_path && hi->saw_extended_args) { struct strbuf expanded_path = STRBUF_INIT; - struct strbuf_expand_dict_entry dict[6]; - - dict[0].placeholder = "H"; dict[0].value = hostname; - dict[1].placeholder = "CH"; dict[1].value = canon_hostname; - dict[2].placeholder = "IP"; dict[2].value = ip_address; - dict[3].placeholder = "P"; dict[3].value = tcp_port; - dict[4].placeholder = "D"; dict[4].value = directory; - dict[5].placeholder = NULL; dict[5].value = NULL; + struct expand_path_context context; + + context.directory = directory; + context.hostinfo = hi; + if (*dir != '/') { /* Allow only absolute */ logerror("'%s': Non-absolute path denied (interpolated-path active)", dir); @@ -159,7 +207,7 @@ static const char *path_ok(const char *directory) } strbuf_expand(&expanded_path, interpolated_path, - strbuf_expand_dict_cb, &dict); + expand_path, &context); strlcpy(interp_path, expanded_path.buf, PATH_MAX); strbuf_release(&expanded_path); loginfo("Interpolated dir '%s'", interp_path); @@ -240,7 +288,8 @@ static int daemon_error(const char *dir, const char *msg) static const char *access_hook; -static int run_access_hook(struct daemon_service *service, const char *dir, const char *path) +static int run_access_hook(struct daemon_service *service, const char *dir, + const char *path, struct hostinfo *hi) { struct child_process child = CHILD_PROCESS_INIT; struct strbuf buf = STRBUF_INIT; @@ -249,16 +298,14 @@ static int run_access_hook(struct daemon_service *service, const char *dir, cons char *eol; int seen_errors = 0; -#define STRARG(x) ((x) ? (x) : "") *arg++ = access_hook; *arg++ = service->name; *arg++ = path; - *arg++ = STRARG(hostname); - *arg++ = STRARG(canon_hostname); - *arg++ = STRARG(ip_address); - *arg++ = STRARG(tcp_port); + *arg++ = hi->hostname.buf; + *arg++ = get_canon_hostname(hi); + *arg++ = get_ip_address(hi); + *arg++ = hi->tcp_port.buf; *arg = NULL; -#undef STRARG child.use_shell = 1; child.argv = argv; @@ -302,7 +349,8 @@ error_return: return -1; } -static int run_service(const char *dir, struct daemon_service *service) +static int run_service(const char *dir, struct daemon_service *service, + struct hostinfo *hi) { const char *path; int enabled = service->enabled; @@ -316,7 +364,7 @@ static int run_service(const char *dir, struct daemon_service *service) return daemon_error(dir, "service not enabled"); } - if (!(path = path_ok(dir))) + if (!(path = path_ok(dir, hi))) return daemon_error(dir, "no such repository"); /* @@ -352,7 +400,7 @@ static int run_service(const char *dir, struct daemon_service *service) * Optionally, a hook can choose to deny access to the * repository depending on the phase of the moon. */ - if (access_hook && run_access_hook(service, dir, path)) + if (access_hook && run_access_hook(service, dir, path, hi)) return -1; /* @@ -485,16 +533,46 @@ static void parse_host_and_port(char *hostport, char **host, } /* + * Sanitize a string from the client so that it's OK to be inserted into a + * filesystem path. Specifically, we disallow slashes, runs of "..", and + * trailing and leading dots, which means that the client cannot escape + * our base path via ".." traversal. + */ +static void sanitize_client(struct strbuf *out, const char *in) +{ + for (; *in; in++) { + if (*in == '/') + continue; + if (*in == '.' && (!out->len || out->buf[out->len - 1] == '.')) + continue; + strbuf_addch(out, *in); + } + + while (out->len && out->buf[out->len - 1] == '.') + strbuf_setlen(out, out->len - 1); +} + +/* + * Like sanitize_client, but we also perform any canonicalization + * to make life easier on the admin. + */ +static void canonicalize_client(struct strbuf *out, const char *in) +{ + sanitize_client(out, in); + strbuf_tolower(out); +} + +/* * Read the host as supplied by the client connection. */ -static void parse_host_arg(char *extra_args, int buflen) +static void parse_host_arg(struct hostinfo *hi, char *extra_args, int buflen) { char *val; int vallen; char *end = extra_args + buflen; if (extra_args < end && *extra_args) { - saw_extended_args = 1; + hi->saw_extended_args = 1; if (strncasecmp("host=", extra_args, 5) == 0) { val = extra_args + 5; vallen = strlen(val) + 1; @@ -503,12 +581,10 @@ static void parse_host_arg(char *extra_args, int buflen) char *host; char *port; parse_host_and_port(val, &host, &port); - if (port) { - free(tcp_port); - tcp_port = xstrdup(port); - } - free(hostname); - hostname = xstrdup_tolower(host); + if (port) + sanitize_client(&hi->tcp_port, port); + canonicalize_client(&hi->hostname, host); + hi->hostname_lookup_done = 0; } /* On to the next one */ @@ -517,11 +593,14 @@ static void parse_host_arg(char *extra_args, int buflen) if (extra_args < end && *extra_args) die("Invalid request"); } +} - /* - * Locate canonical hostname and its IP address. - */ - if (hostname) { +/* + * Locate canonical hostname and its IP address. + */ +static void lookup_hostname(struct hostinfo *hi) +{ + if (!hi->hostname_lookup_done && hi->hostname.len) { #ifndef NO_IPV6 struct addrinfo hints; struct addrinfo *ai; @@ -531,18 +610,20 @@ static void parse_host_arg(char *extra_args, int buflen) memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_CANONNAME; - gai = getaddrinfo(hostname, NULL, &hints, &ai); + gai = getaddrinfo(hi->hostname.buf, NULL, &hints, &ai); if (!gai) { struct sockaddr_in *sin_addr = (void *)ai->ai_addr; inet_ntop(AF_INET, &sin_addr->sin_addr, addrbuf, sizeof(addrbuf)); - free(ip_address); - ip_address = xstrdup(addrbuf); + strbuf_addstr(&hi->ip_address, addrbuf); - free(canon_hostname); - canon_hostname = xstrdup(ai->ai_canonname ? - ai->ai_canonname : ip_address); + if (ai->ai_canonname) + sanitize_client(&hi->canon_hostname, + ai->ai_canonname); + else + strbuf_addbuf(&hi->canon_hostname, + &hi->ip_address); freeaddrinfo(ai); } @@ -552,7 +633,7 @@ static void parse_host_arg(char *extra_args, int buflen) char **ap; static char addrbuf[HOST_NAME_MAX + 1]; - hent = gethostbyname(hostname); + hent = gethostbyname(hostname.buf); if (hent) { ap = hent->h_addr_list; memset(&sa, 0, sizeof sa); @@ -563,21 +644,39 @@ static void parse_host_arg(char *extra_args, int buflen) inet_ntop(hent->h_addrtype, &sa.sin_addr, addrbuf, sizeof(addrbuf)); - free(canon_hostname); - canon_hostname = xstrdup(hent->h_name); - free(ip_address); - ip_address = xstrdup(addrbuf); + sanitize_client(&hi->canon_hostname, hent->h_name); + strbuf_addstr(&hi->ip_address, addrbuf); } #endif + hi->hostname_lookup_done = 1; } } +static void hostinfo_init(struct hostinfo *hi) +{ + memset(hi, 0, sizeof(*hi)); + strbuf_init(&hi->hostname, 0); + strbuf_init(&hi->canon_hostname, 0); + strbuf_init(&hi->ip_address, 0); + strbuf_init(&hi->tcp_port, 0); +} + +static void hostinfo_clear(struct hostinfo *hi) +{ + strbuf_release(&hi->hostname); + strbuf_release(&hi->canon_hostname); + strbuf_release(&hi->ip_address); + strbuf_release(&hi->tcp_port); +} static int execute(void) { char *line = packet_buffer; int pktlen, len, i; char *addr = getenv("REMOTE_ADDR"), *port = getenv("REMOTE_PORT"); + struct hostinfo hi; + + hostinfo_init(&hi); if (addr) loginfo("Connection from %s:%s", addr, port); @@ -596,14 +695,8 @@ static int execute(void) pktlen--; } - free(hostname); - free(canon_hostname); - free(ip_address); - free(tcp_port); - hostname = canon_hostname = ip_address = tcp_port = NULL; - if (len != pktlen) - parse_host_arg(line + len + 1, pktlen - len - 1); + parse_host_arg(&hi, line + len + 1, pktlen - len - 1); for (i = 0; i < ARRAY_SIZE(daemon_service); i++) { struct daemon_service *s = &(daemon_service[i]); @@ -616,10 +709,13 @@ static int execute(void) * Note: The directory here is probably context sensitive, * and might depend on the actual service being performed. */ - return run_service(arg, s); + int rc = run_service(arg, s, &hi); + hostinfo_clear(&hi); + return rc; } } + hostinfo_clear(&hi); logerror("Protocol error: '%s'", line); return -1; } diff --git a/diff-lib.c b/diff-lib.c index 875aff8643..a85c4971ac 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -101,6 +101,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) struct cache_entry *ce = active_cache[i]; int changed; unsigned dirty_submodule = 0; + const unsigned char *old_sha1, *new_sha1; if (diff_can_quit_early(&revs->diffopt)) break; @@ -224,9 +225,12 @@ int run_diff_files(struct rev_info *revs, unsigned int option) continue; } oldmode = ce->ce_mode; + old_sha1 = ce->sha1; + new_sha1 = changed ? null_sha1 : ce->sha1; diff_change(&revs->diffopt, oldmode, newmode, - ce->sha1, (changed ? null_sha1 : ce->sha1), - !is_null_sha1(ce->sha1), (changed ? 0 : !is_null_sha1(ce->sha1)), + old_sha1, new_sha1, + !is_null_sha1(old_sha1), + !is_null_sha1(new_sha1), ce->name, 0, dirty_submodule); } @@ -2093,7 +2093,6 @@ static unsigned char *deflate_it(char *data, unsigned char *deflated; git_zstream stream; - memset(&stream, 0, sizeof(stream)); git_deflate_init(&stream, zlib_compression_level); bound = git_deflate_bound(&stream, size); deflated = xmalloc(bound); @@ -4541,7 +4540,7 @@ void diff_flush(struct diff_options *options) show_stats(&diffstat, options); if (output_format & DIFF_FORMAT_SHORTSTAT) show_shortstats(&diffstat, options); - if (output_format & DIFF_FORMAT_DIRSTAT) + if (output_format & DIFF_FORMAT_DIRSTAT && dirstat_by_line) show_dirstat_by_line(&diffstat, options); free_diffstat_info(&diffstat); separator++; diff --git a/diffcore-rename.c b/diffcore-rename.c index 4e132f1fdb..af1fe08861 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -15,8 +15,7 @@ static struct diff_rename_dst { } *rename_dst; static int rename_dst_nr, rename_dst_alloc; -static struct diff_rename_dst *locate_rename_dst(struct diff_filespec *two, - int insert_ok) +static int find_rename_dst(struct diff_filespec *two) { int first, last; @@ -27,16 +26,33 @@ static struct diff_rename_dst *locate_rename_dst(struct diff_filespec *two, struct diff_rename_dst *dst = &(rename_dst[next]); int cmp = strcmp(two->path, dst->two->path); if (!cmp) - return dst; + return next; if (cmp < 0) { last = next; continue; } first = next+1; } - /* not found */ - if (!insert_ok) - return NULL; + return -first - 1; +} + +static struct diff_rename_dst *locate_rename_dst(struct diff_filespec *two) +{ + int ofs = find_rename_dst(two); + return ofs < 0 ? NULL : &rename_dst[ofs]; +} + +/* + * Returns 0 on success, -1 if we found a duplicate. + */ +static int add_rename_dst(struct diff_filespec *two) +{ + int first = find_rename_dst(two); + + if (first >= 0) + return -1; + first = -first - 1; + /* insert to make it at "first" */ ALLOC_GROW(rename_dst, rename_dst_nr + 1, rename_dst_alloc); rename_dst_nr++; @@ -46,7 +62,7 @@ static struct diff_rename_dst *locate_rename_dst(struct diff_filespec *two, rename_dst[first].two = alloc_filespec(two->path); fill_filespec(rename_dst[first].two, two->sha1, two->sha1_valid, two->mode); rename_dst[first].pair = NULL; - return &(rename_dst[first]); + return 0; } /* Table of rename/copy src files */ @@ -450,8 +466,12 @@ void diffcore_rename(struct diff_options *options) else if (!DIFF_OPT_TST(options, RENAME_EMPTY) && is_empty_blob_sha1(p->two->sha1)) continue; - else - locate_rename_dst(p->two, 1); + else if (add_rename_dst(p->two) < 0) { + warning("skipping rename detection, detected" + " duplicate destination '%s'", + p->two->path); + goto cleanup; + } } else if (!DIFF_OPT_TST(options, RENAME_EMPTY) && is_empty_blob_sha1(p->one->sha1)) @@ -582,8 +602,7 @@ void diffcore_rename(struct diff_options *options) * We would output this create record if it has * not been turned into a rename/copy already. */ - struct diff_rename_dst *dst = - locate_rename_dst(p->two, 0); + struct diff_rename_dst *dst = locate_rename_dst(p->two); if (dst && dst->pair) { diff_q(&outq, dst->pair); pair_to_free = p; @@ -613,8 +632,7 @@ void diffcore_rename(struct diff_options *options) */ if (DIFF_PAIR_BROKEN(p)) { /* broken delete */ - struct diff_rename_dst *dst = - locate_rename_dst(p->one, 0); + struct diff_rename_dst *dst = locate_rename_dst(p->one); if (dst && dst->pair) /* counterpart is now rename/copy */ pair_to_free = p; diff --git a/fast-import.c b/fast-import.c index aac2c24f74..e78ca107b3 100644 --- a/fast-import.c +++ b/fast-import.c @@ -1062,7 +1062,6 @@ static int store_object( } else delta = NULL; - memset(&s, 0, sizeof(s)); git_deflate_init(&s, pack_compression_level); if (delta) { s.next_in = delta; @@ -1090,7 +1089,6 @@ static int store_object( free(delta); delta = NULL; - memset(&s, 0, sizeof(s)); git_deflate_init(&s, pack_compression_level); s.next_in = (void *)dat->buf; s.avail_in = dat->len; @@ -1190,7 +1188,6 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark) crc32_begin(pack_file); - memset(&s, 0, sizeof(s)); git_deflate_init(&s, pack_compression_level); hdrlen = encode_in_pack_object_header(OBJ_BLOB, len, out_buf); @@ -1720,7 +1717,7 @@ static int update_branch(struct branch *b) transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_update(transaction, b->name, b->sha1, old_sha1, - 0, 1, msg, &err) || + 0, msg, &err) || ref_transaction_commit(transaction, &err)) { ref_transaction_free(transaction); error("%s", err.buf); @@ -1760,8 +1757,8 @@ static void dump_tags(void) strbuf_reset(&ref_name); strbuf_addf(&ref_name, "refs/tags/%s", t->name); - if (ref_transaction_update(transaction, ref_name.buf, t->sha1, - NULL, 0, 0, msg, &err)) { + if (ref_transaction_update(transaction, ref_name.buf, + t->sha1, NULL, 0, msg, &err)) { failure |= error("%s", err.buf); goto cleanup; } @@ -18,6 +18,31 @@ # endif #endif +/* + * Guess the user's preferred languages from the value in LANGUAGE environment + * variable and LC_MESSAGES locale category if NO_GETTEXT is not defined. + * + * The result can be a colon-separated list like "ko:ja:en". + */ +const char *get_preferred_languages(void) +{ + const char *retval; + + retval = getenv("LANGUAGE"); + if (retval && *retval) + return retval; + +#ifndef NO_GETTEXT + retval = setlocale(LC_MESSAGES, NULL); + if (retval && *retval && + strcmp(retval, "C") && + strcmp(retval, "POSIX")) + return retval; +#endif + + return NULL; +} + #ifdef GETTEXT_POISON int use_gettext_poison(void) { @@ -63,6 +63,32 @@ const char *Q_(const char *msgid, const char *plu, unsigned long n) } /* Mark msgid for translation but do not translate it. */ +#if !USE_PARENS_AROUND_GETTEXT_N #define N_(msgid) msgid +#else +/* + * Strictly speaking, this will lead to invalid C when + * used this way: + * static const char s[] = N_("FOO"); + * which will expand to + * static const char s[] = ("FOO"); + * and in valid C, the initializer on the right hand side must + * be without the parentheses. But many compilers do accept it + * as a language extension and it will allow us to catch mistakes + * like: + * static const char *msgs[] = { + * N_("one") + * N_("two"), + * N_("three"), + * NULL + * }; + * (notice the missing comma on one of the lines) by forcing + * a compilation error, because parenthesised ("one") ("two") + * will not get silently turned into ("onetwo"). + */ +#define N_(msgid) (msgid) +#endif + +const char *get_preferred_languages(void); #endif diff --git a/git-bisect.sh b/git-bisect.sh index 2fc07acb0f..ae3fec22c4 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -127,7 +127,7 @@ bisect_start() { if test "z$mode" != "z--no-checkout" then git checkout "$start_head" -- || - die "$(eval_gettext "Checking out '\$start_head' failed. Try 'git bisect reset <validbranch>'.")" + die "$(eval_gettext "Checking out '\$start_head' failed. Try 'git bisect reset <valid-branch>'.")" fi else # Get rev from where we start. diff --git a/git-compat-util.h b/git-compat-util.h index 8b72e2a8dd..bc8fc8cf85 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -127,6 +127,9 @@ #else #include <poll.h> #endif +#ifdef HAVE_BSD_SYSCTL +#include <sys/sysctl.h> +#endif #if defined(__MINGW32__) /* pull in Windows compatibility stuff */ @@ -688,7 +691,7 @@ static inline size_t xsize_t(off_t len) } /* in ctype.c, for kwset users */ -extern const char tolower_trans_tbl[256]; +extern const unsigned char tolower_trans_tbl[256]; /* Sane ctype - no locale, and works with signed chars */ #undef isascii @@ -872,4 +875,12 @@ struct tm *git_gmtime_r(const time_t *, struct tm *); #define gmtime_r git_gmtime_r #endif +#if !defined(USE_PARENS_AROUND_GETTEXT_N) && defined(__GNUC__) +#define USE_PARENS_AROUND_GETTEXT_N 1 +#endif + +#ifndef SHELL_PATH +# define SHELL_PATH "/bin/sh" +#endif + #endif diff --git a/git-instaweb.sh b/git-instaweb.sh index 513efa662e..47e38f34c3 100755 --- a/git-instaweb.sh +++ b/git-instaweb.sh @@ -20,6 +20,7 @@ start start the web server restart restart the web server " +SUBDIRECTORY_OK=Yes . git-sh-setup fqgitdir="$GIT_DIR" @@ -204,7 +205,7 @@ webrick_conf () { # actual gitweb.cgi using a shell script to force it wrapper="$fqgitdir/gitweb/$httpd/wrapper.sh" cat > "$wrapper" <<EOF -#!/bin/sh +#!@SHELL_PATH@ # we use this shell script wrapper around the real gitweb.cgi since # there appears to be no other way to pass arbitrary environment variables # into the CGI process @@ -1442,7 +1442,7 @@ class P4Submit(Command, P4UserMap): print " " + self.clientPath print print "To submit, use \"p4 submit\" to write a new description," - print "or \"p4 submit -i %s\" to use the one prepared by" \ + print "or \"p4 submit -i <%s\" to use the one prepared by" \ " \"git p4\"." % fileName print "You can delete the file \"%s\" when finished." % fileName @@ -1915,7 +1915,10 @@ class P4Sync(Command, P4UserMap): optparse.make_option("--keep-path", dest="keepRepoPath", action='store_true', help="Keep entire BRANCH/DIR/SUBDIR prefix during import"), optparse.make_option("--use-client-spec", dest="useClientSpec", action='store_true', - help="Only sync files that are included in the Perforce Client Spec") + help="Only sync files that are included in the Perforce Client Spec"), + optparse.make_option("-/", dest="cloneExclude", + action="append", type="string", + help="exclude depot path"), ] self.description = """Imports from Perforce into a git repository.\n example: @@ -1950,6 +1953,12 @@ class P4Sync(Command, P4UserMap): if gitConfig("git-p4.syncFromOrigin") == "false": self.syncWithOrigin = False + # This is required for the "append" cloneExclude action + def ensure_value(self, attr, value): + if not hasattr(self, attr) or getattr(self, attr) is None: + setattr(self, attr, value) + return getattr(self, attr) + # Force a checkpoint in fast-import and wait for it to finish def checkpoint(self): self.gitStream.write("checkpoint\n\n") @@ -3101,9 +3110,6 @@ class P4Clone(P4Sync): optparse.make_option("--destination", dest="cloneDestination", action='store', default=None, help="where to leave result of the clone"), - optparse.make_option("-/", dest="cloneExclude", - action="append", type="string", - help="exclude depot path"), optparse.make_option("--bare", dest="cloneBare", action="store_true", default=False), ] @@ -3111,12 +3117,6 @@ class P4Clone(P4Sync): self.needsGit = False self.cloneBare = False - # This is required for the "append" cloneExclude action - def ensure_value(self, attr, value): - if not hasattr(self, attr) or getattr(self, attr) is None: - setattr(self, attr, value) - return getattr(self, attr) - def defaultDestination(self, args): ## TODO: use common prefix of args? depotPath = args[0] diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index c96b9847e9..f7deeb096e 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -1030,10 +1030,11 @@ test -n "$autosquash" && rearrange_squash "$todo" test -n "$cmd" && add_exec_commands "$todo" todocount=$(git stripspace --strip-comments <"$todo" | wc -l) +todocount=${todocount##* } cat >>"$todo" <<EOF -$comment_char Rebase $shortrevisions onto $shortonto ($todocount TODO item(s)) +$comment_char Rebase $shortrevisions onto $shortonto ($todocount command(s)) EOF append_todo_help git stripspace --comment-lines >>"$todo" <<\EOF diff --git a/git-send-email.perl b/git-send-email.perl index a18a7959ed..e1e9b1460c 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -762,6 +762,7 @@ if (!defined $auto_8bit_encoding && scalar %broken_encoding) { print " $f\n"; } $auto_8bit_encoding = ask("Which 8bit encoding should I declare [UTF-8]? ", + valid_re => qr/.{4}/, confirm_only => 1, default => "UTF-8"); } diff --git a/git-svn.perl b/git-svn.perl index 32d109ebdf..36f7240c99 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -11,14 +11,10 @@ $AUTHOR = 'Eric Wong <normalperson@yhbt.net>'; $VERSION = '@@GIT_VERSION@@'; use Carp qw/croak/; -use Digest::MD5; -use IO::File qw//; use File::Basename qw/dirname basename/; use File::Path qw/mkpath/; use File::Spec; -use File::Find; use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev/; -use IPC::Open3; use Memoize; use Git::SVN; @@ -298,7 +294,6 @@ my %cmd = ( {} ], ); -use Term::ReadLine; package FakeTerm; sub new { my ($class, $reason) = @_; @@ -313,6 +308,7 @@ package main; my $term; sub term_init { $term = eval { + require Term::ReadLine; $ENV{"GIT_SVN_NOTTY"} ? new Term::ReadLine 'git-svn', \*STDIN, \*STDOUT : new Term::ReadLine 'git-svn'; @@ -1173,6 +1169,7 @@ sub cmd_branch { } ::_req_svn(); + require SVN::Client; my $ctx = SVN::Client->new( config => SVN::Core::config_get_config( @@ -1693,11 +1690,13 @@ sub cmd_reset { } sub cmd_gc { + require File::Find; if (!can_compress()) { warn "Compress::Zlib could not be found; unhandled.log " . "files will not be compressed.\n"; } - find({ wanted => \&gc_directory, no_chdir => 1}, "$ENV{GIT_DIR}/svn"); + File::Find::find({ wanted => \&gc_directory, no_chdir => 1}, + "$ENV{GIT_DIR}/svn"); } ########################### utility functions ######################### @@ -2122,6 +2121,7 @@ sub find_file_type_and_diff_status { sub md5sum { my $arg = shift; my $ref = ref $arg; + require Digest::MD5; my $md5 = Digest::MD5->new(); if ($ref eq 'GLOB' || $ref eq 'IO::File' || $ref eq 'File::Temp') { $md5->addfile($arg) or croak $!; @@ -2148,6 +2148,7 @@ sub gc_directory { $gz->gzwrite($str) or die "Unable to write: ".$gz->gzerror()."!\n"; } + no warnings 'once'; # $File::Find::name would warn unlink $_ or die "unlink $File::Find::name: $!\n"; } elsif (-f $_ && basename($_) eq "index") { unlink $_ or die "unlink $_: $!\n"; @@ -6,7 +6,7 @@ const char git_usage_string[] = "git [--version] [--help] [-C <path>] [-c name=value]\n" " [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\n" - " [-p|--paginate|--no-pager] [--no-replace-objects] [--bare]\n" + " [-p | --paginate | --no-pager] [--no-replace-objects] [--bare]\n" " [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\n" " <command> [<args>]"; @@ -204,10 +204,12 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) fprintf(stderr, "No directory given for -C.\n" ); usage(git_usage_string); } - if (chdir((*argv)[1])) - die_errno("Cannot change to '%s'", (*argv)[1]); - if (envchanged) - *envchanged = 1; + if ((*argv)[1][0]) { + if (chdir((*argv)[1])) + die_errno("Cannot change to '%s'", (*argv)[1]); + if (envchanged) + *envchanged = 1; + } (*argv)++; (*argc)--; } else { @@ -618,6 +620,7 @@ int main(int argc, char **av) { const char **argv = (const char **) av; const char *cmd; + int done_help = 0; startup_info = &git_startup_info; @@ -680,9 +683,7 @@ int main(int argc, char **av) setup_path(); while (1) { - static int done_help = 0; - static int was_alias = 0; - was_alias = run_argv(&argc, &argv); + int was_alias = run_argv(&argc, &argv); if (errno != ENOENT) break; if (was_alias) { diff --git a/http-push.c b/http-push.c index 0beb7ab67f..bfb1c9605b 100644 --- a/http-push.c +++ b/http-push.c @@ -365,7 +365,6 @@ static void start_put(struct transfer_request *request) hdrlen = sprintf(hdr, "%s %lu", typename(type), len) + 1; /* Set it up */ - memset(&stream, 0, sizeof(stream)); git_deflate_init(&stream, zlib_compression_level); size = git_deflate_bound(&stream, len + hdrlen); strbuf_init(&request->buffer.buf, size); @@ -8,6 +8,7 @@ #include "credential.h" #include "version.h" #include "pkt-line.h" +#include "gettext.h" int active_requests; int http_is_verbose; @@ -71,6 +72,8 @@ static struct curl_slist *no_pragma_header; static struct active_request_slot *active_queue_head; +static char *cached_accept_language; + size_t fread_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_) { size_t size = eltsize * nmemb; @@ -117,6 +120,37 @@ size_t fwrite_null(char *ptr, size_t eltsize, size_t nmemb, void *strbuf) return eltsize * nmemb; } +static void closedown_active_slot(struct active_request_slot *slot) +{ + active_requests--; + slot->in_use = 0; +} + +static void finish_active_slot(struct active_request_slot *slot) +{ + closedown_active_slot(slot); + curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE, &slot->http_code); + + if (slot->finished != NULL) + (*slot->finished) = 1; + + /* Store slot results so they can be read after the slot is reused */ + if (slot->results != NULL) { + slot->results->curl_result = slot->curl_result; + slot->results->http_code = slot->http_code; +#if LIBCURL_VERSION_NUM >= 0x070a08 + curl_easy_getinfo(slot->curl, CURLINFO_HTTPAUTH_AVAIL, + &slot->results->auth_avail); +#else + slot->results->auth_avail = 0; +#endif + } + + /* Run callback if appropriate */ + if (slot->callback_func != NULL) + slot->callback_func(slot->callback_data); +} + #ifdef USE_CURL_MULTI static void process_curl_messages(void) { @@ -372,7 +406,9 @@ static CURL *get_curl_handle(void) if (curl_http_proxy) { curl_easy_setopt(result, CURLOPT_PROXY, curl_http_proxy); +#if LIBCURL_VERSION_NUM >= 0x070a07 curl_easy_setopt(result, CURLOPT_PROXYAUTH, CURLAUTH_ANY); +#endif } set_curl_keepalive(result); @@ -518,6 +554,9 @@ void http_cleanup(void) cert_auth.password = NULL; } ssl_cert_password_required = 0; + + free(cached_accept_language); + cached_accept_language = NULL; } struct active_request_slot *get_active_slot(void) @@ -736,12 +775,6 @@ void run_active_slot(struct active_request_slot *slot) #endif } -static void closedown_active_slot(struct active_request_slot *slot) -{ - active_requests--; - slot->in_use = 0; -} - static void release_active_slot(struct active_request_slot *slot) { closedown_active_slot(slot); @@ -758,31 +791,6 @@ static void release_active_slot(struct active_request_slot *slot) #endif } -void finish_active_slot(struct active_request_slot *slot) -{ - closedown_active_slot(slot); - curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE, &slot->http_code); - - if (slot->finished != NULL) - (*slot->finished) = 1; - - /* Store slot results so they can be read after the slot is reused */ - if (slot->results != NULL) { - slot->results->curl_result = slot->curl_result; - slot->results->http_code = slot->http_code; -#if LIBCURL_VERSION_NUM >= 0x070a08 - curl_easy_getinfo(slot->curl, CURLINFO_HTTPAUTH_AVAIL, - &slot->results->auth_avail); -#else - slot->results->auth_avail = 0; -#endif - } - - /* Run callback if appropriate */ - if (slot->callback_func != NULL) - slot->callback_func(slot->callback_data); -} - void finish_all_active_slots(void) { struct active_request_slot *slot = active_queue_head; @@ -845,7 +853,7 @@ char *get_remote_object_url(const char *url, const char *hex, return strbuf_detach(&buf, NULL); } -int handle_curl_result(struct slot_results *results) +static int handle_curl_result(struct slot_results *results) { /* * If we see a failing http code with CURLE_OK, we have turned off @@ -995,6 +1003,116 @@ static void extract_content_type(struct strbuf *raw, struct strbuf *type, strbuf_addstr(charset, "ISO-8859-1"); } +static void write_accept_language(struct strbuf *buf) +{ + /* + * MAX_DECIMAL_PLACES must not be larger than 3. If it is larger than + * that, q-value will be smaller than 0.001, the minimum q-value the + * HTTP specification allows. See + * http://tools.ietf.org/html/rfc7231#section-5.3.1 for q-value. + */ + const int MAX_DECIMAL_PLACES = 3; + const int MAX_LANGUAGE_TAGS = 1000; + const int MAX_ACCEPT_LANGUAGE_HEADER_SIZE = 4000; + char **language_tags = NULL; + int num_langs = 0; + const char *s = get_preferred_languages(); + int i; + struct strbuf tag = STRBUF_INIT; + + /* Don't add Accept-Language header if no language is preferred. */ + if (!s) + return; + + /* + * Split the colon-separated string of preferred languages into + * language_tags array. + */ + do { + /* collect language tag */ + for (; *s && (isalnum(*s) || *s == '_'); s++) + strbuf_addch(&tag, *s == '_' ? '-' : *s); + + /* skip .codeset, @modifier and any other unnecessary parts */ + while (*s && *s != ':') + s++; + + if (tag.len) { + num_langs++; + REALLOC_ARRAY(language_tags, num_langs); + language_tags[num_langs - 1] = strbuf_detach(&tag, NULL); + if (num_langs >= MAX_LANGUAGE_TAGS - 1) /* -1 for '*' */ + break; + } + } while (*s++); + + /* write Accept-Language header into buf */ + if (num_langs) { + int last_buf_len = 0; + int max_q; + int decimal_places; + char q_format[32]; + + /* add '*' */ + REALLOC_ARRAY(language_tags, num_langs + 1); + language_tags[num_langs++] = "*"; /* it's OK; this won't be freed */ + + /* compute decimal_places */ + for (max_q = 1, decimal_places = 0; + max_q < num_langs && decimal_places <= MAX_DECIMAL_PLACES; + decimal_places++, max_q *= 10) + ; + + sprintf(q_format, ";q=0.%%0%dd", decimal_places); + + strbuf_addstr(buf, "Accept-Language: "); + + for (i = 0; i < num_langs; i++) { + if (i > 0) + strbuf_addstr(buf, ", "); + + strbuf_addstr(buf, language_tags[i]); + + if (i > 0) + strbuf_addf(buf, q_format, max_q - i); + + if (buf->len > MAX_ACCEPT_LANGUAGE_HEADER_SIZE) { + strbuf_remove(buf, last_buf_len, buf->len - last_buf_len); + break; + } + + last_buf_len = buf->len; + } + } + + /* free language tags -- last one is a static '*' */ + for (i = 0; i < num_langs - 1; i++) + free(language_tags[i]); + free(language_tags); +} + +/* + * Get an Accept-Language header which indicates user's preferred languages. + * + * Examples: + * LANGUAGE= -> "" + * LANGUAGE=ko:en -> "Accept-Language: ko, en; q=0.9, *; q=0.1" + * LANGUAGE=ko_KR.UTF-8:sr@latin -> "Accept-Language: ko-KR, sr; q=0.9, *; q=0.1" + * LANGUAGE=ko LANG=en_US.UTF-8 -> "Accept-Language: ko, *; q=0.1" + * LANGUAGE= LANG=en_US.UTF-8 -> "Accept-Language: en-US, *; q=0.1" + * LANGUAGE= LANG=C -> "" + */ +static const char *get_accept_language(void) +{ + if (!cached_accept_language) { + struct strbuf buf = STRBUF_INIT; + write_accept_language(&buf); + if (buf.len > 0) + cached_accept_language = strbuf_detach(&buf, NULL); + } + + return cached_accept_language; +} /* http_request() targets */ #define HTTP_REQUEST_STRBUF 0 @@ -1008,6 +1126,7 @@ static int http_request(const char *url, struct slot_results results; struct curl_slist *headers = NULL; struct strbuf buf = STRBUF_INIT; + const char *accept_language; int ret; slot = get_active_slot(); @@ -1033,6 +1152,11 @@ static int http_request(const char *url, fwrite_buffer); } + accept_language = get_accept_language(); + + if (accept_language) + headers = curl_slist_append(headers, accept_language); + strbuf_addstr(&buf, "Pragma:"); if (options && options->no_cache) strbuf_addstr(&buf, " no-cache"); @@ -85,9 +85,7 @@ extern curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp); extern struct active_request_slot *get_active_slot(void); extern int start_active_slot(struct active_request_slot *slot); extern void run_active_slot(struct active_request_slot *slot); -extern void finish_active_slot(struct active_request_slot *slot); extern void finish_all_active_slots(void); -extern int handle_curl_result(struct slot_results *results); /* * This will run one slot to completion in a blocking manner, similar to how diff --git a/imap-send.c b/imap-send.c index d69887da5a..37ac4aa86a 100644 --- a/imap-send.c +++ b/imap-send.c @@ -34,8 +34,16 @@ typedef void *SSL; #include "http.h" #endif +#if defined(USE_CURL_FOR_IMAP_SEND) && defined(NO_OPENSSL) +/* only available option */ +#define USE_CURL_DEFAULT 1 +#else +/* strictly opt in */ +#define USE_CURL_DEFAULT 0 +#endif + static int verbosity; -static int use_curl; /* strictly opt in */ +static int use_curl = USE_CURL_DEFAULT; static const char * const imap_send_usage[] = { "git imap-send [-v] [-q] [--[no-]curl] < <mbox>", NULL }; @@ -1504,9 +1512,14 @@ int main(int argc, char **argv) #ifndef USE_CURL_FOR_IMAP_SEND if (use_curl) { - warning("--use-curl not supported in this build"); + warning("--curl not supported in this build"); use_curl = 0; } +#elif defined(NO_OPENSSL) + if (!use_curl) { + warning("--no-curl not supported in this build"); + use_curl = 1; + } #endif if (!server.port) @@ -80,13 +80,13 @@ struct kwset struct trie *next[NCHAR]; /* Table of children of the root. */ char *target; /* Target string if there's only one. */ int mind2; /* Used in Boyer-Moore search for one string. */ - char const *trans; /* Character translation table. */ + unsigned char const *trans; /* Character translation table. */ }; /* Allocate and initialize a keyword set object, returning an opaque pointer to it. Return NULL if memory is not available. */ kwset_t -kwsalloc (char const *trans) +kwsalloc (unsigned char const *trans) { struct kwset *kwset; @@ -381,7 +381,7 @@ kwsprep (kwset_t kws) register struct kwset *kwset; register int i; register struct trie *curr; - register char const *trans; + register unsigned char const *trans; unsigned char delta[NCHAR]; kwset = (struct kwset *) kws; @@ -590,7 +590,7 @@ cwexec (kwset_t kws, char const *text, size_t len, struct kwsmatch *kwsmatch) register int d; register char const *end, *qlim; register struct tree const *tree; - register char const *trans; + register unsigned char const *trans; accept = NULL; @@ -39,7 +39,7 @@ typedef struct kwset_t* kwset_t; if enough memory cannot be obtained. The argument if non-NULL specifies a table of character translations to be applied to all pattern and search text. */ -extern kwset_t kwsalloc(char const *); +extern kwset_t kwsalloc(unsigned char const *); /* Incrementally extend the keyword set to include the given string. Return NULL for success, or an error message. Remember an index diff --git a/line-log.c b/line-log.c index b7864ad586..a490efea07 100644 --- a/line-log.c +++ b/line-log.c @@ -237,7 +237,7 @@ static void diff_ranges_release(struct diff_ranges *diff) range_set_release(&diff->target); } -void line_log_data_init(struct line_log_data *r) +static void line_log_data_init(struct line_log_data *r) { memset(r, 0, sizeof(struct line_log_data)); range_set_init(&r->ranges, 0); diff --git a/line-log.h b/line-log.h index a9212d84e4..7a5c24e2df 100644 --- a/line-log.h +++ b/line-log.h @@ -54,8 +54,6 @@ struct line_log_data { struct diff_ranges diff; }; -extern void line_log_data_init(struct line_log_data *r); - extern void line_log_init(struct rev_info *rev, const char *prefix, struct string_list *args); extern int line_log_filter(struct rev_info *rev); diff --git a/pack-bitmap.c b/pack-bitmap.c index 6a818419ca..365f9d92ed 100644 --- a/pack-bitmap.c +++ b/pack-bitmap.c @@ -60,7 +60,7 @@ static struct bitmap_index { struct ewah_bitmap *blobs; struct ewah_bitmap *tags; - /* Map from SHA1 -> `stored_bitmap` for all the bitmapped comits */ + /* Map from SHA1 -> `stored_bitmap` for all the bitmapped commits */ khash_sha1 *bitmaps; /* Number of bitmapped commits */ @@ -252,6 +252,20 @@ static int load_bitmap_entries_v1(struct bitmap_index *index) return 0; } +static char *pack_bitmap_filename(struct packed_git *p) +{ + char *idx_name; + int len; + + len = strlen(p->pack_name) - strlen(".pack"); + idx_name = xmalloc(len + strlen(".bitmap") + 1); + + memcpy(idx_name, p->pack_name, len); + memcpy(idx_name + len, ".bitmap", strlen(".bitmap") + 1); + + return idx_name; +} + static int open_pack_bitmap_1(struct packed_git *packfile) { int fd; @@ -322,20 +336,6 @@ failed: return -1; } -char *pack_bitmap_filename(struct packed_git *p) -{ - char *idx_name; - int len; - - len = strlen(p->pack_name) - strlen(".pack"); - idx_name = xmalloc(len + strlen(".bitmap") + 1); - - memcpy(idx_name, p->pack_name, len); - memcpy(idx_name + len, ".bitmap", strlen(".bitmap") + 1); - - return idx_name; -} - static int open_pack_bitmap(void) { struct packed_git *p; diff --git a/pack-bitmap.h b/pack-bitmap.h index 487600b18c..0adcef77b5 100644 --- a/pack-bitmap.h +++ b/pack-bitmap.h @@ -38,7 +38,6 @@ int prepare_bitmap_git(void); void count_bitmap_commit_list(uint32_t *commits, uint32_t *trees, uint32_t *blobs, uint32_t *tags); void traverse_bitmap_commit_list(show_reachable_fn show_reachable); void test_bitmap_walk(struct rev_info *revs); -char *pack_bitmap_filename(struct packed_git *p); int prepare_bitmap_walk(struct rev_info *revs); int reuse_partial_packfile_from_bitmap(struct packed_git **packfile, uint32_t *entries, off_t *up_to); int rebuild_existing_bitmaps(struct packing_data *mapping, khash_sha1 *reused_bitmaps, int show_progress); diff --git a/perl/Git/SVN.pm b/perl/Git/SVN.pm index f24372645b..152fb7e927 100644 --- a/perl/Git/SVN.pm +++ b/perl/Git/SVN.pm @@ -9,10 +9,8 @@ use vars qw/$_no_metadata $_use_log_author $_add_author_from $_localtime/; use Carp qw/croak/; use File::Path qw/mkpath/; -use File::Copy qw/copy/; use IPC::Open3; use Memoize; # core since 5.8.0, Jul 2002 -use Memoize::Storable; use POSIX qw(:signal_h); use Time::Local; @@ -33,11 +31,7 @@ use Git::SVN::Utils qw( add_path_to_url ); -my $can_use_yaml; -BEGIN { - $can_use_yaml = eval { require Git::SVN::Memoize::YAML; 1}; -} - +my $memo_backend; our $_follow_parent = 1; our $_minimize_url = 'unset'; our $default_repo_id = 'svn'; @@ -1579,7 +1573,16 @@ sub tie_for_persistent_memoization { my $hash = shift; my $path = shift; - if ($can_use_yaml) { + unless ($memo_backend) { + if (eval { require Git::SVN::Memoize::YAML; 1}) { + $memo_backend = 1; + } else { + require Memoize::Storable; + $memo_backend = -1; + } + } + + if ($memo_backend > 0) { tie %$hash => 'Git::SVN::Memoize::YAML', "$path.yaml"; } else { tie %$hash => 'Memoize::Storable', "$path.db", 'nstore'; @@ -2189,8 +2192,9 @@ sub rev_map_set { # both of these options make our .rev_db file very, very important # and we can't afford to lose it because rebuild() won't work if ($self->use_svm_props || $self->no_metadata) { + require File::Copy; $sync = 1; - copy($db, $db_lock) or die "rev_map_set(@_): ", + File::Copy::copy($db, $db_lock) or die "rev_map_set(@_): ", "Failed to copy: ", "$db => $db_lock ($!)\n"; } else { diff --git a/perl/Git/SVN/Editor.pm b/perl/Git/SVN/Editor.pm index 4088f13e72..c50176eec9 100644 --- a/perl/Git/SVN/Editor.pm +++ b/perl/Git/SVN/Editor.pm @@ -5,7 +5,6 @@ use warnings; use SVN::Core; use SVN::Delta; use Carp qw/croak/; -use IO::File; use Git qw/command command_oneline command_noisy command_output_pipe command_input_pipe command_close_pipe command_bidi_pipe command_close_bidi_pipe/; @@ -586,7 +585,7 @@ The interface will change as git-svn evolves. =head1 DEPENDENCIES Subversion perl bindings, -the core L<Carp> and L<IO::File> modules, +the core L<Carp> module, and git's L<Git> helper module. C<Git::SVN::Editor> has not been tested using callers other than diff --git a/perl/Git/SVN/Fetcher.pm b/perl/Git/SVN/Fetcher.pm index 613055a3f5..d8c21ad915 100644 --- a/perl/Git/SVN/Fetcher.pm +++ b/perl/Git/SVN/Fetcher.pm @@ -7,7 +7,6 @@ use warnings; use SVN::Delta; use Carp qw/croak/; use File::Basename qw/dirname/; -use IO::File qw//; use Git qw/command command_oneline command_noisy command_output_pipe command_input_pipe command_close_pipe command_bidi_pipe command_close_bidi_pipe/; @@ -608,7 +607,7 @@ developing git-svn. =head1 DEPENDENCIES L<SVN::Delta> from the Subversion perl bindings, -the core L<Carp>, L<File::Basename>, and L<IO::File> modules, +the core L<Carp> and L<File::Basename> modules, and git's L<Git> helper module. C<Git::SVN::Fetcher> has not been tested using callers other than diff --git a/perl/Git/SVN/Ra.pm b/perl/Git/SVN/Ra.pm index 32e2f19ea4..4a499fcb38 100644 --- a/perl/Git/SVN/Ra.pm +++ b/perl/Git/SVN/Ra.pm @@ -3,7 +3,6 @@ use vars qw/@ISA $config_dir $_ignore_refs_regex $_log_window_size/; use strict; use warnings; use Memoize; -use SVN::Client; use Git::SVN::Utils qw( canonicalize_url canonicalize_path @@ -42,6 +41,7 @@ END { } sub _auth_providers () { + require SVN::Client; my @rv = ( SVN::Client::get_simple_provider(), SVN::Client::get_ssl_server_trust_file_provider(), @@ -247,7 +247,10 @@ sub get_log { $ret; } +# uncommon, only for ancient SVN (<= 1.4.2) sub trees_match { + require IO::File; + require SVN::Client; my ($self, $url1, $rev1, $url2, $rev2) = @_; my $ctx = SVN::Client->new(auth => _auth_providers); my $out = IO::File->new_tmpfile; @@ -73,8 +73,3 @@ char *git_prompt(const char *prompt, int flags) } return r; } - -char *git_getpass(const char *prompt) -{ - return git_prompt(prompt, PROMPT_ASKPASS); -} @@ -5,6 +5,5 @@ #define PROMPT_ECHO (1<<1) char *git_prompt(const char *prompt, int flags); -char *git_getpass(const char *prompt); #endif /* PROMPT_H */ @@ -6,6 +6,14 @@ #include "dir.h" #include "string-list.h" +struct ref_lock { + char *ref_name; + char *orig_ref_name; + struct lock_file *lk; + unsigned char old_sha1[20]; + int lock_fd; +}; + /* * How to handle various characters in refnames: * 0: An acceptable character for refs @@ -26,10 +34,29 @@ static unsigned char refname_disposition[256] = { }; /* - * Used as a flag to ref_transaction_delete when a loose ref is being + * Flag passed to lock_ref_sha1_basic() telling it to tolerate broken + * refs (i.e., because the reference is about to be deleted anyway). + */ +#define REF_DELETING 0x02 + +/* + * Used as a flag in ref_update::flags when a loose ref is being * pruned. */ -#define REF_ISPRUNING 0x0100 +#define REF_ISPRUNING 0x04 + +/* + * Used as a flag in ref_update::flags when the reference should be + * updated to new_sha1. + */ +#define REF_HAVE_NEW 0x08 + +/* + * Used as a flag in ref_update::flags when old_sha1 should be + * checked. + */ +#define REF_HAVE_OLD 0x10 + /* * Try to read one refname component from the front of refname. * Return the length of the component found, or -1 if the component is @@ -2093,6 +2120,16 @@ int refname_match(const char *abbrev_name, const char *full_name) return 0; } +static void unlock_ref(struct ref_lock *lock) +{ + /* Do not free lock->lk -- atexit() still looks at them */ + if (lock->lk) + rollback_lock_file(lock->lk); + free(lock->ref_name); + free(lock->orig_ref_name); + free(lock); +} + /* This function should make sure errno is meaningful on error */ static struct ref_lock *verify_lock(struct ref_lock *lock, const unsigned char *old_sha1, int mustexist) @@ -2230,7 +2267,7 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log) static struct ref_lock *lock_ref_sha1_basic(const char *refname, const unsigned char *old_sha1, const struct string_list *skip, - int flags, int *type_p) + unsigned int flags, int *type_p) { char *ref_file; const char *orig_refname = refname; @@ -2239,7 +2276,6 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, int type, lflags; int mustexist = (old_sha1 && !is_null_sha1(old_sha1)); int resolve_flags = 0; - int missing = 0; int attempts_remaining = 3; lock = xcalloc(1, sizeof(struct ref_lock)); @@ -2278,13 +2314,13 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, orig_refname, strerror(errno)); goto error_return; } - missing = is_null_sha1(lock->old_sha1); - /* When the ref did not exist and we are creating it, - * make sure there is no existing ref that is packed - * whose name begins with our refname, nor a ref whose - * name is a proper prefix of our refname. + /* + * If the ref did not exist and we are creating it, make sure + * there is no existing packed ref whose name begins with our + * refname, nor a packed ref whose name is a proper prefix of + * our refname. */ - if (missing && + if (is_null_sha1(lock->old_sha1) && !is_refname_available(refname, skip, get_packed_refs(&ref_cache))) { last_errno = ENOTDIR; goto error_return; @@ -2300,10 +2336,6 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, lock->ref_name = xstrdup(refname); lock->orig_ref_name = xstrdup(orig_refname); ref_file = git_path("%s", refname); - if (missing) - lock->force_write = 1; - if ((flags & REF_NODEREF) && (type & REF_ISSYMREF)) - lock->force_write = 1; retry: switch (safe_create_leading_directories(ref_file)) { @@ -2345,13 +2377,6 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname, return NULL; } -struct ref_lock *lock_any_ref_for_update(const char *refname, - const unsigned char *old_sha1, - int flags, int *type_p) -{ - return lock_ref_sha1_basic(refname, old_sha1, NULL, flags, type_p); -} - /* * Write an entry to the packed-refs file for the specified refname. * If peeled is non-NULL, write it as the entry's peeled value. @@ -2551,7 +2576,7 @@ static void prune_ref(struct ref_to_prune *r) transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_delete(transaction, r->name, r->sha1, - REF_ISPRUNING, 1, NULL, &err) || + REF_ISPRUNING, NULL, &err) || ref_transaction_commit(transaction, &err)) { ref_transaction_free(transaction); error("%s", err.buf); @@ -2721,15 +2746,16 @@ static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err) return 0; } -int delete_ref(const char *refname, const unsigned char *sha1, int delopt) +int delete_ref(const char *refname, const unsigned char *sha1, unsigned int flags) { struct ref_transaction *transaction; struct strbuf err = STRBUF_INIT; transaction = ref_transaction_begin(&err); if (!transaction || - ref_transaction_delete(transaction, refname, sha1, delopt, - sha1 && !is_null_sha1(sha1), NULL, &err) || + ref_transaction_delete(transaction, refname, + (sha1 && !is_null_sha1(sha1)) ? sha1 : NULL, + flags, NULL, &err) || ref_transaction_commit(transaction, &err)) { error("%s", err.buf); ref_transaction_free(transaction); @@ -2865,7 +2891,6 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms error("unable to lock %s for update", newrefname); goto rollback; } - lock->force_write = 1; hashcpy(lock->old_sha1, orig_sha1); if (write_ref_sha1(lock, orig_sha1, logmsg)) { error("unable to write current sha1 into %s", newrefname); @@ -2881,7 +2906,6 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms goto rollbacklog; } - lock->force_write = 1; flag = log_all_ref_updates; log_all_ref_updates = 0; if (write_ref_sha1(lock, orig_sha1, NULL)) @@ -2900,7 +2924,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms return 1; } -int close_ref(struct ref_lock *lock) +static int close_ref(struct ref_lock *lock) { if (close_lock_file(lock->lk)) return -1; @@ -2908,7 +2932,7 @@ int close_ref(struct ref_lock *lock) return 0; } -int commit_ref(struct ref_lock *lock) +static int commit_ref(struct ref_lock *lock) { if (commit_lock_file(lock->lk)) return -1; @@ -2916,16 +2940,6 @@ int commit_ref(struct ref_lock *lock) return 0; } -void unlock_ref(struct ref_lock *lock) -{ - /* Do not free lock->lk -- atexit() still looks at them */ - if (lock->lk) - rollback_lock_file(lock->lk); - free(lock->ref_name); - free(lock->orig_ref_name); - free(lock); -} - /* * copy the reflog message msg to buf, which has been allocated sufficiently * large, while cleaning up the whitespaces. Especially, convert LF to space, @@ -3002,15 +3016,37 @@ int log_ref_setup(const char *refname, char *logfile, int bufsize) return 0; } +static int log_ref_write_fd(int fd, const unsigned char *old_sha1, + const unsigned char *new_sha1, + const char *committer, const char *msg) +{ + int msglen, written; + unsigned maxlen, len; + char *logrec; + + msglen = msg ? strlen(msg) : 0; + maxlen = strlen(committer) + msglen + 100; + logrec = xmalloc(maxlen); + len = sprintf(logrec, "%s %s %s\n", + sha1_to_hex(old_sha1), + sha1_to_hex(new_sha1), + committer); + if (msglen) + len += copy_msg(logrec + len - 1, msg) - 1; + + written = len <= maxlen ? write_in_full(fd, logrec, len) : -1; + free(logrec); + if (written != len) + return -1; + + return 0; +} + static int log_ref_write(const char *refname, const unsigned char *old_sha1, const unsigned char *new_sha1, const char *msg) { - int logfd, result, written, oflags = O_APPEND | O_WRONLY; - unsigned maxlen, len; - int msglen; + int logfd, result, oflags = O_APPEND | O_WRONLY; char log_file[PATH_MAX]; - char *logrec; - const char *committer; if (log_all_ref_updates < 0) log_all_ref_updates = !is_bare_repository(); @@ -3022,19 +3058,9 @@ static int log_ref_write(const char *refname, const unsigned char *old_sha1, logfd = open(log_file, oflags); if (logfd < 0) return 0; - msglen = msg ? strlen(msg) : 0; - committer = git_committer_info(0); - maxlen = strlen(committer) + msglen + 100; - logrec = xmalloc(maxlen); - len = sprintf(logrec, "%s %s %s\n", - sha1_to_hex(old_sha1), - sha1_to_hex(new_sha1), - committer); - if (msglen) - len += copy_msg(logrec + len - 1, msg) - 1; - written = len <= maxlen ? write_in_full(logfd, logrec, len) : -1; - free(logrec); - if (written != len) { + result = log_ref_write_fd(logfd, old_sha1, new_sha1, + git_committer_info(0), msg); + if (result) { int save_errno = errno; close(logfd); error("Unable to append to %s", log_file); @@ -3065,14 +3091,6 @@ static int write_ref_sha1(struct ref_lock *lock, static char term = '\n'; struct object *o; - if (!lock) { - errno = EINVAL; - return -1; - } - if (!lock->force_write && !hashcmp(lock->old_sha1, sha1)) { - unlock_ref(lock); - return 0; - } o = parse_object(sha1); if (!o) { error("Trying to write ref %s with nonexistent object %s", @@ -3542,16 +3560,27 @@ int for_each_reflog(each_ref_fn fn, void *cb_data) } /** - * Information needed for a single ref update. Set new_sha1 to the - * new value or to zero to delete the ref. To check the old value - * while locking the ref, set have_old to 1 and set old_sha1 to the - * value or to zero to ensure the ref does not exist before update. + * Information needed for a single ref update. Set new_sha1 to the new + * value or to null_sha1 to delete the ref. To check the old value + * while the ref is locked, set (flags & REF_HAVE_OLD) and set + * old_sha1 to the old value, or to null_sha1 to ensure the ref does + * not exist before update. */ struct ref_update { + /* + * If (flags & REF_HAVE_NEW), set the reference to this value: + */ unsigned char new_sha1[20]; + /* + * If (flags & REF_HAVE_OLD), check that the reference + * previously had this value: + */ unsigned char old_sha1[20]; - int flags; /* REF_NODEREF? */ - int have_old; /* 1 if old_sha1 is valid, 0 otherwise */ + /* + * One or more of REF_HAVE_NEW, REF_HAVE_OLD, REF_NODEREF, + * REF_DELETING, and REF_ISPRUNING: + */ + unsigned int flags; struct ref_lock *lock; int type; char *msg; @@ -3623,7 +3652,7 @@ int ref_transaction_update(struct ref_transaction *transaction, const char *refname, const unsigned char *new_sha1, const unsigned char *old_sha1, - int flags, int have_old, const char *msg, + unsigned int flags, const char *msg, struct strbuf *err) { struct ref_update *update; @@ -3633,10 +3662,7 @@ int ref_transaction_update(struct ref_transaction *transaction, if (transaction->state != REF_TRANSACTION_OPEN) die("BUG: update called for transaction that is not open"); - if (have_old && !old_sha1) - die("BUG: have_old is true but old_sha1 is NULL"); - - if (!is_null_sha1(new_sha1) && + if (new_sha1 && !is_null_sha1(new_sha1) && check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { strbuf_addf(err, "refusing to update ref with bad name %s", refname); @@ -3644,11 +3670,15 @@ int ref_transaction_update(struct ref_transaction *transaction, } update = add_update(transaction, refname); - hashcpy(update->new_sha1, new_sha1); - update->flags = flags; - update->have_old = have_old; - if (have_old) + if (new_sha1) { + hashcpy(update->new_sha1, new_sha1); + flags |= REF_HAVE_NEW; + } + if (old_sha1) { hashcpy(update->old_sha1, old_sha1); + flags |= REF_HAVE_OLD; + } + update->flags = flags; if (msg) update->msg = xstrdup(msg); return 0; @@ -3657,75 +3687,52 @@ int ref_transaction_update(struct ref_transaction *transaction, int ref_transaction_create(struct ref_transaction *transaction, const char *refname, const unsigned char *new_sha1, - int flags, const char *msg, + unsigned int flags, const char *msg, struct strbuf *err) { - struct ref_update *update; - - assert(err); - - if (transaction->state != REF_TRANSACTION_OPEN) - die("BUG: create called for transaction that is not open"); - if (!new_sha1 || is_null_sha1(new_sha1)) - die("BUG: create ref with null new_sha1"); - - if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { - strbuf_addf(err, "refusing to create ref with bad name %s", - refname); - return -1; - } - - update = add_update(transaction, refname); - - hashcpy(update->new_sha1, new_sha1); - hashclr(update->old_sha1); - update->flags = flags; - update->have_old = 1; - if (msg) - update->msg = xstrdup(msg); - return 0; + die("BUG: create called without valid new_sha1"); + return ref_transaction_update(transaction, refname, new_sha1, + null_sha1, flags, msg, err); } int ref_transaction_delete(struct ref_transaction *transaction, const char *refname, const unsigned char *old_sha1, - int flags, int have_old, const char *msg, + unsigned int flags, const char *msg, struct strbuf *err) { - struct ref_update *update; - - assert(err); - - if (transaction->state != REF_TRANSACTION_OPEN) - die("BUG: delete called for transaction that is not open"); - - if (have_old && !old_sha1) - die("BUG: have_old is true but old_sha1 is NULL"); + if (old_sha1 && is_null_sha1(old_sha1)) + die("BUG: delete called with old_sha1 set to zeros"); + return ref_transaction_update(transaction, refname, + null_sha1, old_sha1, + flags, msg, err); +} - update = add_update(transaction, refname); - update->flags = flags; - update->have_old = have_old; - if (have_old) { - assert(!is_null_sha1(old_sha1)); - hashcpy(update->old_sha1, old_sha1); - } - if (msg) - update->msg = xstrdup(msg); - return 0; +int ref_transaction_verify(struct ref_transaction *transaction, + const char *refname, + const unsigned char *old_sha1, + unsigned int flags, + struct strbuf *err) +{ + if (!old_sha1) + die("BUG: verify called with old_sha1 set to NULL"); + return ref_transaction_update(transaction, refname, + NULL, old_sha1, + flags, NULL, err); } -int update_ref(const char *action, const char *refname, - const unsigned char *sha1, const unsigned char *oldval, - int flags, enum action_on_err onerr) +int update_ref(const char *msg, const char *refname, + const unsigned char *new_sha1, const unsigned char *old_sha1, + unsigned int flags, enum action_on_err onerr) { struct ref_transaction *t; struct strbuf err = STRBUF_INIT; t = ref_transaction_begin(&err); if (!t || - ref_transaction_update(t, refname, sha1, oldval, flags, - !!oldval, action, &err) || + ref_transaction_update(t, refname, new_sha1, old_sha1, + flags, msg, &err) || ref_transaction_commit(t, &err)) { const char *str = "update_ref failed for ref '%s': %s"; @@ -3801,17 +3808,17 @@ int ref_transaction_commit(struct ref_transaction *transaction, /* Acquire all locks while verifying old values */ for (i = 0; i < n; i++) { struct ref_update *update = updates[i]; - int flags = update->flags; + unsigned int flags = update->flags; - if (is_null_sha1(update->new_sha1)) + if ((flags & REF_HAVE_NEW) && is_null_sha1(update->new_sha1)) flags |= REF_DELETING; - update->lock = lock_ref_sha1_basic(update->refname, - (update->have_old ? - update->old_sha1 : - NULL), - NULL, - flags, - &update->type); + update->lock = lock_ref_sha1_basic( + update->refname, + ((update->flags & REF_HAVE_OLD) ? + update->old_sha1 : NULL), + NULL, + flags, + &update->type); if (!update->lock) { ret = (errno == ENOTDIR) ? TRANSACTION_NAME_CONFLICT @@ -3825,31 +3832,46 @@ int ref_transaction_commit(struct ref_transaction *transaction, /* Perform updates first so live commits remain referenced */ for (i = 0; i < n; i++) { struct ref_update *update = updates[i]; + int flags = update->flags; + + if ((flags & REF_HAVE_NEW) && !is_null_sha1(update->new_sha1)) { + int overwriting_symref = ((update->type & REF_ISSYMREF) && + (update->flags & REF_NODEREF)); - if (!is_null_sha1(update->new_sha1)) { - if (write_ref_sha1(update->lock, update->new_sha1, - update->msg)) { + if (!overwriting_symref + && !hashcmp(update->lock->old_sha1, update->new_sha1)) { + /* + * The reference already has the desired + * value, so we don't need to write it. + */ + unlock_ref(update->lock); + update->lock = NULL; + } else if (write_ref_sha1(update->lock, update->new_sha1, + update->msg)) { update->lock = NULL; /* freed by write_ref_sha1 */ strbuf_addf(err, "Cannot update the ref '%s'.", update->refname); ret = TRANSACTION_GENERIC_ERROR; goto cleanup; + } else { + /* freed by write_ref_sha1(): */ + update->lock = NULL; } - update->lock = NULL; /* freed by write_ref_sha1 */ } } /* Perform deletes now that updates are safely completed */ for (i = 0; i < n; i++) { struct ref_update *update = updates[i]; + int flags = update->flags; - if (update->lock) { + if ((flags & REF_HAVE_NEW) && is_null_sha1(update->new_sha1)) { if (delete_ref_loose(update->lock, update->type, err)) { ret = TRANSACTION_GENERIC_ERROR; goto cleanup; } - if (!(update->flags & REF_ISPRUNING)) + if (!(flags & REF_ISPRUNING)) string_list_append(&refs_to_delete, update->lock->ref_name); } @@ -4008,3 +4030,141 @@ int ref_is_hidden(const char *refname) } return 0; } + +struct expire_reflog_cb { + unsigned int flags; + reflog_expiry_should_prune_fn *should_prune_fn; + void *policy_cb; + FILE *newlog; + unsigned char last_kept_sha1[20]; +}; + +static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, + const char *email, unsigned long timestamp, int tz, + const char *message, void *cb_data) +{ + struct expire_reflog_cb *cb = cb_data; + struct expire_reflog_policy_cb *policy_cb = cb->policy_cb; + + if (cb->flags & EXPIRE_REFLOGS_REWRITE) + osha1 = cb->last_kept_sha1; + + if ((*cb->should_prune_fn)(osha1, nsha1, email, timestamp, tz, + message, policy_cb)) { + if (!cb->newlog) + printf("would prune %s", message); + else if (cb->flags & EXPIRE_REFLOGS_VERBOSE) + printf("prune %s", message); + } else { + if (cb->newlog) { + fprintf(cb->newlog, "%s %s %s %lu %+05d\t%s", + sha1_to_hex(osha1), sha1_to_hex(nsha1), + email, timestamp, tz, message); + hashcpy(cb->last_kept_sha1, nsha1); + } + if (cb->flags & EXPIRE_REFLOGS_VERBOSE) + printf("keep %s", message); + } + return 0; +} + +int reflog_expire(const char *refname, const unsigned char *sha1, + unsigned int flags, + reflog_expiry_prepare_fn prepare_fn, + reflog_expiry_should_prune_fn should_prune_fn, + reflog_expiry_cleanup_fn cleanup_fn, + void *policy_cb_data) +{ + static struct lock_file reflog_lock; + struct expire_reflog_cb cb; + struct ref_lock *lock; + char *log_file; + int status = 0; + int type; + + memset(&cb, 0, sizeof(cb)); + cb.flags = flags; + cb.policy_cb = policy_cb_data; + cb.should_prune_fn = should_prune_fn; + + /* + * The reflog file is locked by holding the lock on the + * reference itself, plus we might need to update the + * reference if --updateref was specified: + */ + lock = lock_ref_sha1_basic(refname, sha1, NULL, 0, &type); + if (!lock) + return error("cannot lock ref '%s'", refname); + if (!reflog_exists(refname)) { + unlock_ref(lock); + return 0; + } + + log_file = git_pathdup("logs/%s", refname); + if (!(flags & EXPIRE_REFLOGS_DRY_RUN)) { + /* + * Even though holding $GIT_DIR/logs/$reflog.lock has + * no locking implications, we use the lock_file + * machinery here anyway because it does a lot of the + * work we need, including cleaning up if the program + * exits unexpectedly. + */ + if (hold_lock_file_for_update(&reflog_lock, log_file, 0) < 0) { + struct strbuf err = STRBUF_INIT; + unable_to_lock_message(log_file, errno, &err); + error("%s", err.buf); + strbuf_release(&err); + goto failure; + } + cb.newlog = fdopen_lock_file(&reflog_lock, "w"); + if (!cb.newlog) { + error("cannot fdopen %s (%s)", + reflog_lock.filename.buf, strerror(errno)); + goto failure; + } + } + + (*prepare_fn)(refname, sha1, cb.policy_cb); + for_each_reflog_ent(refname, expire_reflog_ent, &cb); + (*cleanup_fn)(cb.policy_cb); + + if (!(flags & EXPIRE_REFLOGS_DRY_RUN)) { + /* + * It doesn't make sense to adjust a reference pointed + * to by a symbolic ref based on expiring entries in + * the symbolic reference's reflog. Nor can we update + * a reference if there are no remaining reflog + * entries. + */ + int update = (flags & EXPIRE_REFLOGS_UPDATE_REF) && + !(type & REF_ISSYMREF) && + !is_null_sha1(cb.last_kept_sha1); + + if (close_lock_file(&reflog_lock)) { + status |= error("couldn't write %s: %s", log_file, + strerror(errno)); + } else if (update && + (write_in_full(lock->lock_fd, + sha1_to_hex(cb.last_kept_sha1), 40) != 40 || + write_str_in_full(lock->lock_fd, "\n") != 1 || + close_ref(lock) < 0)) { + status |= error("couldn't write %s", + lock->lk->filename.buf); + rollback_lock_file(&reflog_lock); + } else if (commit_lock_file(&reflog_lock)) { + status |= error("unable to commit reflog '%s' (%s)", + log_file, strerror(errno)); + } else if (update && commit_ref(lock)) { + status |= error("couldn't set %s", lock->ref_name); + } + } + free(log_file); + unlock_ref(lock); + return status; + + failure: + rollback_lock_file(&reflog_lock); + free(log_file); + unlock_ref(lock); + return -1; +} @@ -1,15 +1,6 @@ #ifndef REFS_H #define REFS_H -struct ref_lock { - char *ref_name; - char *orig_ref_name; - struct lock_file *lk; - unsigned char old_sha1[20]; - int lock_fd; - int force_write; -}; - /* * A ref_transaction represents a collection of ref updates * that should succeed or fail together. @@ -189,31 +180,13 @@ extern int is_branch(const char *refname); extern int peel_ref(const char *refname, unsigned char *sha1); /* - * Flags controlling lock_any_ref_for_update(), ref_transaction_update(), - * ref_transaction_create(), etc. + * Flags controlling ref_transaction_update(), ref_transaction_create(), etc. * REF_NODEREF: act on the ref directly, instead of dereferencing * symbolic references. - * REF_DELETING: tolerate broken refs * - * Flags >= 0x100 are reserved for internal use. + * Other flags are reserved for internal use. */ #define REF_NODEREF 0x01 -#define REF_DELETING 0x02 -/* - * This function sets errno to something meaningful on failure. - */ -extern struct ref_lock *lock_any_ref_for_update(const char *refname, - const unsigned char *old_sha1, - int flags, int *type_p); - -/** Close the file descriptor owned by a lock and return the status */ -extern int close_ref(struct ref_lock *lock); - -/** Close and commit the ref locked by the lock */ -extern int commit_ref(struct ref_lock *lock); - -/** Release any lock taken but not written. **/ -extern void unlock_ref(struct ref_lock *lock); /* * Setup reflog before using. Set errno to something meaningful on failure. @@ -282,57 +255,95 @@ enum action_on_err { struct ref_transaction *ref_transaction_begin(struct strbuf *err); /* - * The following functions add a reference check or update to a - * ref_transaction. In all of them, refname is the name of the - * reference to be affected. The functions make internal copies of - * refname and msg, so the caller retains ownership of these parameters. - * flags can be REF_NODEREF; it is passed to update_ref_lock(). + * Reference transaction updates + * + * The following four functions add a reference check or update to a + * ref_transaction. They have some common similar parameters: + * + * transaction -- a pointer to an open ref_transaction, obtained + * from ref_transaction_begin(). + * + * refname -- the name of the reference to be affected. + * + * flags -- flags affecting the update, passed to + * update_ref_lock(). Can be REF_NODEREF, which means that + * symbolic references should not be followed. + * + * msg -- a message describing the change (for the reflog). + * + * err -- a strbuf for receiving a description of any error that + * might have occured. + * + * The functions make internal copies of refname and msg, so the + * caller retains ownership of these parameters. + * + * The functions return 0 on success and non-zero on failure. A + * failure means that the transaction as a whole has failed and needs + * to be rolled back. */ /* - * Add a reference update to transaction. new_sha1 is the value that - * the reference should have after the update, or zeros if it should - * be deleted. If have_old is true, then old_sha1 holds the value - * that the reference should have had before the update, or zeros if - * it must not have existed beforehand. - * Function returns 0 on success and non-zero on failure. A failure to update - * means that the transaction as a whole has failed and will need to be - * rolled back. + * Add a reference update to transaction. new_sha1 is the value that + * the reference should have after the update, or null_sha1 if it + * should be deleted. If new_sha1 is NULL, then the reference is not + * changed at all. old_sha1 is the value that the reference must have + * before the update, or null_sha1 if it must not have existed + * beforehand. The old value is checked after the lock is taken to + * prevent races. If the old value doesn't agree with old_sha1, the + * whole transaction fails. If old_sha1 is NULL, then the previous + * value is not checked. + * + * See the above comment "Reference transaction updates" for more + * information. */ int ref_transaction_update(struct ref_transaction *transaction, const char *refname, const unsigned char *new_sha1, const unsigned char *old_sha1, - int flags, int have_old, const char *msg, + unsigned int flags, const char *msg, struct strbuf *err); /* - * Add a reference creation to transaction. new_sha1 is the value - * that the reference should have after the update; it must not be the - * null SHA-1. It is verified that the reference does not exist + * Add a reference creation to transaction. new_sha1 is the value that + * the reference should have after the update; it must not be + * null_sha1. It is verified that the reference does not exist * already. - * Function returns 0 on success and non-zero on failure. A failure to create - * means that the transaction as a whole has failed and will need to be - * rolled back. + * + * See the above comment "Reference transaction updates" for more + * information. */ int ref_transaction_create(struct ref_transaction *transaction, const char *refname, const unsigned char *new_sha1, - int flags, const char *msg, + unsigned int flags, const char *msg, struct strbuf *err); /* - * Add a reference deletion to transaction. If have_old is true, then - * old_sha1 holds the value that the reference should have had before - * the update (which must not be the null SHA-1). - * Function returns 0 on success and non-zero on failure. A failure to delete - * means that the transaction as a whole has failed and will need to be - * rolled back. + * Add a reference deletion to transaction. If old_sha1 is non-NULL, + * then it holds the value that the reference should have had before + * the update (which must not be null_sha1). + * + * See the above comment "Reference transaction updates" for more + * information. */ int ref_transaction_delete(struct ref_transaction *transaction, const char *refname, const unsigned char *old_sha1, - int flags, int have_old, const char *msg, + unsigned int flags, const char *msg, + struct strbuf *err); + +/* + * Verify, within a transaction, that refname has the value old_sha1, + * or, if old_sha1 is null_sha1, then verify that the reference + * doesn't exist. old_sha1 must be non-NULL. + * + * See the above comment "Reference transaction updates" for more + * information. + */ +int ref_transaction_verify(struct ref_transaction *transaction, + const char *refname, + const unsigned char *old_sha1, + unsigned int flags, struct strbuf *err); /* @@ -353,12 +364,65 @@ int ref_transaction_commit(struct ref_transaction *transaction, */ void ref_transaction_free(struct ref_transaction *transaction); -/** Lock a ref and then write its file */ -int update_ref(const char *action, const char *refname, - const unsigned char *sha1, const unsigned char *oldval, - int flags, enum action_on_err onerr); +/** + * Lock, update, and unlock a single reference. This function + * basically does a transaction containing a single call to + * ref_transaction_update(). The parameters to this function have the + * same meaning as the corresponding parameters to + * ref_transaction_update(). Handle errors as requested by the `onerr` + * argument. + */ +int update_ref(const char *msg, const char *refname, + const unsigned char *new_sha1, const unsigned char *old_sha1, + unsigned int flags, enum action_on_err onerr); extern int parse_hide_refs_config(const char *var, const char *value, const char *); extern int ref_is_hidden(const char *); +enum expire_reflog_flags { + EXPIRE_REFLOGS_DRY_RUN = 1 << 0, + EXPIRE_REFLOGS_UPDATE_REF = 1 << 1, + EXPIRE_REFLOGS_VERBOSE = 1 << 2, + EXPIRE_REFLOGS_REWRITE = 1 << 3 +}; + +/* + * The following interface is used for reflog expiration. The caller + * calls reflog_expire(), supplying it with three callback functions, + * of the following types. The callback functions define the + * expiration policy that is desired. + * + * reflog_expiry_prepare_fn -- Called once after the reference is + * locked. + * + * reflog_expiry_should_prune_fn -- Called once for each entry in the + * existing reflog. It should return true iff that entry should be + * pruned. + * + * reflog_expiry_cleanup_fn -- Called once before the reference is + * unlocked again. + */ +typedef void reflog_expiry_prepare_fn(const char *refname, + const unsigned char *sha1, + void *cb_data); +typedef int reflog_expiry_should_prune_fn(unsigned char *osha1, + unsigned char *nsha1, + const char *email, + unsigned long timestamp, int tz, + const char *message, void *cb_data); +typedef void reflog_expiry_cleanup_fn(void *cb_data); + +/* + * Expire reflog entries for the specified reference. sha1 is the old + * value of the reference. flags is a combination of the constants in + * enum expire_reflog_flags. The three function pointers are described + * above. On success, return zero. + */ +extern int reflog_expire(const char *refname, const unsigned char *sha1, + unsigned int flags, + reflog_expiry_prepare_fn prepare_fn, + reflog_expiry_should_prune_fn should_prune_fn, + reflog_expiry_cleanup_fn cleanup_fn, + void *policy_cb_data); + #endif /* REFS_H */ diff --git a/remote-curl.c b/remote-curl.c index 515ac9b411..af7b6786dc 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -567,7 +567,6 @@ retry: git_zstream stream; int ret; - memset(&stream, 0, sizeof(stream)); git_deflate_init_gzip(&stream, Z_BEST_COMPRESSION); gzip_size = git_deflate_bound(&stream, rpc->len); gzip_body = xmalloc(gzip_size); @@ -962,6 +961,8 @@ int main(int argc, const char **argv) struct strbuf buf = STRBUF_INIT; int nongit; + git_setup_gettext(); + git_extract_argv0_path(argv[0]); setup_git_directory_gently(&nongit); if (argc < 2) { @@ -2156,7 +2156,7 @@ struct ref *get_stale_heads(struct refspec *refs, int ref_count, struct ref *fet /* * Compare-and-swap */ -void clear_cas_option(struct push_cas_option *cas) +static void clear_cas_option(struct push_cas_option *cas) { int i; @@ -115,7 +115,8 @@ struct ref { REF_STATUS_REJECT_SHALLOW, REF_STATUS_UPTODATE, REF_STATUS_REMOTE_REJECT, - REF_STATUS_EXPECTING_REPORT + REF_STATUS_EXPECTING_REPORT, + REF_STATUS_ATOMIC_PUSH_FAILED } status; char *remote_status; struct ref *peer_ref; /* when renaming */ @@ -260,7 +261,6 @@ struct push_cas_option { extern int parseopt_push_cas_option(const struct option *, const char *arg, int unset); extern int parse_push_cas_option(struct push_cas_option *, const char *arg, int unset); -extern void clear_cas_option(struct push_cas_option *); extern int is_empty_cas(const struct push_cas_option *); void apply_push_cas(struct push_cas_option *, struct remote *, struct ref *); diff --git a/revision.c b/revision.c index 86406a26a2..66520c671e 100644 --- a/revision.c +++ b/revision.c @@ -2017,6 +2017,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg grep_set_pattern_type_option(GREP_PATTERN_TYPE_PCRE, &revs->grep_filter); } else if (!strcmp(arg, "--all-match")) { revs->grep_filter.all_match = 1; + } else if (!strcmp(arg, "--invert-grep")) { + revs->invert_grep = 1; } else if ((argcount = parse_long_opt("encoding", argv, &optarg))) { if (strcmp(optarg, "none")) git_log_output_encoding = xstrdup(optarg); @@ -2915,7 +2917,7 @@ static int commit_match(struct commit *commit, struct rev_info *opt) (char *)message, strlen(message)); strbuf_release(&buf); unuse_commit_buffer(commit, message); - return retval; + return opt->invert_grep ? !retval : retval; } static inline int want_ancestry(const struct rev_info *revs) @@ -2968,6 +2970,61 @@ enum commit_action get_commit_action(struct rev_info *revs, struct commit *commi return commit_show; } +define_commit_slab(saved_parents, struct commit_list *); + +#define EMPTY_PARENT_LIST ((struct commit_list *)-1) + +/* + * You may only call save_parents() once per commit (this is checked + * for non-root commits). + */ +static void save_parents(struct rev_info *revs, struct commit *commit) +{ + struct commit_list **pp; + + if (!revs->saved_parents_slab) { + revs->saved_parents_slab = xmalloc(sizeof(struct saved_parents)); + init_saved_parents(revs->saved_parents_slab); + } + + pp = saved_parents_at(revs->saved_parents_slab, commit); + + /* + * When walking with reflogs, we may visit the same commit + * several times: once for each appearance in the reflog. + * + * In this case, save_parents() will be called multiple times. + * We want to keep only the first set of parents. We need to + * store a sentinel value for an empty (i.e., NULL) parent + * list to distinguish it from a not-yet-saved list, however. + */ + if (*pp) + return; + if (commit->parents) + *pp = copy_commit_list(commit->parents); + else + *pp = EMPTY_PARENT_LIST; +} + +static void free_saved_parents(struct rev_info *revs) +{ + if (revs->saved_parents_slab) + clear_saved_parents(revs->saved_parents_slab); +} + +struct commit_list *get_saved_parents(struct rev_info *revs, const struct commit *commit) +{ + struct commit_list *parents; + + if (!revs->saved_parents_slab) + return commit->parents; + + parents = *saved_parents_at(revs->saved_parents_slab, commit); + if (parents == EMPTY_PARENT_LIST) + return NULL; + return parents; +} + enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit) { enum commit_action action = get_commit_action(revs, commit); @@ -3267,54 +3324,3 @@ void put_revision_mark(const struct rev_info *revs, const struct commit *commit) fputs(mark, stdout); putchar(' '); } - -define_commit_slab(saved_parents, struct commit_list *); - -#define EMPTY_PARENT_LIST ((struct commit_list *)-1) - -void save_parents(struct rev_info *revs, struct commit *commit) -{ - struct commit_list **pp; - - if (!revs->saved_parents_slab) { - revs->saved_parents_slab = xmalloc(sizeof(struct saved_parents)); - init_saved_parents(revs->saved_parents_slab); - } - - pp = saved_parents_at(revs->saved_parents_slab, commit); - - /* - * When walking with reflogs, we may visit the same commit - * several times: once for each appearance in the reflog. - * - * In this case, save_parents() will be called multiple times. - * We want to keep only the first set of parents. We need to - * store a sentinel value for an empty (i.e., NULL) parent - * list to distinguish it from a not-yet-saved list, however. - */ - if (*pp) - return; - if (commit->parents) - *pp = copy_commit_list(commit->parents); - else - *pp = EMPTY_PARENT_LIST; -} - -struct commit_list *get_saved_parents(struct rev_info *revs, const struct commit *commit) -{ - struct commit_list *parents; - - if (!revs->saved_parents_slab) - return commit->parents; - - parents = *saved_parents_at(revs->saved_parents_slab, commit); - if (parents == EMPTY_PARENT_LIST) - return NULL; - return parents; -} - -void free_saved_parents(struct rev_info *revs) -{ - if (revs->saved_parents_slab) - clear_saved_parents(revs->saved_parents_slab); -} diff --git a/revision.h b/revision.h index 033a24460e..0ea8b4e255 100644 --- a/revision.h +++ b/revision.h @@ -169,6 +169,8 @@ struct rev_info { /* Filter by commit log message */ struct grep_opt grep_filter; + /* Negate the match of grep_filter */ + int invert_grep; /* Display history graph */ struct git_graph *graph; @@ -298,18 +300,14 @@ extern int rewrite_parents(struct rev_info *revs, struct commit *commit, rewrite_parent_fn_t rewrite_parent); /* - * Save a copy of the parent list, and return the saved copy. This is - * used by the log machinery to retrieve the original parents when - * commit->parents has been modified by history simpification. - * - * You may only call save_parents() once per commit (this is checked - * for non-root commits). + * The log machinery saves the original parent list so that + * get_saved_parents() can later tell what the real parents of the + * commits are, when commit->parents has been modified by history + * simpification. * * get_saved_parents() will transparently return commit->parents if * history simplification is off. */ -extern void save_parents(struct rev_info *revs, struct commit *commit); extern struct commit_list *get_saved_parents(struct rev_info *revs, const struct commit *commit); -extern void free_saved_parents(struct rev_info *revs); #endif diff --git a/run-command.c b/run-command.c index 0b432cc971..3afb124c79 100644 --- a/run-command.c +++ b/run-command.c @@ -4,10 +4,6 @@ #include "sigchain.h" #include "argv-array.h" -#ifndef SHELL_PATH -# define SHELL_PATH "/bin/sh" -#endif - void child_process_init(struct child_process *child) { memset(child, 0, sizeof(*child)); diff --git a/send-pack.c b/send-pack.c index 25947d7df9..9d2b0c52ed 100644 --- a/send-pack.c +++ b/send-pack.c @@ -193,10 +193,13 @@ static void advertise_shallow_grafts_buf(struct strbuf *sb) for_each_commit_graft(advertise_shallow_grafts_cb, sb); } -static int ref_update_to_be_sent(const struct ref *ref, const struct send_pack_args *args) +#define CHECK_REF_NO_PUSH -1 +#define CHECK_REF_STATUS_REJECTED -2 +#define CHECK_REF_UPTODATE -3 +static int check_to_send_update(const struct ref *ref, const struct send_pack_args *args) { if (!ref->peer_ref && !args->send_mirror) - return 0; + return CHECK_REF_NO_PUSH; /* Check for statuses set by set_ref_status_for_push() */ switch (ref->status) { @@ -206,10 +209,11 @@ static int ref_update_to_be_sent(const struct ref *ref, const struct send_pack_a case REF_STATUS_REJECT_NEEDS_FORCE: case REF_STATUS_REJECT_STALE: case REF_STATUS_REJECT_NODELETE: + return CHECK_REF_STATUS_REJECTED; case REF_STATUS_UPTODATE: - return 0; + return CHECK_REF_UPTODATE; default: - return 1; + return 0; } } @@ -253,7 +257,7 @@ static int generate_push_cert(struct strbuf *req_buf, strbuf_addstr(&cert, "\n"); for (ref = remote_refs; ref; ref = ref->next) { - if (!ref_update_to_be_sent(ref, args)) + if (check_to_send_update(ref, args) < 0) continue; update_seen = 1; strbuf_addf(&cert, "%s %s %s\n", @@ -281,6 +285,29 @@ free_return: return update_seen; } + +static int atomic_push_failure(struct send_pack_args *args, + struct ref *remote_refs, + struct ref *failing_ref) +{ + struct ref *ref; + /* Mark other refs as failed */ + for (ref = remote_refs; ref; ref = ref->next) { + if (!ref->peer_ref && !args->send_mirror) + continue; + + switch (ref->status) { + case REF_STATUS_EXPECTING_REPORT: + ref->status = REF_STATUS_ATOMIC_PUSH_FAILED; + continue; + default: + break; /* do nothing */ + } + } + return error("atomic push failed for ref %s. status: %d\n", + failing_ref->name, failing_ref->status); +} + int send_pack(struct send_pack_args *args, int fd[], struct child_process *conn, struct ref *remote_refs, @@ -297,6 +324,8 @@ int send_pack(struct send_pack_args *args, int use_sideband = 0; int quiet_supported = 0; int agent_supported = 0; + int use_atomic = 0; + int atomic_supported = 0; unsigned cmds_sent = 0; int ret; struct async demux; @@ -317,6 +346,8 @@ int send_pack(struct send_pack_args *args, agent_supported = 1; if (server_supports("no-thin")) args->use_thin_pack = 0; + if (server_supports("atomic")) + atomic_supported = 1; if (args->push_cert) { int len; @@ -331,6 +362,10 @@ int send_pack(struct send_pack_args *args, "Perhaps you should specify a branch such as 'master'.\n"); return 0; } + if (args->atomic && !atomic_supported) + die(_("server does not support --atomic push")); + + use_atomic = atomic_supported && args->atomic; if (status_report) strbuf_addstr(&cap_buf, " report-status"); @@ -338,6 +373,8 @@ int send_pack(struct send_pack_args *args, strbuf_addstr(&cap_buf, " side-band-64k"); if (quiet_supported && (args->quiet || !args->progress)) strbuf_addstr(&cap_buf, " quiet"); + if (use_atomic) + strbuf_addstr(&cap_buf, " atomic"); if (agent_supported) strbuf_addf(&cap_buf, " agent=%s", git_user_agent_sanitized()); @@ -362,9 +399,21 @@ int send_pack(struct send_pack_args *args, * the pack data. */ for (ref = remote_refs; ref; ref = ref->next) { - if (!ref_update_to_be_sent(ref, args)) + switch (check_to_send_update(ref, args)) { + case 0: /* no error */ + break; + case CHECK_REF_STATUS_REJECTED: + /* + * When we know the server would reject a ref update if + * we were to send it and we're trying to send the refs + * atomically, abort the whole operation. + */ + if (use_atomic) + return atomic_push_failure(args, remote_refs, ref); + /* Fallthrough for non atomic case. */ + default: continue; - + } if (!ref->deletion) need_pack_data = 1; @@ -383,7 +432,7 @@ int send_pack(struct send_pack_args *args, if (args->dry_run || args->push_cert) continue; - if (!ref_update_to_be_sent(ref, args)) + if (check_to_send_update(ref, args) < 0) continue; old_hex = sha1_to_hex(ref->old_sha1); diff --git a/send-pack.h b/send-pack.h index 5635457746..b6646488aa 100644 --- a/send-pack.h +++ b/send-pack.h @@ -13,7 +13,8 @@ struct send_pack_args { use_ofs_delta:1, dry_run:1, push_cert:1, - stateless_rpc:1; + stateless_rpc:1, + atomic:1; }; int send_pack(struct send_pack_args *args, diff --git a/sequencer.c b/sequencer.c index 77a1266760..c4f4b7d571 100644 --- a/sequencer.c +++ b/sequencer.c @@ -252,7 +252,7 @@ static int fast_forward_to(const unsigned char *to, const unsigned char *from, if (!transaction || ref_transaction_update(transaction, "HEAD", to, unborn ? null_sha1 : from, - 0, 1, sb.buf, &err) || + 0, sb.buf, &err) || ref_transaction_commit(transaction, &err)) { ref_transaction_free(transaction); error("%s", err.buf); @@ -373,6 +373,7 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts, { struct argv_array array; int rc; + const char *value; argv_array_init(&array); argv_array_push(&array, "commit"); @@ -385,6 +386,10 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts, if (!opts->edit) { argv_array_push(&array, "-F"); argv_array_push(&array, defmsg); + if (!opts->signoff && + !opts->record_origin && + git_config_get_value("commit.cleanup", &value)) + argv_array_push(&array, "--cleanup=verbatim"); } if (allow_empty) diff --git a/sha1_file.c b/sha1_file.c index 69a60ec88b..88f06bac92 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -2943,7 +2943,6 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen, } /* Set it up */ - memset(&stream, 0, sizeof(stream)); git_deflate_init(&stream, zlib_compression_level); stream.next_out = compressed; stream.avail_out = sizeof(compressed); diff --git a/sha1_name.c b/sha1_name.c index 95f9f8fa1a..6d10f052b5 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -757,7 +757,7 @@ static int get_describe_name(const char *name, int len, unsigned char *sha1) for (cp = name + len - 1; name + 2 <= cp; cp--) { char ch = *cp; - if (hexval(ch) & ~0377) { + if (!isxdigit(ch)) { /* We must be looking at g in "SOMETHING-g" * for it to be describe output. */ @@ -137,7 +137,7 @@ struct commit_list *get_shallow_commits(struct object_array *heads, int depth, return result; } -void check_shallow_file_for_update(void) +static void check_shallow_file_for_update(void) { if (is_shallow == -1) die("BUG: shallow must be initialized by now"); @@ -1,22 +1,112 @@ #ifndef STRBUF_H #define STRBUF_H -/* See Documentation/technical/api-strbuf.txt */ +/** + * strbuf's are meant to be used with all the usual C string and memory + * APIs. Given that the length of the buffer is known, it's often better to + * use the mem* functions than a str* one (memchr vs. strchr e.g.). + * Though, one has to be careful about the fact that str* functions often + * stop on NULs and that strbufs may have embedded NULs. + * + * A strbuf is NUL terminated for convenience, but no function in the + * strbuf API actually relies on the string being free of NULs. + * + * strbufs have some invariants that are very important to keep in mind: + * + * - The `buf` member is never NULL, so it can be used in any usual C + * string operations safely. strbuf's _have_ to be initialized either by + * `strbuf_init()` or by `= STRBUF_INIT` before the invariants, though. + * + * Do *not* assume anything on what `buf` really is (e.g. if it is + * allocated memory or not), use `strbuf_detach()` to unwrap a memory + * buffer from its strbuf shell in a safe way. That is the sole supported + * way. This will give you a malloced buffer that you can later `free()`. + * + * However, it is totally safe to modify anything in the string pointed by + * the `buf` member, between the indices `0` and `len-1` (inclusive). + * + * - The `buf` member is a byte array that has at least `len + 1` bytes + * allocated. The extra byte is used to store a `'\0'`, allowing the + * `buf` member to be a valid C-string. Every strbuf function ensure this + * invariant is preserved. + * + * NOTE: It is OK to "play" with the buffer directly if you work it this + * way: + * + * strbuf_grow(sb, SOME_SIZE); <1> + * strbuf_setlen(sb, sb->len + SOME_OTHER_SIZE); + * + * <1> Here, the memory array starting at `sb->buf`, and of length + * `strbuf_avail(sb)` is all yours, and you can be sure that + * `strbuf_avail(sb)` is at least `SOME_SIZE`. + * + * NOTE: `SOME_OTHER_SIZE` must be smaller or equal to `strbuf_avail(sb)`. + * + * Doing so is safe, though if it has to be done in many places, adding the + * missing API to the strbuf module is the way to go. + * + * WARNING: Do _not_ assume that the area that is yours is of size `alloc + * - 1` even if it's true in the current implementation. Alloc is somehow a + * "private" member that should not be messed with. Use `strbuf_avail()` + * instead. +*/ + +/** + * Data Structures + * --------------- + */ -extern char strbuf_slopbuf[]; +/** + * This is the string buffer structure. The `len` member can be used to + * determine the current length of the string, and `buf` member provides + * access to the string itself. + */ struct strbuf { size_t alloc; size_t len; char *buf; }; +extern char strbuf_slopbuf[]; #define STRBUF_INIT { 0, 0, strbuf_slopbuf } -/*----- strbuf life cycle -----*/ +/** + * Life Cycle Functions + * -------------------- + */ + +/** + * Initialize the structure. The second parameter can be zero or a bigger + * number to allocate memory, in case you want to prevent further reallocs. + */ extern void strbuf_init(struct strbuf *, size_t); + +/** + * Release a string buffer and the memory it used. You should not use the + * string buffer after using this function, unless you initialize it again. + */ extern void strbuf_release(struct strbuf *); + +/** + * Detach the string from the strbuf and returns it; you now own the + * storage the string occupies and it is your responsibility from then on + * to release it with `free(3)` when you are done with it. + */ extern char *strbuf_detach(struct strbuf *, size_t *); + +/** + * Attach a string to a buffer. You should specify the string to attach, + * the current length of the string and the amount of allocated memory. + * The amount must be larger than the string length, because the string you + * pass is supposed to be a NUL-terminated string. This string _must_ be + * malloc()ed, and after attaching, the pointer cannot be relied upon + * anymore, and neither be free()d directly. + */ extern void strbuf_attach(struct strbuf *, void *, size_t, size_t); + +/** + * Swap the contents of two string buffers. + */ static inline void strbuf_swap(struct strbuf *a, struct strbuf *b) { struct strbuf tmp = *a; @@ -24,14 +114,36 @@ static inline void strbuf_swap(struct strbuf *a, struct strbuf *b) *b = tmp; } -/*----- strbuf size related -----*/ + +/** + * Functions related to the size of the buffer + * ------------------------------------------- + */ + +/** + * Determine the amount of allocated but unused memory. + */ static inline size_t strbuf_avail(const struct strbuf *sb) { return sb->alloc ? sb->alloc - sb->len - 1 : 0; } +/** + * Ensure that at least this amount of unused memory is available after + * `len`. This is used when you know a typical size for what you will add + * and want to avoid repetitive automatic resizing of the underlying buffer. + * This is never a needed operation, but can be critical for performance in + * some cases. + */ extern void strbuf_grow(struct strbuf *, size_t); +/** + * Set the length of the buffer to a given value. This function does *not* + * allocate new memory, so you should not perform a `strbuf_setlen()` to a + * length that is larger than `len + strbuf_avail()`. `strbuf_setlen()` is + * just meant as a 'please fix invariants from this strbuf I just messed + * with'. + */ static inline void strbuf_setlen(struct strbuf *sb, size_t len) { if (len > (sb->alloc ? sb->alloc - 1 : 0)) @@ -39,78 +151,58 @@ static inline void strbuf_setlen(struct strbuf *sb, size_t len) sb->len = len; sb->buf[len] = '\0'; } + +/** + * Empty the buffer by setting the size of it to zero. + */ #define strbuf_reset(sb) strbuf_setlen(sb, 0) -/*----- content related -----*/ + +/** + * Functions related to the contents of the buffer + * ----------------------------------------------- + */ + +/** + * Strip whitespace from the beginning (`ltrim`), end (`rtrim`), or both side + * (`trim`) of a string. + */ extern void strbuf_trim(struct strbuf *); extern void strbuf_rtrim(struct strbuf *); extern void strbuf_ltrim(struct strbuf *); -extern int strbuf_reencode(struct strbuf *sb, const char *from, const char *to); -extern void strbuf_tolower(struct strbuf *sb); -extern int strbuf_cmp(const struct strbuf *, const struct strbuf *); -static inline int strbuf_strip_suffix(struct strbuf *sb, const char *suffix) -{ - if (strip_suffix_mem(sb->buf, &sb->len, suffix)) { - strbuf_setlen(sb, sb->len); - return 1; - } else - return 0; -} - -/* - * Split str (of length slen) at the specified terminator character. - * Return a null-terminated array of pointers to strbuf objects - * holding the substrings. The substrings include the terminator, - * except for the last substring, which might be unterminated if the - * original string did not end with a terminator. If max is positive, - * then split the string into at most max substrings (with the last - * substring containing everything following the (max-1)th terminator - * character). - * - * For lighter-weight alternatives, see string_list_split() and - * string_list_split_in_place(). +/** + * Replace the contents of the strbuf with a reencoded form. Returns -1 + * on error, 0 on success. */ -extern struct strbuf **strbuf_split_buf(const char *, size_t, - int terminator, int max); +extern int strbuf_reencode(struct strbuf *sb, const char *from, const char *to); -/* - * Split a NUL-terminated string at the specified terminator - * character. See strbuf_split_buf() for more information. +/** + * Lowercase each character in the buffer using `tolower`. */ -static inline struct strbuf **strbuf_split_str(const char *str, - int terminator, int max) -{ - return strbuf_split_buf(str, strlen(str), terminator, max); -} +extern void strbuf_tolower(struct strbuf *sb); -/* - * Split a strbuf at the specified terminator character. See - * strbuf_split_buf() for more information. +/** + * Compare two buffers. Returns an integer less than, equal to, or greater + * than zero if the first buffer is found, respectively, to be less than, + * to match, or be greater than the second buffer. */ -static inline struct strbuf **strbuf_split_max(const struct strbuf *sb, - int terminator, int max) -{ - return strbuf_split_buf(sb->buf, sb->len, terminator, max); -} +extern int strbuf_cmp(const struct strbuf *, const struct strbuf *); -/* - * Split a strbuf at the specified terminator character. See - * strbuf_split_buf() for more information. - */ -static inline struct strbuf **strbuf_split(const struct strbuf *sb, - int terminator) -{ - return strbuf_split_max(sb, terminator, 0); -} -/* - * Free a NULL-terminated list of strbufs (for example, the return - * values of the strbuf_split*() functions). +/** + * Adding data to the buffer + * ------------------------- + * + * NOTE: All of the functions in this section will grow the buffer as + * necessary. If they fail for some reason other than memory shortage and the + * buffer hadn't been allocated before (i.e. the `struct strbuf` was set to + * `STRBUF_INIT`), then they will free() it. */ -extern void strbuf_list_free(struct strbuf **); -/*----- add data in your buffer -----*/ +/** + * Add a single character to the buffer. + */ static inline void strbuf_addch(struct strbuf *sb, int c) { strbuf_grow(sb, 1); @@ -118,47 +210,276 @@ static inline void strbuf_addch(struct strbuf *sb, int c) sb->buf[sb->len] = '\0'; } +/** + * Add a character the specified number of times to the buffer. + */ +extern void strbuf_addchars(struct strbuf *sb, int c, size_t n); + +/** + * Insert data to the given position of the buffer. The remaining contents + * will be shifted, not overwritten. + */ extern void strbuf_insert(struct strbuf *, size_t pos, const void *, size_t); + +/** + * Remove given amount of data from a given position of the buffer. + */ extern void strbuf_remove(struct strbuf *, size_t pos, size_t len); -/* splice pos..pos+len with given data */ +/** + * Remove the bytes between `pos..pos+len` and replace it with the given + * data. + */ extern void strbuf_splice(struct strbuf *, size_t pos, size_t len, - const void *, size_t); + const void *, size_t); +/** + * Add a NUL-terminated string to the buffer. Each line will be prepended + * by a comment character and a blank. + */ extern void strbuf_add_commented_lines(struct strbuf *out, const char *buf, size_t size); + +/** + * Add data of given length to the buffer. + */ extern void strbuf_add(struct strbuf *, const void *, size_t); + +/** + * Add a NUL-terminated string to the buffer. + * + * NOTE: This function will *always* be implemented as an inline or a macro + * using strlen, meaning that this is efficient to write things like: + * + * strbuf_addstr(sb, "immediate string"); + * + */ static inline void strbuf_addstr(struct strbuf *sb, const char *s) { strbuf_add(sb, s, strlen(s)); } + +/** + * Copy the contents of another buffer at the end of the current one. + */ static inline void strbuf_addbuf(struct strbuf *sb, const struct strbuf *sb2) { strbuf_grow(sb, sb2->len); strbuf_add(sb, sb2->buf, sb2->len); } + +/** + * Copy part of the buffer from a given position till a given length to the + * end of the buffer. + */ extern void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len); -extern void strbuf_addchars(struct strbuf *sb, int c, size_t n); +/** + * This function can be used to expand a format string containing + * placeholders. To that end, it parses the string and calls the specified + * function for every percent sign found. + * + * The callback function is given a pointer to the character after the `%` + * and a pointer to the struct strbuf. It is expected to add the expanded + * version of the placeholder to the strbuf, e.g. to add a newline + * character if the letter `n` appears after a `%`. The function returns + * the length of the placeholder recognized and `strbuf_expand()` skips + * over it. + * + * The format `%%` is automatically expanded to a single `%` as a quoting + * mechanism; callers do not need to handle the `%` placeholder themselves, + * and the callback function will not be invoked for this placeholder. + * + * All other characters (non-percent and not skipped ones) are copied + * verbatim to the strbuf. If the callback returned zero, meaning that the + * placeholder is unknown, then the percent sign is copied, too. + * + * In order to facilitate caching and to make it possible to give + * parameters to the callback, `strbuf_expand()` passes a context pointer, + * which can be used by the programmer of the callback as she sees fit. + */ typedef size_t (*expand_fn_t) (struct strbuf *sb, const char *placeholder, void *context); extern void strbuf_expand(struct strbuf *sb, const char *format, expand_fn_t fn, void *context); + +/** + * Used as callback for `strbuf_expand()`, expects an array of + * struct strbuf_expand_dict_entry as context, i.e. pairs of + * placeholder and replacement string. The array needs to be + * terminated by an entry with placeholder set to NULL. + */ struct strbuf_expand_dict_entry { const char *placeholder; const char *value; }; extern size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder, void *context); + +/** + * Append the contents of one strbuf to another, quoting any + * percent signs ("%") into double-percents ("%%") in the + * destination. This is useful for literal data to be fed to either + * strbuf_expand or to the *printf family of functions. + */ extern void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src); +/** + * Append the given byte size as a human-readable string (i.e. 12.23 KiB, + * 3.50 MiB). + */ +extern void strbuf_humanise_bytes(struct strbuf *buf, off_t bytes); + +/** + * Add a formatted string to the buffer. + */ __attribute__((format (printf,2,3))) extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...); + +/** + * Add a formatted string prepended by a comment character and a + * blank to the buffer. + */ __attribute__((format (printf, 2, 3))) extern void strbuf_commented_addf(struct strbuf *sb, const char *fmt, ...); + __attribute__((format (printf,2,0))) extern void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap); +/** + * Read a given size of data from a FILE* pointer to the buffer. + * + * NOTE: The buffer is rewound if the read fails. If -1 is returned, + * `errno` must be consulted, like you would do for `read(3)`. + * `strbuf_read()`, `strbuf_read_file()` and `strbuf_getline()` has the + * same behaviour as well. + */ +extern size_t strbuf_fread(struct strbuf *, size_t, FILE *); + +/** + * Read the contents of a given file descriptor. The third argument can be + * used to give a hint about the file size, to avoid reallocs. If read fails, + * any partial read is undone. + */ +extern ssize_t strbuf_read(struct strbuf *, int fd, size_t hint); + +/** + * Read the contents of a file, specified by its path. The third argument + * can be used to give a hint about the file size, to avoid reallocs. + */ +extern int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint); + +/** + * Read the target of a symbolic link, specified by its path. The third + * argument can be used to give a hint about the size, to avoid reallocs. + */ +extern int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint); + +/** + * Read a line from a FILE *, overwriting the existing contents + * of the strbuf. The second argument specifies the line + * terminator character, typically `'\n'`. + * Reading stops after the terminator or at EOF. The terminator + * is removed from the buffer before returning. Returns 0 unless + * there was nothing left before EOF, in which case it returns `EOF`. + */ +extern int strbuf_getline(struct strbuf *, FILE *, int); + +/** + * Like `strbuf_getline`, but keeps the trailing terminator (if + * any) in the buffer. + */ +extern int strbuf_getwholeline(struct strbuf *, FILE *, int); + +/** + * Like `strbuf_getwholeline`, but operates on a file descriptor. + * It reads one character at a time, so it is very slow. Do not + * use it unless you need the correct position in the file + * descriptor. + */ +extern int strbuf_getwholeline_fd(struct strbuf *, int, int); + +/** + * Set the buffer to the path of the current working directory. + */ +extern int strbuf_getcwd(struct strbuf *sb); + +/** + * Add a path to a buffer, converting a relative path to an + * absolute one in the process. Symbolic links are not + * resolved. + */ +extern void strbuf_add_absolute_path(struct strbuf *sb, const char *path); + +/** + * Strip whitespace from a buffer. The second parameter controls if + * comments are considered contents to be removed or not. + */ +extern void stripspace(struct strbuf *buf, int skip_comments); + +static inline int strbuf_strip_suffix(struct strbuf *sb, const char *suffix) +{ + if (strip_suffix_mem(sb->buf, &sb->len, suffix)) { + strbuf_setlen(sb, sb->len); + return 1; + } else + return 0; +} + +/** + * Split str (of length slen) at the specified terminator character. + * Return a null-terminated array of pointers to strbuf objects + * holding the substrings. The substrings include the terminator, + * except for the last substring, which might be unterminated if the + * original string did not end with a terminator. If max is positive, + * then split the string into at most max substrings (with the last + * substring containing everything following the (max-1)th terminator + * character). + * + * The most generic form is `strbuf_split_buf`, which takes an arbitrary + * pointer/len buffer. The `_str` variant takes a NUL-terminated string, + * the `_max` variant takes a strbuf, and just `strbuf_split` is a convenience + * wrapper to drop the `max` parameter. + * + * For lighter-weight alternatives, see string_list_split() and + * string_list_split_in_place(). + */ +extern struct strbuf **strbuf_split_buf(const char *, size_t, + int terminator, int max); + +static inline struct strbuf **strbuf_split_str(const char *str, + int terminator, int max) +{ + return strbuf_split_buf(str, strlen(str), terminator, max); +} + +static inline struct strbuf **strbuf_split_max(const struct strbuf *sb, + int terminator, int max) +{ + return strbuf_split_buf(sb->buf, sb->len, terminator, max); +} + +static inline struct strbuf **strbuf_split(const struct strbuf *sb, + int terminator) +{ + return strbuf_split_max(sb, terminator, 0); +} + +/** + * Free a NULL-terminated list of strbufs (for example, the return + * values of the strbuf_split*() functions). + */ +extern void strbuf_list_free(struct strbuf **); + +/** + * Launch the user preferred editor to edit a file and fill the buffer + * with the file's contents upon the user completing their editing. The + * third argument can be used to set the environment which the editor is + * run in. If the buffer is NULL the editor is launched as usual but the + * file's contents are not read into the buffer upon completion. + */ +extern int launch_editor(const char *path, struct strbuf *buffer, const char *const *env); + extern void strbuf_add_lines(struct strbuf *sb, const char *prefix, const char *buf, size_t size); -/* +/** * Append s to sb, with the characters '<', '>', '&' and '"' converted * into XML entities. */ @@ -170,28 +491,11 @@ static inline void strbuf_complete_line(struct strbuf *sb) strbuf_addch(sb, '\n'); } -extern size_t strbuf_fread(struct strbuf *, size_t, FILE *); -/* XXX: if read fails, any partial read is undone */ -extern ssize_t strbuf_read(struct strbuf *, int fd, size_t hint); -extern int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint); -extern int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint); -extern int strbuf_getcwd(struct strbuf *sb); - -extern int strbuf_getwholeline(struct strbuf *, FILE *, int); -extern int strbuf_getline(struct strbuf *, FILE *, int); -extern int strbuf_getwholeline_fd(struct strbuf *, int, int); - -extern void stripspace(struct strbuf *buf, int skip_comments); -extern int launch_editor(const char *path, struct strbuf *buffer, const char *const *env); - extern int strbuf_branchname(struct strbuf *sb, const char *name); 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); - -extern void strbuf_add_absolute_path(struct strbuf *sb, const char *path); __attribute__((format (printf,1,2))) extern int printf_ln(const char *fmt, ...); @@ -200,7 +504,7 @@ extern int fprintf_ln(FILE *fp, const char *fmt, ...); char *xstrdup_tolower(const char *); -/* +/** * Create a newly allocated string using printf format. You can do this easily * with a strbuf, but this provides a shortcut to save a few lines. */ diff --git a/t/diff-lib.sh b/t/diff-lib.sh index 75a35fcd06..c211dc40ee 100644 --- a/t/diff-lib.sh +++ b/t/diff-lib.sh @@ -1,6 +1,6 @@ : -sanitize_diff_raw='/^:/s/ '"$_x40"' '"$_x40"' \([A-Z]\)[0-9]* / X X \1# /' +sanitize_diff_raw='/^:/s/ '"\($_x40\)"' '"\($_x40\)"' \([A-Z]\)[0-9]* / \1 \2 \3# /' compare_diff_raw () { # When heuristics are improved, the score numbers would change. # Ignore them while comparing. diff --git a/t/diff-lib/COPYING b/t/diff-lib/COPYING new file mode 100644 index 0000000000..6ff87c4664 --- /dev/null +++ b/t/diff-lib/COPYING @@ -0,0 +1,361 @@ + + Note that the only valid version of the GPL as far as this project + is concerned is _this_ particular version of the license (ie v2, not + v2.2 or v3.x or whatever), unless explicitly otherwise stated. + + HOWEVER, in order to allow a migration to GPLv3 if that seems like + a good idea, I also ask that people involved with the project make + their preferences known. In particular, if you trust me to make that + decision, you might note so in your copyright message, ie something + like + + This file is licensed under the GPL v2, or a later version + at the discretion of Linus. + + might avoid issues. But we can also just decide to synchronize and + contact all copyright holders on record if/when the occasion arises. + + Linus Torvalds + +---------------------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/t/diff-lib/README b/t/diff-lib/README new file mode 100644 index 0000000000..548142c327 --- /dev/null +++ b/t/diff-lib/README @@ -0,0 +1,46 @@ +//////////////////////////////////////////////////////////////// + + GIT - the stupid content tracker + +//////////////////////////////////////////////////////////////// + +"git" can mean anything, depending on your mood. + + - random three-letter combination that is pronounceable, and not + actually used by any common UNIX command. The fact that it is a + mispronunciation of "get" may or may not be relevant. + - stupid. contemptible and despicable. simple. Take your pick from the + dictionary of slang. + - "global information tracker": you're in a good mood, and it actually + works for you. Angels sing, and a light suddenly fills the room. + - "goddamn idiotic truckload of sh*t": when it breaks + +Git is a fast, scalable, distributed revision control system with an +unusually rich command set that provides both high-level operations +and full access to internals. + +Git is an Open Source project covered by the GNU General Public License. +It was originally written by Linus Torvalds with help of a group of +hackers around the net. It is currently maintained by Junio C Hamano. + +Please read the file INSTALL for installation instructions. +See Documentation/tutorial.txt to get started, then see +Documentation/everyday.txt for a useful minimum set of commands, +and "man git-commandname" for documentation of each command. +CVS users may also want to read Documentation/cvs-migration.txt. + +Many Git online resources are accessible from http://git.or.cz/ +including full documentation and Git related tools. + +The user discussion and development of Git take place on the Git +mailing list -- everyone is welcome to post bug reports, feature +requests, comments and patches to git@vger.kernel.org. To subscribe +to the list, send an email with just "subscribe git" in the body to +majordomo@vger.kernel.org. The mailing list archives are available at +http://marc.theaimsgroup.com/?l=git and other archival sites. + +The messages titled "A note from the maintainer", "What's in +git.git (stable)" and "What's cooking in git.git (topics)" and +the discussion following them on the mailing list give a good +reference for project status, development direction and +remaining tasks. diff --git a/t/lib-terminal.sh b/t/lib-terminal.sh index 51845491bb..cd220e378e 100644 --- a/t/lib-terminal.sh +++ b/t/lib-terminal.sh @@ -1,7 +1,7 @@ # Helpers for terminal output tests. # Catch tests which should depend on TTY but forgot to. There's no need -# to aditionally check that the TTY prereq is set here. If the test declared +# to additionally check that the TTY prereq is set here. If the test declared # it and we are running the test, then it must have been set. test_terminal () { if ! test_declared_prereq TTY diff --git a/t/t0056-git-C.sh b/t/t0056-git-C.sh index 99c037703a..2630e756da 100755 --- a/t/t0056-git-C.sh +++ b/t/t0056-git-C.sh @@ -14,6 +14,16 @@ test_expect_success '"git -C <path>" runs git from the directory <path>' ' test_cmp expected actual ' +test_expect_success '"git -C <path>" with an empty <path> is a no-op' ' + ( + mkdir -p dir1/subdir && + cd dir1/subdir && + git -C "" rev-parse --show-prefix >actual && + echo subdir/ >expect && + test_cmp expect actual + ) +' + test_expect_success 'Multiple -C options: "-C dir1 -C dir2" is equivalent to "-C dir1/dir2"' ' test_create_repo dir1/dir2 && echo 1 >dir1/dir2/b.txt && diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh index ba4f98e800..f51d0f3cad 100755 --- a/t/t3203-branch-output.sh +++ b/t/t3203-branch-output.sh @@ -96,7 +96,7 @@ test_expect_success 'git branch -v pattern does not show branch summaries' ' test_expect_success 'git branch shows detached HEAD properly' ' cat >expect <<EOF && -* (detached from $(git rev-parse --short HEAD^0)) +* (HEAD detached at $(git rev-parse --short HEAD^0)) branch-one branch-two master @@ -106,4 +106,41 @@ EOF test_i18ncmp expect actual ' +test_expect_success 'git branch shows detached HEAD properly after moving' ' + cat >expect <<EOF && +* (HEAD detached from $(git rev-parse --short HEAD)) + branch-one + branch-two + master +EOF + git reset --hard HEAD^1 && + git branch >actual && + test_i18ncmp expect actual +' + +test_expect_success 'git branch shows detached HEAD properly from tag' ' + cat >expect <<EOF && +* (HEAD detached at fromtag) + branch-one + branch-two + master +EOF + git tag fromtag master && + git checkout fromtag && + git branch >actual && + test_i18ncmp expect actual +' + +test_expect_success 'git branch shows detached HEAD properly after moving from tag' ' + cat >expect <<EOF && +* (HEAD detached from fromtag) + branch-one + branch-two + master +EOF + git reset --hard HEAD^1 && + git branch >actual && + test_i18ncmp expect actual +' + test_done diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index a31f7e0430..034eb35cdf 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -1046,4 +1046,13 @@ test_expect_success 'respect core.abbrev' ' test 4 = $(grep -c "pick [0-9a-f]\{12,\}" todo-list) ' +test_expect_success 'todo count' ' + write_script dump-raw.sh <<-\EOF && + cat "$1" + EOF + test_set_editor "$(pwd)/dump-raw.sh" && + git rebase -i HEAD~4 >actual && + grep "^# Rebase ..* onto ..* ([0-9]" actual +' + test_done diff --git a/t/t3511-cherry-pick-x.sh b/t/t3511-cherry-pick-x.sh index f97727975b..b7dff09d06 100755 --- a/t/t3511-cherry-pick-x.sh +++ b/t/t3511-cherry-pick-x.sh @@ -36,6 +36,20 @@ mesg_with_cherry_footer="$mesg_with_footer_sob (cherry picked from commit da39a3ee5e6b4b0d3255bfef95601890afd80709) Tested-by: C.U. Thor <cuthor@example.com>" +mesg_unclean="$mesg_one_line + + +leading empty lines + + +consecutive empty lines + +# hash tag comment + +trailing empty lines + + +" test_expect_success setup ' git config advice.detachedhead false && @@ -53,6 +67,10 @@ test_expect_success setup ' 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 && + git reset --hard initial && + test_config commit.cleanup verbatim && + test_commit "$mesg_unclean" foo b mesg-unclean && + test_unconfig commit.cleanup && pristine_detach initial && test_commit conflicting unrelated ' @@ -216,4 +234,14 @@ test_expect_success 'cherry-pick -x -s treats "(cherry picked from..." line as p test_cmp expect actual ' +test_expect_success 'cherry-pick preserves commit message' ' + pristine_detach initial && + printf "$mesg_unclean" >expect && + git log -1 --pretty=format:%B mesg-unclean >actual && + test_cmp expect actual && + git cherry-pick mesg-unclean && + git log -1 --pretty=format:%B >actual && + test_cmp expect actual +' + test_done diff --git a/t/t4003-diff-rename-1.sh b/t/t4003-diff-rename-1.sh index bfa8835638..df2accb655 100755 --- a/t/t4003-diff-rename-1.sh +++ b/t/t4003-diff-rename-1.sh @@ -11,7 +11,7 @@ test_description='More rename detection test_expect_success \ 'prepare reference tree' \ - 'cat "$TEST_DIRECTORY"/../COPYING >COPYING && + 'cat "$TEST_DIRECTORY"/diff-lib/COPYING >COPYING && echo frotz >rezrov && git update-index --add COPYING rezrov && tree=$(git write-tree) && @@ -99,7 +99,7 @@ test_expect_success \ test_expect_success \ 'prepare work tree once again' \ - 'cat "$TEST_DIRECTORY"/../COPYING >COPYING && + 'cat "$TEST_DIRECTORY"/diff-lib/COPYING >COPYING && git update-index --add --remove COPYING COPYING.1' # tree has COPYING and rezrov. work tree has COPYING and COPYING.1, diff --git a/t/t4005-diff-rename-2.sh b/t/t4005-diff-rename-2.sh index 7d2c6e13a2..135addbfbd 100755 --- a/t/t4005-diff-rename-2.sh +++ b/t/t4005-diff-rename-2.sh @@ -11,7 +11,7 @@ test_description='Same rename detection as t4003 but testing diff-raw. test_expect_success \ 'prepare reference tree' \ - 'cat "$TEST_DIRECTORY"/../COPYING >COPYING && + 'cat "$TEST_DIRECTORY"/diff-lib/COPYING >COPYING && echo frotz >rezrov && git update-index --add COPYING rezrov && tree=$(git write-tree) && @@ -71,7 +71,7 @@ test_expect_success \ test_expect_success \ 'prepare work tree once again' \ - 'cat "$TEST_DIRECTORY"/../COPYING >COPYING && + 'cat "$TEST_DIRECTORY"/diff-lib/COPYING >COPYING && git update-index --add --remove COPYING COPYING.1' git diff-index -C --find-copies-harder $tree >current diff --git a/t/t4007-rename-3.sh b/t/t4007-rename-3.sh index 11502b7509..dae327fabb 100755 --- a/t/t4007-rename-3.sh +++ b/t/t4007-rename-3.sh @@ -11,7 +11,7 @@ test_description='Rename interaction with pathspec. test_expect_success 'prepare reference tree' ' mkdir path0 path1 && - cp "$TEST_DIRECTORY"/../COPYING path0/COPYING && + cp "$TEST_DIRECTORY"/diff-lib/COPYING path0/COPYING && git update-index --add path0/COPYING && tree=$(git write-tree) && echo $tree diff --git a/t/t4008-diff-break-rewrite.sh b/t/t4008-diff-break-rewrite.sh index 8920464896..9dd1bc5e16 100755 --- a/t/t4008-diff-break-rewrite.sh +++ b/t/t4008-diff-break-rewrite.sh @@ -10,179 +10,145 @@ We have two very different files, file0 and file1, registered in a tree. We update file1 so drastically that it is more similar to file0, and then remove file0. With -B, changes to file1 should be broken into separate delete and create, resulting in removal of file0, removal of -original file1 and creation of completely rewritten file1. +original file1 and creation of completely rewritten file1. The latter +two are then merged back into a single "complete rewrite". Further, with -B and -M together, these three modifications should turn into rename-edit of file0 into file1. Starting from the same two files in the tree, we swap file0 and file1. -With -B, this should be detected as two complete rewrites, resulting in -four changes in total. +With -B, this should be detected as two complete rewrites. Further, with -B and -M together, these should turn into two renames. ' . ./test-lib.sh . "$TEST_DIRECTORY"/diff-lib.sh ;# test-lib chdir's into trash -test_expect_success \ - setup \ - 'cat "$TEST_DIRECTORY"/../README >file0 && - cat "$TEST_DIRECTORY"/../COPYING >file1 && - git update-index --add file0 file1 && - tree=$(git write-tree) && - echo "$tree"' - -test_expect_success \ - 'change file1 with copy-edit of file0 and remove file0' \ - 'sed -e "s/git/GIT/" file0 >file1 && - rm -f file0 && - git update-index --remove file0 file1' - -test_expect_success \ - 'run diff with -B' \ - 'git diff-index -B --cached "$tree" >current' - -cat >expected <<\EOF -:100644 000000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 0000000000000000000000000000000000000000 D file0 -:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 11e331465a89c394dc25c780de230043750c1ec8 M100 file1 -EOF - -test_expect_success \ - 'validate result of -B (#1)' \ - 'compare_diff_raw expected current' - -test_expect_success \ - 'run diff with -B and -M' \ - 'git diff-index -B -M "$tree" >current' - -cat >expected <<\EOF -:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 08bb2fb671deff4c03a4d4a0a1315dff98d5732c R100 file0 file1 -EOF - -test_expect_success \ - 'validate result of -B -M (#2)' \ - 'compare_diff_raw expected current' - -test_expect_success \ - 'swap file0 and file1' \ - 'rm -f file0 file1 && - git read-tree -m $tree && - git checkout-index -f -u -a && - mv file0 tmp && - mv file1 file0 && - mv tmp file1 && - git update-index file0 file1' - -test_expect_success \ - 'run diff with -B' \ - 'git diff-index -B "$tree" >current' - -cat >expected <<\EOF -:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 6ff87c4664981e4397625791c8ea3bbb5f2279a3 M100 file0 -:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M100 file1 -EOF - -test_expect_success \ - 'validate result of -B (#3)' \ - 'compare_diff_raw expected current' - -test_expect_success \ - 'run diff with -B and -M' \ - 'git diff-index -B -M "$tree" >current' - -cat >expected <<\EOF -:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 R100 file1 file0 -:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 R100 file0 file1 -EOF - -test_expect_success \ - 'validate result of -B -M (#4)' \ - 'compare_diff_raw expected current' - -test_expect_success \ - 'make file0 into something completely different' \ - 'rm -f file0 && - test_ln_s_add frotz file0 && - git update-index file1' - -test_expect_success \ - 'run diff with -B' \ - 'git diff-index -B "$tree" >current' - -cat >expected <<\EOF -:100644 120000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 67be421f88824578857624f7b3dc75e99a8a1481 T file0 -:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M100 file1 -EOF - -test_expect_success \ - 'validate result of -B (#5)' \ - 'compare_diff_raw expected current' - -test_expect_success \ - 'run diff with -B -M' \ - 'git diff-index -B -M "$tree" >current' - -# file0 changed from regular to symlink. file1 is very close to the preimage of file0. -# the change does not make file0 disappear, so file1 is denoted as a copy of file0 -cat >expected <<\EOF -:100644 120000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 67be421f88824578857624f7b3dc75e99a8a1481 T file0 -:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 C file0 file1 -EOF - -test_expect_success \ - 'validate result of -B -M (#6)' \ - 'compare_diff_raw expected current' - -test_expect_success \ - 'run diff with -M' \ - 'git diff-index -M "$tree" >current' - -# This should not mistake file0 as the copy source of new file1 -# due to type differences. -cat >expected <<\EOF -:100644 120000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 67be421f88824578857624f7b3dc75e99a8a1481 T file0 -:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M file1 -EOF - -test_expect_success \ - 'validate result of -M (#7)' \ - 'compare_diff_raw expected current' - -test_expect_success \ - 'file1 edited to look like file0 and file0 rename-edited to file2' \ - 'rm -f file0 file1 && - git read-tree -m $tree && - git checkout-index -f -u -a && - sed -e "s/git/GIT/" file0 >file1 && - sed -e "s/git/GET/" file0 >file2 && - rm -f file0 && - git update-index --add --remove file0 file1 file2' - -test_expect_success \ - 'run diff with -B' \ - 'git diff-index -B "$tree" >current' - -cat >expected <<\EOF -:100644 000000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 0000000000000000000000000000000000000000 D file0 -:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 08bb2fb671deff4c03a4d4a0a1315dff98d5732c M100 file1 -:000000 100644 0000000000000000000000000000000000000000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 A file2 -EOF - -test_expect_success \ - 'validate result of -B (#8)' \ - 'compare_diff_raw expected current' - -test_expect_success \ - 'run diff with -B -C' \ - 'git diff-index -B -C "$tree" >current' - -cat >expected <<\EOF -:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 08bb2fb671deff4c03a4d4a0a1315dff98d5732c C095 file0 file1 -:100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 59f832e5c8b3f7e486be15ad0cd3e95ba9af8998 R095 file0 file2 -EOF - -test_expect_success \ - 'validate result of -B -M (#9)' \ - 'compare_diff_raw expected current' +test_expect_success setup ' + cat "$TEST_DIRECTORY"/diff-lib/README >file0 && + cat "$TEST_DIRECTORY"/diff-lib/COPYING >file1 && + git update-index --add file0 file1 && + git tag reference $(git write-tree) +' + +test_expect_success 'change file1 with copy-edit of file0 and remove file0' ' + sed -e "s/git/GIT/" file0 >file1 && + rm -f file0 && + git update-index --remove file0 file1 +' + +test_expect_success 'run diff with -B (#1)' ' + git diff-index -B --cached reference >current && + cat >expect <<-\EOF && + :100644 000000 548142c327a6790ff8821d67c2ee1eff7a656b52 0000000000000000000000000000000000000000 D file0 + :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 2fbedd0b5d4b8126e4750c3bee305e8ff79f80ec M100 file1 + EOF + compare_diff_raw expect current +' + +test_expect_success 'run diff with -B and -M (#2)' ' + git diff-index -B -M reference >current && + cat >expect <<-\EOF && + :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 2fbedd0b5d4b8126e4750c3bee305e8ff79f80ec R100 file0 file1 + EOF + compare_diff_raw expect current +' + +test_expect_success 'swap file0 and file1' ' + rm -f file0 file1 && + git read-tree -m reference && + git checkout-index -f -u -a && + mv file0 tmp && + mv file1 file0 && + mv tmp file1 && + git update-index file0 file1 +' + +test_expect_success 'run diff with -B (#3)' ' + git diff-index -B reference >current && + cat >expect <<-\EOF && + :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 6ff87c4664981e4397625791c8ea3bbb5f2279a3 M100 file0 + :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 548142c327a6790ff8821d67c2ee1eff7a656b52 M100 file1 + EOF + compare_diff_raw expect current +' + +test_expect_success 'run diff with -B and -M (#4)' ' + git diff-index -B -M reference >current && + cat >expect <<-\EOF && + :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 R100 file1 file0 + :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 548142c327a6790ff8821d67c2ee1eff7a656b52 R100 file0 file1 + EOF + compare_diff_raw expect current +' + +test_expect_success 'make file0 into something completely different' ' + rm -f file0 && + test_ln_s_add frotz file0 && + git update-index file1 +' + +test_expect_success 'run diff with -B (#5)' ' + git diff-index -B reference >current && + cat >expect <<-\EOF && + :100644 120000 548142c327a6790ff8821d67c2ee1eff7a656b52 67be421f88824578857624f7b3dc75e99a8a1481 T file0 + :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 548142c327a6790ff8821d67c2ee1eff7a656b52 M100 file1 + EOF + compare_diff_raw expect current +' + +test_expect_success 'run diff with -B -M (#6)' ' + git diff-index -B -M reference >current && + + # file0 changed from regular to symlink. file1 is the same as the preimage + # of file0. Because the change does not make file0 disappear, file1 is + # denoted as a copy of file0 + cat >expect <<-\EOF && + :100644 120000 548142c327a6790ff8821d67c2ee1eff7a656b52 67be421f88824578857624f7b3dc75e99a8a1481 T file0 + :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 548142c327a6790ff8821d67c2ee1eff7a656b52 C file0 file1 + EOF + compare_diff_raw expect current +' + +test_expect_success 'run diff with -M (#7)' ' + git diff-index -M reference >current && + + # This should not mistake file0 as the copy source of new file1 + # due to type differences. + cat >expect <<-\EOF && + :100644 120000 548142c327a6790ff8821d67c2ee1eff7a656b52 67be421f88824578857624f7b3dc75e99a8a1481 T file0 + :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 548142c327a6790ff8821d67c2ee1eff7a656b52 M file1 + EOF + compare_diff_raw expect current +' + +test_expect_success 'file1 edited to look like file0 and file0 rename-edited to file2' ' + rm -f file0 file1 && + git read-tree -m reference && + git checkout-index -f -u -a && + sed -e "s/git/GIT/" file0 >file1 && + sed -e "s/git/GET/" file0 >file2 && + rm -f file0 && + git update-index --add --remove file0 file1 file2 +' + +test_expect_success 'run diff with -B (#8)' ' + git diff-index -B reference >current && + cat >expect <<-\EOF && + :100644 000000 548142c327a6790ff8821d67c2ee1eff7a656b52 0000000000000000000000000000000000000000 D file0 + :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 2fbedd0b5d4b8126e4750c3bee305e8ff79f80ec M100 file1 + :000000 100644 0000000000000000000000000000000000000000 69a939f651686f56322566e2fd76715947a24162 A file2 + EOF + compare_diff_raw expect current +' + +test_expect_success 'run diff with -B -C (#9)' ' + git diff-index -B -C reference >current && + cat >expect <<-\EOF && + :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 2fbedd0b5d4b8126e4750c3bee305e8ff79f80ec C095 file0 file1 + :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 69a939f651686f56322566e2fd76715947a24162 R095 file0 file2 + EOF + compare_diff_raw expect current +' test_done diff --git a/t/t4009-diff-rename-4.sh b/t/t4009-diff-rename-4.sh index 57c094fdce..3641fd84d6 100755 --- a/t/t4009-diff-rename-4.sh +++ b/t/t4009-diff-rename-4.sh @@ -11,7 +11,7 @@ test_description='Same rename detection as t4003 but testing diff-raw -z. test_expect_success \ 'prepare reference tree' \ - 'cat "$TEST_DIRECTORY"/../COPYING >COPYING && + 'cat "$TEST_DIRECTORY"/diff-lib/COPYING >COPYING && echo frotz >rezrov && git update-index --add COPYING rezrov && tree=$(git write-tree) && @@ -78,7 +78,7 @@ test_expect_success \ test_expect_success \ 'prepare work tree once again' \ - 'cat "$TEST_DIRECTORY"/../COPYING >COPYING && + 'cat "$TEST_DIRECTORY"/diff-lib/COPYING >COPYING && git update-index --add --remove COPYING COPYING.1' git diff-index -z -C --find-copies-harder $tree >current diff --git a/t/t4010-diff-pathspec.sh b/t/t4010-diff-pathspec.sh index bf07841866..43c488b545 100755 --- a/t/t4010-diff-pathspec.sh +++ b/t/t4010-diff-pathspec.sh @@ -56,7 +56,7 @@ test_expect_success \ compare_diff_raw current expected' cat >expected <<\EOF -:100644 100644 766498d93a4b06057a8e49d23f4068f1170ff38f 0a41e115ab61be0328a19b29f18cdcb49338d516 M file0 +:100644 100644 8e4020bb5a8d8c873b25de15933e75cc0fc275df dca6b92303befc93086aa025d90a5facd7eb2812 M file0 EOF test_expect_success \ 'limit to file0 should show file0' \ diff --git a/t/t4047-diff-dirstat.sh b/t/t4047-diff-dirstat.sh index ed7e093366..0d50dce97e 100755 --- a/t/t4047-diff-dirstat.sh +++ b/t/t4047-diff-dirstat.sh @@ -973,4 +973,18 @@ test_expect_success 'diff.dirstat=future_param,0,lines should warn, but still wo test_i18ngrep -q "diff\\.dirstat" actual_error ' +test_expect_success '--shortstat --dirstat should output only one dirstat' ' + git diff --shortstat --dirstat=changes HEAD^..HEAD >out && + grep " dst/copy/changed/$" out >actual_diff_shortstat_dirstat_changes && + test_line_count = 1 actual_diff_shortstat_dirstat_changes && + + git diff --shortstat --dirstat=lines HEAD^..HEAD >out && + grep " dst/copy/changed/$" out >actual_diff_shortstat_dirstat_lines && + test_line_count = 1 actual_diff_shortstat_dirstat_lines && + + git diff --shortstat --dirstat=files HEAD^..HEAD >out && + grep " dst/copy/changed/$" out >actual_diff_shortstat_dirstat_files && + test_line_count = 1 actual_diff_shortstat_dirstat_files +' + test_done diff --git a/t/t4058-diff-duplicates.sh b/t/t4058-diff-duplicates.sh new file mode 100755 index 0000000000..0a23242cb6 --- /dev/null +++ b/t/t4058-diff-duplicates.sh @@ -0,0 +1,79 @@ +#!/bin/sh + +test_description='test tree diff when trees have duplicate entries' +. ./test-lib.sh + +# make_tree_entry <mode> <mode> <sha1> +# +# We have to rely on perl here because not all printfs understand +# hex escapes (only octal), and xxd is not portable. +make_tree_entry () { + printf '%s %s\0' "$1" "$2" && + perl -e 'print chr(hex($_)) for ($ARGV[0] =~ /../g)' "$3" +} + +# Like git-mktree, but without all of the pesky sanity checking. +# Arguments come in groups of three, each group specifying a single +# tree entry (see make_tree_entry above). +make_tree () { + while test $# -gt 2; do + make_tree_entry "$1" "$2" "$3" + shift; shift; shift + done | + git hash-object -w -t tree --stdin +} + +# this is kind of a convoluted setup, but matches +# a real-world case. Each tree contains four entries +# for the given path, one with one sha1, and three with +# the other. The first tree has them split across +# two subtrees (which are themselves duplicate entries in +# the root tree), and the second has them all in a single subtree. +test_expect_success 'create trees with duplicate entries' ' + blob_one=$(echo one | git hash-object -w --stdin) && + blob_two=$(echo two | git hash-object -w --stdin) && + inner_one_a=$(make_tree \ + 100644 inner $blob_one + ) && + inner_one_b=$(make_tree \ + 100644 inner $blob_two \ + 100644 inner $blob_two \ + 100644 inner $blob_two + ) && + outer_one=$(make_tree \ + 040000 outer $inner_one_a \ + 040000 outer $inner_one_b + ) && + inner_two=$(make_tree \ + 100644 inner $blob_one \ + 100644 inner $blob_two \ + 100644 inner $blob_two \ + 100644 inner $blob_two + ) && + outer_two=$(make_tree \ + 040000 outer $inner_two + ) && + git tag one $outer_one && + git tag two $outer_two +' + +test_expect_success 'diff-tree between trees' ' + { + printf ":000000 100644 $_z40 $blob_two A\touter/inner\n" && + printf ":000000 100644 $_z40 $blob_two A\touter/inner\n" && + printf ":000000 100644 $_z40 $blob_two A\touter/inner\n" && + printf ":100644 000000 $blob_two $_z40 D\touter/inner\n" && + printf ":100644 000000 $blob_two $_z40 D\touter/inner\n" && + printf ":100644 000000 $blob_two $_z40 D\touter/inner\n" + } >expect && + git diff-tree -r --no-abbrev one two >actual && + test_cmp expect actual +' + +test_expect_success 'diff-tree with renames' ' + # same expectation as above, since we disable rename detection + git diff-tree -M -r --no-abbrev one two >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t4122-apply-symlink-inside.sh b/t/t4122-apply-symlink-inside.sh index 70b3a06e1d..4acb3f336e 100755 --- a/t/t4122-apply-symlink-inside.sh +++ b/t/t4122-apply-symlink-inside.sh @@ -3,17 +3,10 @@ test_description='apply to deeper directory without getting fooled with symlink' . ./test-lib.sh -lecho () { - for l_ - do - echo "$l_" - done -} - test_expect_success setup ' mkdir -p arch/i386/boot arch/x86_64 && - lecho 1 2 3 4 5 >arch/i386/boot/Makefile && + test_write_lines 1 2 3 4 5 >arch/i386/boot/Makefile && test_ln_s_add ../i386/boot arch/x86_64/boot && git add . && test_tick && @@ -22,7 +15,7 @@ test_expect_success setup ' rm arch/x86_64/boot && mkdir arch/x86_64/boot && - lecho 2 3 4 5 6 >arch/x86_64/boot/Makefile && + test_write_lines 2 3 4 5 6 >arch/x86_64/boot/Makefile && git add . && test_tick && git commit -a -m second && @@ -52,4 +45,110 @@ test_expect_success 'check result' ' ' +test_expect_success SYMLINKS 'do not read from beyond symbolic link' ' + git reset --hard && + mkdir -p arch/x86_64/dir && + >arch/x86_64/dir/file && + git add arch/x86_64/dir/file && + echo line >arch/x86_64/dir/file && + git diff >patch && + git reset --hard && + + mkdir arch/i386/dir && + >arch/i386/dir/file && + ln -s ../i386/dir arch/x86_64/dir && + + test_must_fail git apply patch && + test_must_fail git apply --cached patch && + test_must_fail git apply --index patch + +' + +test_expect_success SYMLINKS 'do not follow symbolic link (setup)' ' + + rm -rf arch/i386/dir arch/x86_64/dir && + git reset --hard && + ln -s ../i386/dir arch/x86_64/dir && + git add arch/x86_64/dir && + git diff HEAD >add_symlink.patch && + git reset --hard && + + mkdir arch/x86_64/dir && + >arch/x86_64/dir/file && + git add arch/x86_64/dir/file && + git diff HEAD >add_file.patch && + git diff -R HEAD >del_file.patch && + git reset --hard && + rm -fr arch/x86_64/dir && + + cat add_symlink.patch add_file.patch >patch && + cat add_symlink.patch del_file.patch >tricky_del && + + mkdir arch/i386/dir +' + +test_expect_success SYMLINKS 'do not follow symbolic link (same input)' ' + + # same input creates a confusing symbolic link + test_must_fail git apply patch 2>error-wt && + test_i18ngrep "beyond a symbolic link" error-wt && + test_path_is_missing arch/x86_64/dir && + test_path_is_missing arch/i386/dir/file && + + test_must_fail git apply --index patch 2>error-ix && + test_i18ngrep "beyond a symbolic link" error-ix && + test_path_is_missing arch/x86_64/dir && + test_path_is_missing arch/i386/dir/file && + test_must_fail git ls-files --error-unmatch arch/x86_64/dir && + test_must_fail git ls-files --error-unmatch arch/i386/dir && + + test_must_fail git apply --cached patch 2>error-ct && + test_i18ngrep "beyond a symbolic link" error-ct && + test_must_fail git ls-files --error-unmatch arch/x86_64/dir && + test_must_fail git ls-files --error-unmatch arch/i386/dir && + + >arch/i386/dir/file && + git add arch/i386/dir/file && + + test_must_fail git apply tricky_del && + test_path_is_file arch/i386/dir/file && + + test_must_fail git apply --index tricky_del && + test_path_is_file arch/i386/dir/file && + test_must_fail git ls-files --error-unmatch arch/x86_64/dir && + git ls-files --error-unmatch arch/i386/dir && + + test_must_fail git apply --cached tricky_del && + test_must_fail git ls-files --error-unmatch arch/x86_64/dir && + git ls-files --error-unmatch arch/i386/dir +' + +test_expect_success SYMLINKS 'do not follow symbolic link (existing)' ' + + # existing symbolic link + git reset --hard && + ln -s ../i386/dir arch/x86_64/dir && + git add arch/x86_64/dir && + + test_must_fail git apply add_file.patch 2>error-wt-add && + test_i18ngrep "beyond a symbolic link" error-wt-add && + test_path_is_missing arch/i386/dir/file && + + mkdir arch/i386/dir && + >arch/i386/dir/file && + test_must_fail git apply del_file.patch 2>error-wt-del && + test_i18ngrep "beyond a symbolic link" error-wt-del && + test_path_is_file arch/i386/dir/file && + rm arch/i386/dir/file && + + test_must_fail git apply --index add_file.patch 2>error-ix-add && + test_i18ngrep "beyond a symbolic link" error-ix-add && + test_path_is_missing arch/i386/dir/file && + test_must_fail git ls-files --error-unmatch arch/i386/dir && + + test_must_fail git apply --cached add_file.patch 2>error-ct-file && + test_i18ngrep "beyond a symbolic link" error-ct-file && + test_must_fail git ls-files --error-unmatch arch/i386/dir +' + test_done diff --git a/t/t4139-apply-escape.sh b/t/t4139-apply-escape.sh new file mode 100755 index 0000000000..45b5660a47 --- /dev/null +++ b/t/t4139-apply-escape.sh @@ -0,0 +1,141 @@ +#!/bin/sh + +test_description='paths written by git-apply cannot escape the working tree' +. ./test-lib.sh + +# tests will try to write to ../foo, and we do not +# want them to escape the trash directory when they +# fail +test_expect_success 'bump git repo one level down' ' + mkdir inside && + mv .git inside/ && + cd inside +' + +# $1 = name of file +# $2 = current path to file (if different) +mkpatch_add () { + rm -f "${2:-$1}" && + cat <<-EOF + diff --git a/$1 b/$1 + new file mode 100644 + index 0000000..53c74cd + --- /dev/null + +++ b/$1 + @@ -0,0 +1 @@ + +evil + EOF +} + +mkpatch_del () { + echo evil >"${2:-$1}" && + cat <<-EOF + diff --git a/$1 b/$1 + deleted file mode 100644 + index 53c74cd..0000000 + --- a/$1 + +++ /dev/null + @@ -1 +0,0 @@ + -evil + EOF +} + +# $1 = name of file +# $2 = content of symlink +mkpatch_symlink () { + rm -f "$1" && + cat <<-EOF + diff --git a/$1 b/$1 + new file mode 120000 + index 0000000..$(printf "%s" "$2" | git hash-object --stdin) + --- /dev/null + +++ b/$1 + @@ -0,0 +1 @@ + +$2 + \ No newline at end of file + EOF +} + +test_expect_success 'cannot create file containing ..' ' + mkpatch_add ../foo >patch && + test_must_fail git apply patch && + test_path_is_missing ../foo +' + +test_expect_success 'can create file containing .. with --unsafe-paths' ' + mkpatch_add ../foo >patch && + git apply --unsafe-paths patch && + test_path_is_file ../foo +' + +test_expect_success 'cannot create file containing .. (index)' ' + mkpatch_add ../foo >patch && + test_must_fail git apply --index patch && + test_path_is_missing ../foo +' + +test_expect_success 'cannot create file containing .. with --unsafe-paths (index)' ' + mkpatch_add ../foo >patch && + test_must_fail git apply --index --unsafe-paths patch && + test_path_is_missing ../foo +' + +test_expect_success 'cannot delete file containing ..' ' + mkpatch_del ../foo >patch && + test_must_fail git apply patch && + test_path_is_file ../foo +' + +test_expect_success 'can delete file containing .. with --unsafe-paths' ' + mkpatch_del ../foo >patch && + git apply --unsafe-paths patch && + test_path_is_missing ../foo +' + +test_expect_success 'cannot delete file containing .. (index)' ' + mkpatch_del ../foo >patch && + test_must_fail git apply --index patch && + test_path_is_file ../foo +' + +test_expect_success SYMLINKS 'symlink escape via ..' ' + { + mkpatch_symlink tmp .. && + mkpatch_add tmp/foo ../foo + } >patch && + test_must_fail git apply patch && + test_path_is_missing tmp && + test_path_is_missing ../foo +' + +test_expect_success SYMLINKS 'symlink escape via .. (index)' ' + { + mkpatch_symlink tmp .. && + mkpatch_add tmp/foo ../foo + } >patch && + test_must_fail git apply --index patch && + test_path_is_missing tmp && + test_path_is_missing ../foo +' + +test_expect_success SYMLINKS 'symlink escape via absolute path' ' + { + mkpatch_symlink tmp "$(pwd)" && + mkpatch_add tmp/foo ../foo + } >patch && + test_must_fail git apply patch && + test_path_is_missing tmp && + test_path_is_missing ../foo +' + +test_expect_success SYMLINKS 'symlink escape via absolute path (index)' ' + { + mkpatch_symlink tmp "$(pwd)" && + mkpatch_add tmp/foo ../foo + } >patch && + test_must_fail git apply --index patch && + test_path_is_missing tmp && + test_path_is_missing ../foo +' + +test_done diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 99ab7ca21f..5f2b290d2b 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -212,6 +212,21 @@ test_expect_success 'log --grep' ' test_cmp expect actual ' +cat > expect << EOF +second +initial +EOF +test_expect_success 'log --invert-grep --grep' ' + git log --pretty="tformat:%s" --invert-grep --grep=th --grep=Sec >actual && + test_cmp expect actual +' + +test_expect_success 'log --invert-grep --grep -i' ' + echo initial >expect && + git log --pretty="tformat:%s" --invert-grep -i --grep=th --grep=Sec >actual && + test_cmp expect actual +' + test_expect_success 'log --grep option parsing' ' echo second >expect && git log -1 --pretty="tformat:%s" --grep sec >actual && diff --git a/t/t5003-archive-zip.sh b/t/t5003-archive-zip.sh index c929db5633..14744b2a4b 100755 --- a/t/t5003-archive-zip.sh +++ b/t/t5003-archive-zip.sh @@ -33,6 +33,37 @@ check_zip() { test_expect_success UNZIP " validate file contents" " diff -r a ${dir_with_prefix}a " + + dir=eol_$1 + dir_with_prefix=$dir/$2 + extracted=${dir_with_prefix}a + original=a + + test_expect_success UNZIP " extract ZIP archive with EOL conversion" ' + (mkdir $dir && cd $dir && "$GIT_UNZIP" -a ../$zipfile) + ' + + test_expect_success UNZIP " validate that text files are converted" " + test_cmp_bin $extracted/text.cr $extracted/text.crlf && + test_cmp_bin $extracted/text.cr $extracted/text.lf + " + + test_expect_success UNZIP " validate that binary files are unchanged" " + test_cmp_bin $original/binary.cr $extracted/binary.cr && + test_cmp_bin $original/binary.crlf $extracted/binary.crlf && + test_cmp_bin $original/binary.lf $extracted/binary.lf + " + + test_expect_success UNZIP " validate that diff files are converted" " + test_cmp_bin $extracted/diff.cr $extracted/diff.crlf && + test_cmp_bin $extracted/diff.cr $extracted/diff.lf + " + + test_expect_success UNZIP " validate that -diff files are unchanged" " + test_cmp_bin $original/nodiff.cr $extracted/nodiff.cr && + test_cmp_bin $original/nodiff.crlf $extracted/nodiff.crlf && + test_cmp_bin $original/nodiff.lf $extracted/nodiff.lf + " } test_expect_success \ @@ -41,6 +72,18 @@ test_expect_success \ echo simple textfile >a/a && mkdir a/bin && cp /bin/sh a/bin && + printf "text\r" >a/text.cr && + printf "text\r\n" >a/text.crlf && + printf "text\n" >a/text.lf && + printf "text\r" >a/nodiff.cr && + printf "text\r\n" >a/nodiff.crlf && + printf "text\n" >a/nodiff.lf && + printf "\0\r" >a/binary.cr && + printf "\0\r\n" >a/binary.crlf && + printf "\0\n" >a/binary.lf && + printf "\0\r" >a/diff.cr && + printf "\0\r\n" >a/diff.crlf && + printf "\0\n" >a/diff.lf && printf "A\$Format:%s\$O" "$SUBSTFORMAT" >a/substfile1 && printf "A not substituted O" >a/substfile2 && (p=long_path_to_a_file && cd a && @@ -66,7 +109,9 @@ test_expect_success 'add files to repository' ' GIT_COMMITTER_DATE="2005-05-27 22:00" git commit -m initial ' -test_expect_success 'setup export-subst' ' +test_expect_success 'setup export-subst and diff attributes' ' + echo "a/nodiff.* -diff" >>.git/info/attributes && + echo "a/diff.* diff" >>.git/info/attributes && echo "substfile?" export-subst >>.git/info/attributes && git log --max-count=1 "--pretty=format:A${SUBSTFORMAT}O" HEAD \ >a/substfile1 diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh index 5b2b1c2c13..bd37f040b6 100755 --- a/t/t5500-fetch-pack.sh +++ b/t/t5500-fetch-pack.sh @@ -541,13 +541,30 @@ check_prot_path () { test_cmp expected actual } -check_prot_host_path () { - cat >expected <<-EOF && +check_prot_host_port_path () { + local diagport + case "$2" in + *ssh*) + pp=ssh + uah=userandhost + ehost=$(echo $3 | tr -d "[]") + diagport="Diag: port=$4" + ;; + *) + pp=$p + uah=hostandport + ehost=$(echo $3$4 | sed -e "s/22$/:22/" -e "s/NONE//") + diagport="" + ;; + esac + cat >exp <<-EOF && Diag: url=$1 - Diag: protocol=$2 - Diag: hostandport=$3 - Diag: path=$4 + Diag: protocol=$pp + Diag: $uah=$ehost + $diagport + Diag: path=$5 EOF + grep -v "^$" exp >expected git fetch-pack --diag-url "$1" >actual && test_cmp expected actual } @@ -557,22 +574,20 @@ do # git or ssh with scheme for p in "ssh+git" "git+ssh" git ssh do - for h in host host:12 [::1] [::1]:23 + for h in host user@host user@[::1] user@::1 do - case "$p" in - *ssh*) - pp=ssh - ;; - *) - pp=$p - ;; - esac test_expect_success "fetch-pack --diag-url $p://$h/$r" ' - check_prot_host_path $p://$h/$r $pp "$h" "/$r" + check_prot_host_port_path $p://$h/$r $p "$h" NONE "/$r" ' # "/~" -> "~" conversion test_expect_success "fetch-pack --diag-url $p://$h/~$r" ' - check_prot_host_path $p://$h/~$r $pp "$h" "~$r" + check_prot_host_port_path $p://$h/~$r $p "$h" NONE "~$r" + ' + done + for h in host User@host User@[::1] + do + test_expect_success "fetch-pack --diag-url $p://$h:22/$r" ' + check_prot_host_port_path $p://$h:22/$r $p "$h" 22 "/$r" ' done done @@ -603,11 +618,11 @@ do for h in host [::1] do test_expect_success "fetch-pack --diag-url $h:$r" ' - check_prot_path $h:$r $p "$r" + check_prot_host_port_path $h:$r $p "$h" NONE "$r" ' # Do "/~" -> "~" conversion test_expect_success "fetch-pack --diag-url $h:/~$r" ' - check_prot_host_path $h:/~$r $p "$h" "~$r" + check_prot_host_port_path $h:/~$r $p "$h" NONE "~$r" ' done done diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 85c7fecd22..630885d6df 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -238,7 +238,7 @@ test_expect_success 'push with pushInsteadOf' ' test_expect_success 'push with pushInsteadOf and explicit pushurl (pushInsteadOf should not rewrite)' ' mk_empty testrepo && test_config "url.trash2/.pushInsteadOf" testrepo/ && - test_config "url.trash3/.pusnInsteadOf" trash/wrong && + test_config "url.trash3/.pushInsteadOf" 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 && @@ -1434,4 +1434,67 @@ test_expect_success 'receive.denyCurrentBranch = updateInstead' ' ' +test_expect_success 'updateInstead with push-to-checkout hook' ' + rm -fr testrepo && + git init testrepo && + ( + cd testrepo && + git pull .. master && + git reset --hard HEAD^^ && + git tag initial && + git config receive.denyCurrentBranch updateInstead && + write_script .git/hooks/push-to-checkout <<-\EOF + echo >&2 updating from $(git rev-parse HEAD) + echo >&2 updating to "$1" + + git update-index -q --refresh && + git read-tree -u -m HEAD "$1" || { + status=$? + echo >&2 read-tree failed + exit $status + } + EOF + ) && + + # Try pushing into a pristine + git push testrepo master && + ( + cd testrepo && + git diff --quiet && + git diff HEAD --quiet && + test $(git -C .. rev-parse HEAD) = $(git rev-parse HEAD) + ) && + + # Try pushing into a repository with conflicting change + ( + cd testrepo && + git reset --hard initial && + echo conflicting >path2 + ) && + test_must_fail git push testrepo master && + ( + cd testrepo && + test $(git rev-parse initial) = $(git rev-parse HEAD) && + test conflicting = "$(cat path2)" && + git diff-index --quiet --cached HEAD + ) && + + # Try pushing into a repository with unrelated change + ( + cd testrepo && + git reset --hard initial && + echo unrelated >path1 && + echo irrelevant >path5 && + git add path5 + ) && + git push testrepo master && + ( + cd testrepo && + test "$(cat path1)" = unrelated && + test "$(cat path5)" = irrelevant && + test "$(git diff --name-only --cached HEAD)" = path5 && + test $(git -C .. rev-parse HEAD) = $(git rev-parse HEAD) + ) +' + test_done diff --git a/t/t5528-push-default.sh b/t/t5528-push-default.sh index cc7451908b..73f4bb6346 100755 --- a/t/t5528-push-default.sh +++ b/t/t5528-push-default.sh @@ -26,7 +26,7 @@ check_pushed_commit () { # $2 = expected target branch for the push # $3 = [optional] repo to check for actual output (repo1 by default) test_push_success () { - git ${1:+-c push.default="$1"} push && + git ${1:+-c} ${1:+push.default="$1"} push && check_pushed_commit HEAD "$2" "$3" } @@ -34,7 +34,7 @@ test_push_success () { # check that push fails and does not modify any remote branch test_push_failure () { git --git-dir=repo1 log --no-walk --format='%h %s' --all >expect && - test_must_fail git ${1:+-c push.default="$1"} push && + test_must_fail git ${1:+-c} ${1:+push.default="$1"} push && git --git-dir=repo1 log --no-walk --format='%h %s' --all >actual && test_cmp expect actual } diff --git a/t/t5543-atomic-push.sh b/t/t5543-atomic-push.sh new file mode 100755 index 0000000000..3480b33007 --- /dev/null +++ b/t/t5543-atomic-push.sh @@ -0,0 +1,194 @@ +#!/bin/sh + +test_description='pushing to a repository using the atomic push option' + +. ./test-lib.sh + +mk_repo_pair () { + rm -rf workbench upstream && + test_create_repo upstream && + test_create_repo workbench && + ( + cd upstream && + git config receive.denyCurrentBranch warn + ) && + ( + cd workbench && + git remote add up ../upstream + ) +} + +# Compare the ref ($1) in upstream with a ref value from workbench ($2) +# i.e. test_refs second HEAD@{2} +test_refs () { + test $# = 2 && + git -C upstream rev-parse --verify "$1" >expect && + git -C workbench rev-parse --verify "$2" >actual && + test_cmp expect actual +} + +test_expect_success 'atomic push works for a single branch' ' + mk_repo_pair && + ( + cd workbench && + test_commit one && + git push --mirror up && + test_commit two && + git push --atomic up master + ) && + test_refs master master +' + +test_expect_success 'atomic push works for two branches' ' + mk_repo_pair && + ( + cd workbench && + test_commit one && + git branch second && + git push --mirror up && + test_commit two && + git checkout second && + test_commit three && + git push --atomic up master second + ) && + test_refs master master && + test_refs second second +' + +test_expect_success 'atomic push works in combination with --mirror' ' + mk_repo_pair && + ( + cd workbench && + test_commit one && + git checkout -b second && + test_commit two && + git push --atomic --mirror up + ) && + test_refs master master && + test_refs second second +' + +test_expect_success 'atomic push works in combination with --force' ' + mk_repo_pair && + ( + cd workbench && + test_commit one && + git branch second master && + test_commit two_a && + git checkout second && + test_commit two_b && + test_commit three_b && + test_commit four && + git push --mirror up && + # The actual test is below + git checkout master && + test_commit three_a && + git checkout second && + git reset --hard HEAD^ && + git push --force --atomic up master second + ) && + test_refs master master && + test_refs second second +' + +# set up two branches where master can be pushed but second can not +# (non-fast-forward). Since second can not be pushed the whole operation +# will fail and leave master untouched. +test_expect_success 'atomic push fails if one branch fails' ' + mk_repo_pair && + ( + cd workbench && + test_commit one && + git checkout -b second master && + test_commit two && + test_commit three && + test_commit four && + git push --mirror up && + git reset --hard HEAD~2 && + test_commit five && + git checkout master && + test_commit six && + test_must_fail git push --atomic --all up + ) && + test_refs master HEAD@{7} && + test_refs second HEAD@{4} +' + +test_expect_success 'atomic push fails if one tag fails remotely' ' + # prepare the repo + mk_repo_pair && + ( + cd workbench && + test_commit one && + git checkout -b second master && + test_commit two && + git push --mirror up + ) && + # a third party modifies the server side: + ( + cd upstream && + git checkout second && + git tag test_tag second + ) && + # see if we can now push both branches. + ( + cd workbench && + git checkout master && + test_commit three && + git checkout second && + test_commit four && + git tag test_tag && + test_must_fail git push --tags --atomic up master second + ) && + test_refs master HEAD@{3} && + test_refs second HEAD@{1} +' + +test_expect_success 'atomic push obeys update hook preventing a branch to be pushed' ' + mk_repo_pair && + ( + cd workbench && + test_commit one && + git checkout -b second master && + test_commit two && + git push --mirror up + ) && + ( + cd upstream && + HOOKDIR="$(git rev-parse --git-dir)/hooks" && + HOOK="$HOOKDIR/update" && + mkdir -p "$HOOKDIR" && + write_script "$HOOK" <<-\EOF + # only allow update to master from now on + test "$1" = "refs/heads/master" + EOF + ) && + ( + cd workbench && + git checkout master && + test_commit three && + git checkout second && + test_commit four && + test_must_fail git push --atomic up master second + ) && + test_refs master HEAD@{3} && + test_refs second HEAD@{1} +' + +test_expect_success 'atomic push is not advertised if configured' ' + mk_repo_pair && + ( + cd upstream + git config receive.advertiseatomic 0 + ) && + ( + cd workbench && + test_commit one && + git push --mirror up && + test_commit two && + test_must_fail git push --atomic up master + ) && + test_refs master HEAD@{1} +' + +test_done diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh index 6da9422431..2731ad4cea 100755 --- a/t/t5550-http-fetch-dumb.sh +++ b/t/t5550-http-fetch-dumb.sh @@ -214,5 +214,47 @@ test_expect_success 'reencoding is robust to whitespace oddities' ' grep "this is the error message" stderr ' +check_language () { + case "$2" in + '') + >expect + ;; + ?*) + echo "Accept-Language: $1" >expect + ;; + esac && + GIT_CURL_VERBOSE=1 \ + LANGUAGE=$2 \ + git ls-remote "$HTTPD_URL/dumb/repo.git" >output 2>&1 && + tr -d '\015' <output | + sort -u | + sed -ne '/^Accept-Language:/ p' >actual && + test_cmp expect actual +} + +test_expect_success 'git client sends Accept-Language based on LANGUAGE' ' + check_language "ko-KR, *;q=0.9" ko_KR.UTF-8' + +test_expect_success 'git client sends Accept-Language correctly with unordinary LANGUAGE' ' + check_language "ko-KR, *;q=0.9" "ko_KR:" && + check_language "ko-KR, en-US;q=0.9, *;q=0.8" "ko_KR::en_US" && + check_language "ko-KR, *;q=0.9" ":::ko_KR" && + check_language "ko-KR, en-US;q=0.9, *;q=0.8" "ko_KR!!:en_US" && + check_language "ko-KR, ja-JP;q=0.9, *;q=0.8" "ko_KR en_US:ja_JP"' + +test_expect_success 'git client sends Accept-Language with many preferred languages' ' + check_language "ko-KR, en-US;q=0.9, fr-CA;q=0.8, de;q=0.7, sr;q=0.6, \ +ja;q=0.5, zh;q=0.4, sv;q=0.3, pt;q=0.2, *;q=0.1" \ + ko_KR.EUC-KR:en_US.UTF-8:fr_CA:de.UTF-8@euro:sr@latin:ja:zh:sv:pt && + check_language "ko-KR, en-US;q=0.99, fr-CA;q=0.98, de;q=0.97, sr;q=0.96, \ +ja;q=0.95, zh;q=0.94, sv;q=0.93, pt;q=0.92, nb;q=0.91, *;q=0.90" \ + ko_KR.EUC-KR:en_US.UTF-8:fr_CA:de.UTF-8@euro:sr@latin:ja:zh:sv:pt:nb +' + +test_expect_success 'git client does not send an empty Accept-Language' ' + GIT_CURL_VERBOSE=1 LANGUAGE= git ls-remote "$HTTPD_URL/dumb/repo.git" 2>stderr && + ! grep "^Accept-Language:" stderr +' + stop_httpd test_done diff --git a/t/t5570-git-daemon.sh b/t/t5570-git-daemon.sh index 6b16379951..b7e283252d 100755 --- a/t/t5570-git-daemon.sh +++ b/t/t5570-git-daemon.sh @@ -142,4 +142,31 @@ test_expect_success 'read access denied' "test_remote_error -x 'no such reposito test_expect_success 'not exported' "test_remote_error -n 'repository not exported' fetch repo.git " stop_git_daemon +start_git_daemon --interpolated-path="$GIT_DAEMON_DOCUMENT_ROOT_PATH/%H%D" + +test_expect_success 'access repo via interpolated hostname' ' + repo="$GIT_DAEMON_DOCUMENT_ROOT_PATH/localhost/interp.git" && + git init --bare "$repo" && + git push "$repo" HEAD && + >"$repo"/git-daemon-export-ok && + rm -rf tmp.git && + GIT_OVERRIDE_VIRTUAL_HOST=localhost \ + git clone --bare "$GIT_DAEMON_URL/interp.git" tmp.git && + rm -rf tmp.git && + GIT_OVERRIDE_VIRTUAL_HOST=LOCALHOST \ + git clone --bare "$GIT_DAEMON_URL/interp.git" tmp.git +' + +test_expect_success 'hostname cannot break out of directory' ' + rm -rf tmp.git && + repo="$GIT_DAEMON_DOCUMENT_ROOT_PATH/../escape.git" && + git init --bare "$repo" && + git push "$repo" HEAD && + >"$repo"/git-daemon-export-ok && + test_must_fail \ + env GIT_OVERRIDE_VIRTUAL_HOST=.. \ + git clone --bare "$GIT_DAEMON_URL/escape.git" tmp.git +' + +stop_git_daemon test_done diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index e4f10c0f68..02b40b117f 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -301,11 +301,17 @@ expect_ssh () { (cd "$TRASH_DIRECTORY" && rm -f ssh-expect && >ssh-output) ' && { - case "$1" in - none) + case "$#" in + 1) ;; - *) + 2) echo "ssh: $1 git-upload-pack '$2'" + ;; + 3) + echo "ssh: $1 $2 git-upload-pack '$3'" + ;; + *) + echo "ssh: $1 $2 git-upload-pack '$3' $4" esac } >"$TRASH_DIRECTORY/ssh-expect" && (cd "$TRASH_DIRECTORY" && test_cmp ssh-expect ssh-output) @@ -326,7 +332,7 @@ test_expect_success !MINGW,!CYGWIN 'clone local path foo:bar' ' test_expect_success 'bracketed hostnames are still ssh' ' git clone "[myhost:123]:src" ssh-bracket-clone && - expect_ssh myhost:123 src + expect_ssh myhost '-p 123' src ' counter=0 @@ -336,7 +342,8 @@ counter=0 test_clone_url () { counter=$(($counter + 1)) test_might_fail git clone "$1" tmp$counter && - expect_ssh "$2" "$3" + shift && + expect_ssh "$@" } test_expect_success !MINGW 'clone c:temp is ssl' ' @@ -359,7 +366,7 @@ done for repo in rep rep/home/project 123 do test_expect_success "clone [::1]:$repo" ' - test_clone_url [::1]:$repo ::1 $repo + test_clone_url [::1]:$repo ::1 "$repo" ' done #home directory @@ -400,24 +407,40 @@ test_expect_success 'clone ssh://host.xz:22/~repo' ' ' #IPv6 -test_expect_success 'clone ssh://[::1]/home/user/repo' ' - test_clone_url "ssh://[::1]/home/user/repo" "::1" "/home/user/repo" -' +for tuah in ::1 [::1] user@::1 user@[::1] [user@::1] +do + ehost=$(echo $tuah | tr -d "[]") + test_expect_success "clone ssh://$tuah/home/user/repo" " + test_clone_url ssh://$tuah/home/user/repo $ehost /home/user/repo + " +done #IPv6 from home directory -test_expect_success 'clone ssh://[::1]/~repo' ' - test_clone_url "ssh://[::1]/~repo" "::1" "~repo" -' +for tuah in ::1 [::1] user@::1 user@[::1] [user@::1] +do + euah=$(echo $tuah | tr -d "[]") + test_expect_success "clone ssh://$tuah/~repo" " + test_clone_url ssh://$tuah/~repo $euah '~repo' + " +done #IPv6 with port number -test_expect_success 'clone ssh://[::1]:22/home/user/repo' ' - test_clone_url "ssh://[::1]:22/home/user/repo" "-p 22 ::1" "/home/user/repo" -' +for tuah in [::1] user@[::1] [user@::1] +do + euah=$(echo $tuah | tr -d "[]") + test_expect_success "clone ssh://$tuah:22/home/user/repo" " + test_clone_url ssh://$tuah:22/home/user/repo '-p 22' $euah /home/user/repo + " +done #IPv6 from home directory with port number -test_expect_success 'clone ssh://[::1]:22/~repo' ' - test_clone_url "ssh://[::1]:22/~repo" "-p 22 ::1" "~repo" -' +for tuah in [::1] user@[::1] [user@::1] +do + euah=$(echo $tuah | tr -d "[]") + test_expect_success "clone ssh://$tuah:22/~repo" " + test_clone_url ssh://$tuah:22/~repo '-p 22' $euah '~repo' + " +done test_expect_success 'clone from a repository with two identical branches' ' diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 35c805a44e..8bfeef9b0b 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -1459,6 +1459,34 @@ test_expect_success 'invalid sort parameter in configuratoin' ' test_cmp expect actual ' +test_expect_success 'version sort with prerelease reordering' ' + git config --unset tag.sort && + git config versionsort.prereleaseSuffix -rc && + git tag foo1.6-rc1 && + git tag foo1.6-rc2 && + git tag -l --sort=version:refname "foo*" >actual && + cat >expect <<-\EOF && + foo1.3 + foo1.6-rc1 + foo1.6-rc2 + foo1.6 + foo1.10 + EOF + test_cmp expect actual +' + +test_expect_success 'reverse version sort with prerelease reordering' ' + git tag -l --sort=-version:refname "foo*" >actual && + cat >expect <<-\EOF && + foo1.10 + foo1.6 + foo1.6-rc2 + foo1.6-rc1 + foo1.3 + EOF + test_cmp expect actual +' + run_with_limited_stack () { (ulimit -s 128 && "$@") } diff --git a/t/t7508-status.sh b/t/t7508-status.sh index 8ed5788808..6b16bcb640 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -66,6 +66,12 @@ strip_comments () { rm "$1" && mv "$1".tmp "$1" } +cat >.gitignore <<\EOF +.gitignore +expect* +output* +EOF + test_expect_success 'status --column' ' cat >expect <<\EOF && # On branch master @@ -83,8 +89,8 @@ test_expect_success 'status --column' ' # Untracked files: # (use "git add <file>..." to include in what will be committed) # -# dir1/untracked dir2/untracked output -# dir2/modified expect untracked +# dir1/untracked dir2/untracked +# dir2/modified untracked # EOF COLUMNS=50 git -c status.displayCommentPrefix=true status --column="column dense" >output && @@ -116,8 +122,6 @@ cat >expect <<\EOF # dir1/untracked # dir2/modified # dir2/untracked -# expect -# output # untracked # EOF @@ -133,6 +137,23 @@ test_expect_success 'status with status.displayCommentPrefix=false' ' test_i18ncmp expect output ' +test_expect_success 'status -v' ' + (cat expect && git diff --cached) >expect-with-v && + git status -v >output && + test_i18ncmp expect-with-v output +' + +test_expect_success 'status -v -v' ' + (cat expect && + echo "Changes to be committed:" && + git -c diff.mnemonicprefix=true diff --cached && + echo "--------------------------------------------------" && + echo "Changes not staged for commit:" && + git -c diff.mnemonicprefix=true diff) >expect-with-v && + git status -v -v >output && + test_i18ncmp expect-with-v output +' + test_expect_success 'setup fake editor' ' cat >.git/editor <<-\EOF && #! /bin/sh @@ -167,8 +188,6 @@ Untracked files: dir1/untracked dir2/modified dir2/untracked - expect - output untracked EOF @@ -186,8 +205,6 @@ A dir2/added ?? dir1/untracked ?? dir2/modified ?? dir2/untracked -?? expect -?? output ?? untracked EOF @@ -201,7 +218,7 @@ test_expect_success 'status -s' ' test_expect_success 'status with gitignore' ' { echo ".gitignore" && - echo "expect" && + echo "expect*" && echo "output" && echo "untracked" } >.gitignore && @@ -222,6 +239,7 @@ test_expect_success 'status with gitignore' ' !! dir1/untracked !! dir2/untracked !! expect + !! expect-with-v !! output !! untracked EOF @@ -253,6 +271,7 @@ Ignored files: dir1/untracked dir2/untracked expect + expect-with-v output untracked @@ -264,7 +283,7 @@ EOF test_expect_success 'status with gitignore (nothing untracked)' ' { echo ".gitignore" && - echo "expect" && + echo "expect*" && echo "dir2/modified" && echo "output" && echo "untracked" @@ -285,6 +304,7 @@ test_expect_success 'status with gitignore (nothing untracked)' ' !! dir2/modified !! dir2/untracked !! expect + !! expect-with-v !! output !! untracked EOF @@ -312,6 +332,7 @@ Ignored files: dir2/modified dir2/untracked expect + expect-with-v output untracked @@ -320,7 +341,11 @@ EOF test_i18ncmp expect output ' -rm -f .gitignore +cat >.gitignore <<\EOF +.gitignore +expect* +output* +EOF cat >expect <<\EOF ## master @@ -329,8 +354,6 @@ A dir2/added ?? dir1/untracked ?? dir2/modified ?? dir2/untracked -?? expect -?? output ?? untracked EOF @@ -434,8 +457,6 @@ Untracked files: dir2/modified dir2/untracked dir3/ - expect - output untracked EOF @@ -456,8 +477,6 @@ A dir2/added ?? dir2/modified ?? dir2/untracked ?? dir3/ -?? expect -?? output ?? untracked EOF test_expect_success 'status -s -unormal' ' @@ -493,8 +512,6 @@ Untracked files: dir2/untracked dir3/untracked1 dir3/untracked2 - expect - output untracked EOF @@ -518,8 +535,6 @@ A dir2/added ?? dir1/untracked ?? dir2/modified ?? dir2/untracked -?? expect -?? output ?? untracked EOF test_expect_success 'status -s -uall' ' @@ -554,8 +569,6 @@ Untracked files: untracked ../dir2/modified ../dir2/untracked - ../expect - ../output ../untracked EOF @@ -569,8 +582,6 @@ A ../dir2/added ?? untracked ?? ../dir2/modified ?? ../dir2/untracked -?? ../expect -?? ../output ?? ../untracked EOF test_expect_success 'status -s with relative paths' ' @@ -586,8 +597,6 @@ A dir2/added ?? dir1/untracked ?? dir2/modified ?? dir2/untracked -?? expect -?? output ?? untracked EOF @@ -625,8 +634,6 @@ Untracked files: <BLUE>dir1/untracked<RESET> <BLUE>dir2/modified<RESET> <BLUE>dir2/untracked<RESET> - <BLUE>expect<RESET> - <BLUE>output<RESET> <BLUE>untracked<RESET> EOF @@ -647,8 +654,6 @@ cat >expect <<\EOF <BLUE>??<RESET> dir1/untracked <BLUE>??<RESET> dir2/modified <BLUE>??<RESET> dir2/untracked -<BLUE>??<RESET> expect -<BLUE>??<RESET> output <BLUE>??<RESET> untracked EOF @@ -676,8 +681,6 @@ cat >expect <<\EOF <BLUE>??<RESET> dir1/untracked <BLUE>??<RESET> dir2/modified <BLUE>??<RESET> dir2/untracked -<BLUE>??<RESET> expect -<BLUE>??<RESET> output <BLUE>??<RESET> untracked EOF @@ -694,8 +697,6 @@ A dir2/added ?? dir1/untracked ?? dir2/modified ?? dir2/untracked -?? expect -?? output ?? untracked EOF @@ -755,8 +756,6 @@ Untracked files: dir1/untracked dir2/modified dir2/untracked - expect - output untracked EOF @@ -772,8 +771,6 @@ A dir2/added ?? dir1/untracked ?? dir2/modified ?? dir2/untracked -?? expect -?? output ?? untracked EOF @@ -798,8 +795,6 @@ Untracked files: dir1/untracked dir2/ - expect - output untracked EOF @@ -848,8 +843,6 @@ Untracked files: dir1/untracked dir2/modified dir2/untracked - expect - output untracked EOF @@ -870,8 +863,6 @@ A sm ?? dir1/untracked ?? dir2/modified ?? dir2/untracked -?? expect -?? output ?? untracked EOF test_expect_success 'status -s submodule summary is disabled by default' ' @@ -913,8 +904,6 @@ Untracked files: dir1/untracked dir2/modified dir2/untracked - expect - output untracked EOF @@ -940,8 +929,6 @@ A sm ?? dir1/untracked ?? dir2/modified ?? dir2/untracked -?? expect -?? output ?? untracked EOF test_expect_success 'status -s submodule summary' ' @@ -964,8 +951,6 @@ Untracked files: dir1/untracked dir2/modified dir2/untracked - expect - output untracked no changes added to commit (use "git add" and/or "git commit -a") @@ -983,8 +968,6 @@ cat >expect <<EOF ?? dir1/untracked ?? dir2/modified ?? dir2/untracked -?? expect -?? output ?? untracked EOF test_expect_success 'status -s submodule summary (clean submodule)' ' @@ -1025,8 +1008,6 @@ Untracked files: dir1/untracked dir2/modified dir2/untracked - expect - output untracked EOF @@ -1080,8 +1061,6 @@ Untracked files: dir1/untracked dir2/modified dir2/untracked - expect - output untracked EOF @@ -1192,8 +1171,6 @@ Untracked files: dir1/untracked dir2/modified dir2/untracked - expect - output untracked EOF @@ -1254,8 +1231,6 @@ Untracked files: dir1/untracked dir2/modified dir2/untracked - expect - output untracked EOF @@ -1336,8 +1311,6 @@ cat > expect << EOF ; dir1/untracked ; dir2/modified ; dir2/untracked -; expect -; output ; untracked ; EOF @@ -1369,8 +1342,6 @@ Untracked files: dir1/untracked dir2/modified dir2/untracked - expect - output untracked no changes added to commit (use "git add" and/or "git commit -a") @@ -1400,8 +1371,6 @@ Untracked files: dir1/untracked dir2/modified dir2/untracked - expect - output untracked EOF diff --git a/t/t7510-signed-commit.sh b/t/t7510-signed-commit.sh index 474dab381a..3cef18cfda 100755 --- a/t/t7510-signed-commit.sh +++ b/t/t7510-signed-commit.sh @@ -86,8 +86,8 @@ test_expect_success GPG 'show signed commit with signature' ' git show -s --show-signature initial >show && git verify-commit -v initial >verify.1 2>verify.2 && git cat-file commit initial >cat && - grep -v "gpg: " show >show.commit && - grep "gpg: " show >show.gpg && + grep -v -e "gpg: " -e "Warning: " show >show.commit && + grep -e "gpg: " -e "Warning: " show >show.gpg && grep -v "^ " cat | grep -v "^gpgsig " >cat.commit && test_cmp show.commit commit && test_cmp show.gpg verify.2 && diff --git a/t/t7516-commit-races.sh b/t/t7516-commit-races.sh new file mode 100755 index 0000000000..f2ce14e907 --- /dev/null +++ b/t/t7516-commit-races.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +test_description='git commit races' +. ./test-lib.sh + +test_expect_success 'race to create orphan commit' ' + write_script hare-editor <<-\EOF && + git commit --allow-empty -m hare + EOF + test_must_fail env EDITOR=./hare-editor git commit --allow-empty -m tortoise -e && + git show -s --pretty=format:%s >subject && + grep hare subject && + test -z "$(git show -s --pretty=format:%P)" +' + +test_expect_success 'race to create non-orphan commit' ' + write_script airplane-editor <<-\EOF && + git commit --allow-empty -m airplane + EOF + git checkout --orphan branch && + git commit --allow-empty -m base && + git rev-parse HEAD >base && + test_must_fail env EDITOR=./airplane-editor git commit --allow-empty -m ship -e && + git show -s --pretty=format:%s >subject && + grep airplane subject && + git rev-parse HEAD^ >parent && + test_cmp base parent +' + +test_done diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index 37c2d633f0..c538e0a4e9 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -552,8 +552,8 @@ test_expect_success 'D: verify pack' ' ' cat >expect <<EOF -:000000 100755 0000000000000000000000000000000000000000 35a59026a33beac1569b1c7f66f3090ce9c09afc A newdir/exec.sh -:000000 100644 0000000000000000000000000000000000000000 046d0371e9220107917db0d0e030628de8a1de9b A newdir/interesting +:000000 100755 0000000000000000000000000000000000000000 e74b7d465e52746be2b4bae983670711e6e66657 A newdir/exec.sh +:000000 100644 0000000000000000000000000000000000000000 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 A newdir/interesting EOF git diff-tree -M -r branch^ branch >actual test_expect_success \ diff --git a/t/t9817-git-p4-exclude.sh b/t/t9817-git-p4-exclude.sh new file mode 100755 index 0000000000..aac568eadf --- /dev/null +++ b/t/t9817-git-p4-exclude.sh @@ -0,0 +1,71 @@ +#!/bin/sh + +test_description='git p4 tests for excluded paths during clone and sync' + +. ./lib-git-p4.sh + +test_expect_success 'start p4d' ' + start_p4d +' + +# Create a repo with the structure: +# +# //depot/wanted/foo +# //depot/discard/foo +# +# Check that we can exclude a subdirectory with both +# clone and sync operations. + +test_expect_success 'create exclude repo' ' + ( + cd "$cli" && + mkdir -p wanted discard && + echo wanted >wanted/foo && + echo discard >discard/foo && + p4 add wanted/foo discard/foo && + p4 submit -d "initial revision" + ) +' + +test_expect_success 'check the repo was created correctly' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot/...@all && + ( + cd "$git" && + test_path_is_file wanted/foo && + test_path_is_file discard/foo + ) +' + +test_expect_success 'clone, excluding part of repo' ' + test_when_finished cleanup_git && + git p4 clone -//depot/discard/... --dest="$git" //depot/...@all && + ( + cd "$git" && + test_path_is_file wanted/foo && + test_path_is_missing discard/foo + ) +' + +test_expect_success 'clone, then sync with exclude' ' + test_when_finished cleanup_git && + git p4 clone -//depot/discard/... --dest="$git" //depot/...@all && + ( + cd "$cli" && + p4 edit wanted/foo discard/foo && + date >>wanted/foo && + date >>discard/foo && + p4 submit -d "updating" && + + cd "$git" && + git p4 sync -//depot/discard/... && + test_path_is_file wanted/foo && + test_path_is_missing discard/foo + ) +' + +test_expect_success 'kill p4d' ' + kill_p4d +' + +test_done diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index f10a75290e..7a883d1a67 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -351,6 +351,25 @@ test_expect_success '__gitcomp_nl - doesnt fail because of invalid variable name __gitcomp_nl "$invalid_variable_name" ' +test_expect_success '__git_remotes - list remotes from $GIT_DIR/remotes and from config file' ' + cat >expect <<-EOF && + remote_from_file_1 + remote_from_file_2 + remote_in_config_1 + remote_in_config_2 + EOF + test_when_finished "rm -rf .git/remotes" && + mkdir -p .git/remotes && + >.git/remotes/remote_from_file_1 && + >.git/remotes/remote_from_file_2 && + test_when_finished "git remote remove remote_in_config_1" && + git remote add remote_in_config_1 git://remote_1 && + test_when_finished "git remote remove remote_in_config_2" && + git remote add remote_in_config_2 git://remote_2 && + __git_remotes >actual && + test_cmp expect actual +' + test_expect_success 'basic' ' run_completion "git " && # built-in diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 0d93e33de4..0698ce7908 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -745,7 +745,9 @@ test_ln_s_add () { else printf '%s' "$1" >"$2" && ln_s_obj=$(git hash-object -w "$2") && - git update-index --add --cacheinfo 120000 $ln_s_obj "$2" + git update-index --add --cacheinfo 120000 $ln_s_obj "$2" && + # pick up stat info from the file + git update-index "$2" fi } diff --git a/thread-utils.c b/thread-utils.c index 97396a75ae..a2135e0743 100644 --- a/thread-utils.c +++ b/thread-utils.c @@ -35,7 +35,23 @@ int online_cpus(void) if (!pstat_getdynamic(&psd, sizeof(psd), (size_t)1, 0)) return (int)psd.psd_proc_cnt; -#endif +#elif defined(HAVE_BSD_SYSCTL) && defined(HW_NCPU) + int mib[2]; + size_t len; + int cpucount; + + mib[0] = CTL_HW; +# ifdef HW_AVAILCPU + mib[1] = HW_AVAILCPU; + len = sizeof(cpucount); + if (!sysctl(mib, 2, &cpucount, &len, NULL, 0)) + return cpucount; +# endif /* HW_AVAILCPU */ + mib[1] = HW_NCPU; + len = sizeof(cpucount); + if (!sysctl(mib, 2, &cpucount, &len, NULL, 0)) + return cpucount; +#endif /* defined(HAVE_BSD_SYSCTL) && defined(HW_NCPU) */ #ifdef _SC_NPROCESSORS_ONLN if ((ncpus = (long)sysconf(_SC_NPROCESSORS_ONLN)) > 0) diff --git a/transport-helper.c b/transport-helper.c index 7dc4a443ae..5d99a6bc2e 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -97,6 +97,8 @@ static void do_take_over(struct transport *transport) free(data); } +static void standard_options(struct transport *t); + static struct child_process *get_helper(struct transport *transport) { struct helper_data *data = transport->data; @@ -211,6 +213,7 @@ static struct child_process *get_helper(struct transport *transport) strbuf_release(&buf); if (debug) fprintf(stderr, "Debug: Capabilities complete.\n"); + standard_options(transport); return data->helper; } @@ -338,17 +341,6 @@ static int fetch_with_fetch(struct transport *transport, int i; struct strbuf buf = STRBUF_INIT; - standard_options(transport); - if (data->check_connectivity && - data->transport_options.check_self_contained_and_connected) - set_helper_option(transport, "check-connectivity", "true"); - - if (transport->cloning) - set_helper_option(transport, "cloning", "true"); - - if (data->transport_options.update_shallow) - set_helper_option(transport, "update-shallow", "true"); - for (i = 0; i < nr_heads; i++) { const struct ref *posn = to_fetch[i]; if (posn->status & REF_STATUS_UPTODATE) @@ -622,6 +614,16 @@ static int fetch(struct transport *transport, if (!count) return 0; + if (data->check_connectivity && + data->transport_options.check_self_contained_and_connected) + set_helper_option(transport, "check-connectivity", "true"); + + if (transport->cloning) + set_helper_option(transport, "cloning", "true"); + + if (data->transport_options.update_shallow) + set_helper_option(transport, "update-shallow", "true"); + if (data->fetch) return fetch_with_fetch(transport, nr_heads, to_fetch); @@ -826,7 +828,6 @@ static int push_refs_with_push(struct transport *transport, return 0; } - standard_options(transport); for_each_string_list_item(cas_option, &cas_options) set_helper_option(transport, "cas", cas_option->string); diff --git a/transport.c b/transport.c index 08bcd3a4eb..00f39d9f5b 100644 --- a/transport.c +++ b/transport.c @@ -117,7 +117,7 @@ static void insert_packed_refs(const char *packed_refs, struct ref **list) return; } - if (hexval(buffer[0]) > 0xf) + if (!isxdigit(buffer[0])) continue; len = strlen(buffer); if (len && buffer[len - 1] == '\n') @@ -728,6 +728,10 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i ref->deletion ? NULL : ref->peer_ref, "remote failed to report status", porcelain); break; + case REF_STATUS_ATOMIC_PUSH_FAILED: + print_ref_status('!', "[rejected]", ref, ref->peer_ref, + "atomic push failed", porcelain); + break; case REF_STATUS_OK: print_ok_ref_status(ref, porcelain); break; @@ -826,6 +830,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN); args.porcelain = !!(flags & TRANSPORT_PUSH_PORCELAIN); args.push_cert = !!(flags & TRANSPORT_PUSH_CERT); + args.atomic = !!(flags & TRANSPORT_PUSH_ATOMIC); args.url = transport->url; ret = send_pack(&args, data->fd, data->conn, remote_refs, diff --git a/transport.h b/transport.h index 3e0091eaab..18d2cf8275 100644 --- a/transport.h +++ b/transport.h @@ -125,6 +125,7 @@ struct transport { #define TRANSPORT_PUSH_NO_HOOK 512 #define TRANSPORT_PUSH_FOLLOW_TAGS 1024 #define TRANSPORT_PUSH_CERT 2048 +#define TRANSPORT_PUSH_ATOMIC 4096 #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/urlmatch.c b/urlmatch.c index 618d216491..132d342bc1 100644 --- a/urlmatch.c +++ b/urlmatch.c @@ -412,9 +412,9 @@ static size_t url_match_prefix(const char *url, return 0; } -int match_urls(const struct url_info *url, - const struct url_info *url_prefix, - int *exactusermatch) +static int match_urls(const struct url_info *url, + const struct url_info *url_prefix, + int *exactusermatch) { /* * url_prefix matches url if the scheme, host and port of url_prefix diff --git a/urlmatch.h b/urlmatch.h index b461dfd3df..528862adc5 100644 --- a/urlmatch.h +++ b/urlmatch.h @@ -31,7 +31,6 @@ struct url_info { }; extern char *url_normalize(const char *, struct url_info *); -extern int match_urls(const struct url_info *url, const struct url_info *url_prefix, int *exactusermatch); struct urlmatch_item { size_t matched_len; diff --git a/versioncmp.c b/versioncmp.c index 7511e08271..80bfd109fa 100644 --- a/versioncmp.c +++ b/versioncmp.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "string-list.h" /* * versioncmp(): copied from string/strverscmp.c in glibc commit @@ -20,6 +21,48 @@ #define CMP 2 #define LEN 3 +static const struct string_list *prereleases; +static int initialized; + +/* + * p1 and p2 point to the first different character in two strings. If + * either p1 or p2 starts with a prerelease suffix, it will be forced + * to be on top. + * + * If both p1 and p2 start with (different) suffix, the order is + * determined by config file. + * + * Note that we don't have to deal with the situation when both p1 and + * p2 start with the same suffix because the common part is already + * consumed by the caller. + * + * Return non-zero if *diff contains the return value for versioncmp() + */ +static int swap_prereleases(const void *p1_, + const void *p2_, + int *diff) +{ + const char *p1 = p1_; + const char *p2 = p2_; + int i, i1 = -1, i2 = -1; + + for (i = 0; i < prereleases->nr; i++) { + const char *suffix = prereleases->items[i].string; + if (i1 == -1 && starts_with(p1, suffix)) + i1 = i; + if (i2 == -1 && starts_with(p2, suffix)) + i2 = i; + } + if (i1 == -1 && i2 == -1) + return 0; + if (i1 >= 0 && i2 >= 0) + *diff = i1 - i2; + else if (i1 >= 0) + *diff = -1; + else /* if (i2 >= 0) */ + *diff = 1; + return 1; +} /* * Compare S1 and S2 as strings holding indices/version numbers, @@ -74,6 +117,13 @@ int versioncmp(const char *s1, const char *s2) state += (c1 == '0') + (isdigit (c1) != 0); } + if (!initialized) { + initialized = 1; + prereleases = git_config_get_value_multi("versionsort.prereleasesuffix"); + } + if (prereleases && swap_prereleases(p1 - 1, p2 - 1, &diff)) + return diff; + state = result_type[state * 3 + (((c2 == '0') + (isdigit (c2) != 0)))]; switch (state) { @@ -299,7 +299,7 @@ int walker_fetch(struct walker *walker, int targets, char **target, strbuf_reset(&refname); strbuf_addf(&refname, "refs/%s", write_ref[i]); if (ref_transaction_update(transaction, refname.buf, - &sha1[20 * i], NULL, 0, 0, + &sha1[20 * i], NULL, 0, msg ? msg : "fetch (unknown)", &err)) { error("%s", err.buf); @@ -172,8 +172,22 @@ void *xcalloc(size_t nmemb, size_t size) * 64-bit is buggy, returning EINVAL if len >= INT_MAX; and even in * the absence of bugs, large chunks can result in bad latencies when * you decide to kill the process. + * + * We pick 8 MiB as our default, but if the platform defines SSIZE_MAX + * that is smaller than that, clip it to SSIZE_MAX, as a call to + * read(2) or write(2) larger than that is allowed to fail. As the last + * resort, we allow a port to pass via CFLAGS e.g. "-DMAX_IO_SIZE=value" + * to override this, if the definition of SSIZE_MAX given by the platform + * is broken. */ -#define MAX_IO_SIZE (8*1024*1024) +#ifndef MAX_IO_SIZE +# define MAX_IO_SIZE_DEFAULT (8*1024*1024) +# if defined(SSIZE_MAX) && (SSIZE_MAX < MAX_IO_SIZE_DEFAULT) +# define MAX_IO_SIZE SSIZE_MAX +# else +# define MAX_IO_SIZE MAX_IO_SIZE_DEFAULT +# endif +#endif /* * xread() is the same a read(), but it automatically restarts read() diff --git a/wt-status.c b/wt-status.c index 29666d0dba..7036fa28dc 100644 --- a/wt-status.c +++ b/wt-status.c @@ -849,6 +849,8 @@ static void wt_status_print_verbose(struct wt_status *s) { struct rev_info rev; struct setup_revision_opt opt; + int dirty_submodules; + const char *c = color(WT_STATUS_HEADER, s); init_revisions(&rev, NULL); DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV); @@ -873,7 +875,25 @@ static void wt_status_print_verbose(struct wt_status *s) rev.diffopt.use_color = 0; wt_status_add_cut_line(s->fp); } + if (s->verbose > 1 && s->commitable) { + /* print_updated() printed a header, so do we */ + if (s->fp != stdout) + wt_status_print_trailer(s); + status_printf_ln(s, c, _("Changes to be committed:")); + rev.diffopt.a_prefix = "c/"; + rev.diffopt.b_prefix = "i/"; + } /* else use prefix as per user config */ run_diff_index(&rev, 1); + if (s->verbose > 1 && + wt_status_check_worktree_changes(s, &dirty_submodules)) { + status_printf_ln(s, c, + "--------------------------------------------------"); + status_printf_ln(s, c, _("Changes not staged for commit:")); + setup_work_tree(); + rev.diffopt.a_prefix = "i/"; + rev.diffopt.b_prefix = "w/"; + run_diff_files(&rev, 0); + } } static void wt_status_print_tracking(struct wt_status *s) @@ -1222,6 +1242,8 @@ static void wt_status_get_detached_from(struct wt_status_state *state) state->detached_from = xstrdup(find_unique_abbrev(cb.nsha1, DEFAULT_ABBREV)); hashcpy(state->detached_sha1, cb.nsha1); + state->detached_at = !get_sha1("HEAD", sha1) && + !hashcmp(sha1, state->detached_sha1); free(ref); strbuf_release(&cb.buf); @@ -1310,10 +1332,8 @@ void wt_status_print(struct wt_status *s) on_what = _("rebase in progress; onto "); branch_name = state.onto; } else if (state.detached_from) { - unsigned char sha1[20]; branch_name = state.detached_from; - if (!get_sha1("HEAD", sha1) && - !hashcmp(sha1, state.detached_sha1)) + if (state.detached_at) on_what = _("HEAD detached at "); else on_what = _("HEAD detached from "); diff --git a/wt-status.h b/wt-status.h index 283a9fef03..e0a99f75c7 100644 --- a/wt-status.h +++ b/wt-status.h @@ -84,6 +84,7 @@ struct wt_status_state { int cherry_pick_in_progress; int bisect_in_progress; int revert_in_progress; + int detached_at; char *branch; char *onto; char *detached_from; @@ -159,6 +159,7 @@ void git_deflate_init(git_zstream *strm, int level) { int status; + memset(strm, 0, sizeof(*strm)); zlib_pre_call(strm); status = deflateInit(&strm->z, level); zlib_post_call(strm); @@ -172,6 +173,7 @@ static void do_git_deflate_init(git_zstream *strm, int level, int windowBits) { int status; + memset(strm, 0, sizeof(*strm)); zlib_pre_call(strm); status = deflateInit2(&strm->z, level, Z_DEFLATED, windowBits, |