From: Junio C Hamano Date: Mon, 3 Dec 2012 17:27:29 +0000 (-0800) Subject: Merge branch 'mm/status-push-pull-advise' X-Git-Tag: v1.8.1-rc0~1 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/90ae9f259eb55adf54bec6442fe9e2fe223ef80c?hp=491e3075a29a0475984098a85cdbbb185d5a1e33 Merge branch 'mm/status-push-pull-advise' Finishing touch to allow the new advice message squelched with an advice.* configuration variable. * mm/status-push-pull-advise: status: respect advice.statusHints for ahead/behind advice --- diff --git a/.gitignore b/.gitignore index a188a82bb1..f702415c12 100644 --- a/.gitignore +++ b/.gitignore @@ -125,6 +125,7 @@ /git-remote-fd /git-remote-ext /git-remote-testgit +/git-remote-testsvn /git-repack /git-replace /git-repo-config diff --git a/Documentation/Makefile b/Documentation/Makefile index 267dfe135d..361550422a 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -24,8 +24,30 @@ SP_ARTICLES = user-manual SP_ARTICLES += howto/revert-branch-rebase SP_ARTICLES += howto/using-merge-subtree SP_ARTICLES += howto/using-signed-tag-in-pull-request +SP_ARTICLES += howto/use-git-daemon +SP_ARTICLES += howto/update-hook-example +SP_ARTICLES += howto/setup-git-server-over-http +SP_ARTICLES += howto/separating-topic-branches +SP_ARTICLES += howto/revert-a-faulty-merge +SP_ARTICLES += howto/recover-corrupted-blob-object +SP_ARTICLES += howto/rebuild-from-update-hook +SP_ARTICLES += howto/rebuild-from-update-hook +SP_ARTICLES += howto/rebase-from-internal-branch +SP_ARTICLES += howto/maintain-git API_DOCS = $(patsubst %.txt,%,$(filter-out technical/api-index-skel.txt technical/api-index.txt, $(wildcard technical/api-*.txt))) SP_ARTICLES += $(API_DOCS) + +TECH_DOCS = technical/index-format +TECH_DOCS += technical/pack-format +TECH_DOCS += technical/pack-heuristics +TECH_DOCS += technical/pack-protocol +TECH_DOCS += technical/protocol-capabilities +TECH_DOCS += technical/protocol-common +TECH_DOCS += technical/racy-git +TECH_DOCS += technical/send-pack-pipeline +TECH_DOCS += technical/shallow +TECH_DOCS += technical/trivial-merge +SP_ARTICLES += $(TECH_DOCS) SP_ARTICLES += technical/api-index DOC_HTML += $(patsubst %,%.html,$(ARTICLES) $(SP_ARTICLES)) @@ -231,7 +253,7 @@ clean: $(RM) *.texi *.texi+ *.texi++ git.info gitman.info $(RM) *.pdf $(RM) howto-index.txt howto/*.html doc.dep - $(RM) technical/api-*.html technical/api-index.txt + $(RM) technical/*.html technical/api-index.txt $(RM) $(cmds_txt) *.made $(RM) manpage-base-url.xsl @@ -264,7 +286,7 @@ technical/api-index.txt: technical/api-index-skel.txt \ $(QUIET_GEN)cd technical && '$(SHELL_PATH_SQ)' ./api-index.sh technical/%.html: ASCIIDOC_EXTRA += -a git-relative-html-prefix=../ -$(patsubst %,%.html,$(API_DOCS) technical/api-index): %.html : %.txt +$(patsubst %,%.html,$(API_DOCS) technical/api-index $(TECH_DOCS)): %.html : %.txt $(QUIET_ASCIIDOC)$(ASCIIDOC) -b xhtml11 -f asciidoc.conf \ $(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) $*.txt diff --git a/Documentation/RelNotes/1.8.0.1.txt b/Documentation/RelNotes/1.8.0.1.txt new file mode 100644 index 0000000000..1f372fa0b5 --- /dev/null +++ b/Documentation/RelNotes/1.8.0.1.txt @@ -0,0 +1,64 @@ +Git v1.8.0.1 Release Notes +========================== + +Fixes since v1.8.0 +------------------ + + * The configuration parser had an unnecessary hardcoded limit on + variable names that was not checked consistently. + + * The "say" function in the test scaffolding incorrectly allowed + "echo" to interpret "\a" as if it were a C-string asking for a + BEL output. + + * "git mergetool" feeds /dev/null as a common ancestor when dealing + with an add/add conflict, but p4merge backend cannot handle + it. Work it around by passing a temporary empty file. + + * "git log -F -E --grep=''" failed to use the given + pattern as extended regular expression, and instead looked for the + string literally. + + * "git grep -e pattern " asked the attribute system to read + ":.gitattributes" file in the working tree, which was + nonsense. + + * A symbolic ref refs/heads/SYM was not correctly removed with "git + branch -d SYM"; the command removed the ref pointed by SYM + instead. + + * Earlier we fixed documentation to hyphenate "remote-tracking branch" + to clarify that these are not a remote entity, but unhyphenated + spelling snuck in to a few places since then. + + * "git pull --rebase" run while the HEAD is detached tried to find + the upstream branch of the detached HEAD (which by definition + does not exist) and emitted unnecessary error messages. + + * The refs/replace hierarchy was not mentioned in the + repository-layout docs. + + * Sometimes curl_multi_timeout() function suggested a wrong timeout + value when there is no file descriptors to wait on and the http + transport ended up sleeping for minutes in select(2) system call. + A workaround has been added for this. + + * Various rfc2047 quoting issues around a non-ASCII name on the + From: line in the output from format-patch have been corrected. + + * "git diff -G" did not honor textconv filter when looking + for changes. + + * Bash completion script (in contrib/) did not correctly complete a + lazy "git checkout $name_of_remote_tracking_branch_that_is_unique" + command line. + + * RSS feed from "gitweb" had a xss hole in its title output. + + * "git config --path $key" segfaulted on "[section] key" (a boolean + "true" spelled without "=", not "[section] key = true"). + + * "git checkout -b foo" while on an unborn branch did not say + "Switched to a new branch 'foo'" like other cases. + +Also contains other minor fixes and documentation updates. diff --git a/Documentation/RelNotes/1.8.0.2.txt b/Documentation/RelNotes/1.8.0.2.txt new file mode 100644 index 0000000000..5d08021a8d --- /dev/null +++ b/Documentation/RelNotes/1.8.0.2.txt @@ -0,0 +1,15 @@ +Git v1.8.0.2 Release Notes +========================== + +Fixes since v1.8.0.1 +-------------------- + + * "git p4" used to try expanding malformed "$keyword$" that spans + across multiple lines. + + * "git update-ref -d --deref SYM" to delete a ref through a symbolic + ref that points to it did not remove it correctly. + + * Syntax highlighting in "gitweb" was not quite working. + +Also contains other minor fixes and documentation updates. diff --git a/Documentation/RelNotes/1.8.1.txt b/Documentation/RelNotes/1.8.1.txt new file mode 100644 index 0000000000..8f53af3403 --- /dev/null +++ b/Documentation/RelNotes/1.8.1.txt @@ -0,0 +1,251 @@ +Git v1.8.1 Release Notes +======================== + +Backward compatibility notes +---------------------------- + +In the next major release (not *this* one), we will change the +behavior of the "git push" command. + +When "git push [$there]" does not say what to push, we have used the +traditional "matching" semantics so far (all your branches were sent +to the remote as long as there already are branches of the same name +over there). We will use the "simple" semantics that pushes the +current branch to the branch with the same name, only when the current +branch is set to integrate with that remote branch. There is a user +preference configuration variable "push.default" to change this, and +"git push" will warn about the upcoming change until you set this +variable in this release. + +"git branch --set-upstream" is deprecated and may be removed in a +relatively distant future. "git branch [-u|--set-upstream-to]" has +been introduced with a saner order of arguments. + + +Updates since v1.8.0 +-------------------- + +UI, Workflows & Features + + * Command-line completion scripts for tcsh and zsh have been added. + + * A new remote-helper interface for Mercurial has been added to + contrib/remote-helpers. + + * We used to have a workaround for a bug in ancient "less" that + causes it to exit without any output when the terminal is resized. + The bug has been fixed in "less" version 406 (June 2007), and the + workaround has been removed in this release. + + * Some documentation pages that used to ship only in the plain text + format are now formatted in HTML as well. + + * "git-prompt" scriptlet (in contrib/completion) can be told to paint + pieces of the hints in the prompt string in colors. + + * A new configuration variable "diff.context" can be used to + give the default number of context lines in the patch output, to + override the hardcoded default of 3 lines. + + * When "git checkout" checks out a branch, it tells the user how far + behind (or ahead) the new branch is relative to the remote tracking + branch it builds upon. The message now also advises how to sync + them up by pushing or pulling. + + * "git config --get" used to diagnose presence of multiple + definitions of the same variable in the same configuration file as + an error, but it now applies the "last one wins" rule used by the + internal configuration logic. Strictly speaking, this may be an + API regression but it is expected that nobody will notice it in + practice. + + * "git log -p -S" now looks for the after applying + the textconv filter (if defined); earlier it inspected the contents + of the blobs without filtering. + + * "git format-patch" learned the "--notes=" option to give + notes for the commit after the three-dash lines in its output. + + * "git log --grep=" learned to honor the "grep.patterntype" + configuration set to "perl". + + * "git replace -d " now interprets , instead of only + accepting full hex object name. + + * "git rm $submodule" used to punt on removing a submodule working + tree to avoid losing the repository embedded in it. Because + recent git uses a mechanism to separate the submodule repository + from the submodule working tree, "git rm" learned to detect this + case and removes the submodule working tree when it is safe. + + * "git send-email" used to prompt for the sender address, even when + the committer identify is well specified (e.g. via user.name and + user.email configuration variables). The command no longer gives + this prompt when not necessary. + + * "git send-email" did not allow non-address garbage strings to + appear after addresses on Cc: lines in the patch files (and when + told to pick them up to find more recipients), e.g. + + Cc: Stable Kernel # for v3.2 and up + + The command now strips " # for v3.2 and up" part before adding the + remainder of this line to the list of recipients. + + * "git submodule add" learned to add a new submodule at the same + path as the path where an unrelated submodule was bound to in an + existing revision via the "--name" option. + + * "git submodule sync" learned the "--recursive" option. + + * "diff.submodule" configuration variable can be used to give custom + default value to the "git diff --submodule" option. + + * "git symbolic-ref" learned the "-d $symref" option to delete the + named symbolic ref, which is more intuitive way to spell it than + "update-ref -d --no-deref". + + +Foreign Interface + + * "git cvsimport" can be told to record timezones (other than GMT) + per-author via its author info file. + + * The remote helper interface to interact with subversion + repositories (one of the GSoC 2012 projects) has been merged. + + +Performance, Internal Implementation, etc. + + * Compilation on Cygwin with newer header files are supported now. + + * The logic to generate the initial advertisement from + "upload-pack" (what is invoked by "git fetch" on the other side + of the connection) to list what refs are available in the + repository has been optimized. + + * The logic to find set of attributes that match a given path has + been optimized. + + * Use preloadindex in "git diff-index" and "git update-index", which + has a nice speedup on systems with slow stat calls (and even on + Linux). + + +Also contains minor documentation updates and code clean-ups. + + +Fixes since v1.8.0 +------------------ + +Unless otherwise noted, all the fixes since v1.8.0 in the maintenance +track are contained in this release (see release notes to them for +details). + + * The configuration parser had an unnecessary hardcoded limit on + variable names that was not checked consistently. + (merge 0971e99 bw/config-lift-variable-name-length-limit later to maint). + + * The "say" function in the test scaffolding incorrectly allowed + "echo" to interpret "\a" as if it were a C-string asking for a + BEL output. + (merge 7bc0911 jc/test-say-color-avoid-echo-escape later to maint). + + * "git mergetool" feeds /dev/null as a common ancestor when dealing + with an add/add conflict, but p4merge backend cannot handle + it. Work it around by passing a temporary empty file. + (merge 3facc60 da/mergetools-p4 later to maint). + + * "git log -F -E --grep=''" failed to use the given + pattern as extended regular expression, and instead looked for the + string literally. + (merge 727b6fc jc/grep-pcre-loose-ends~1 later to maint). + + * "git grep -e pattern " asked the attribute system to read + ":.gitattributes" file in the working tree, which was + nonsense. + (merge 55c6168 nd/grep-true-path later to maint). + + * A symbolic ref refs/heads/SYM was not correctly removed with "git + branch -d SYM"; the command removed the ref pointed by SYM + instead. + (merge 13baa9f rs/branch-del-symref later to maint). + + * Update "remote tracking branch" in the documentation to + "remote-tracking branch". + (merge a6d3bde mm/maint-doc-remote-tracking later to maint). + + * "git pull --rebase" run while the HEAD is detached tried to find + the upstream branch of the detached HEAD (which by definition + does not exist) and emitted unnecessary error messages. + (merge e980765 ph/pull-rebase-detached later to maint). + + * The refs/replace hierarchy was not mentioned in the + repository-layout docs. + (merge 11fbe18 po/maint-refs-replace-docs later to maint). + + * Various rfc2047 quoting issues around a non-ASCII name on the + From: line in the output from format-patch have been corrected. + (merge 25dc8da js/format-2047 later to maint). + + * Sometimes curl_multi_timeout() function suggested a wrong timeout + value when there is no file descriptor to wait on and the http + transport ended up sleeping for minutes in select(2) system call. + A workaround has been added for this. + (merge 7202b81 sz/maint-curl-multi-timeout later to maint). + + * For a fetch refspec (or the result of applying wildcard on one), + we always want the RHS to map to something inside "refs/" + hierarchy, but the logic to check it was not exactly right. + (merge 5c08c1f jc/maint-fetch-tighten-refname-check later to maint). + + * "git diff -G" did not honor textconv filter when looking + for changes. + (merge b1c2f57 jk/maint-diff-grep-textconv later to maint). + + * Some HTTP servers ask for auth only during the actual packing phase + (not in ls-remote phase); this is not really a recommended + configuration, but the clients used to fail to authenticate with + such servers. + (merge 2e736fd jk/maint-http-half-auth-fetch later to maint). + + * "git p4" used to try expanding malformed "$keyword$" that spans + across multiple lines. + (merge 6b2bf41 pw/maint-p4-rcs-expansion-newline later to maint). + + * Syntax highlighting in "gitweb" was not quite working. + (merge 048b399 rh/maint-gitweb-highlight-ext later to maint). + + * RSS feed from "gitweb" had a xss hole in its title output. + (merge 0f0ecf6 jk/maint-gitweb-xss later to maint). + + * "git config --path $key" segfaulted on "[section] key" (a boolean + "true" spelled without "=", not "[section] key = true"). + (merge 962c38e cn/config-missing-path later to maint). + + * "git checkout -b foo" while on an unborn branch did not say + "Switched to a new branch 'foo'" like other cases. + (merge afa8c07 jk/checkout-out-of-unborn later to maint). + + * We failed to mention a file without any content change but whose + permission bit was modified, or (worse yet) a new file without any + content in the "git diff --stat" output. + (merge de9095955 lt/diff-stat-show-0-lines later to maint). + + * When "--stat-count" hides a diffstat for binary contents, the total + number of added and removed lines at the bottom was computed + incorrectly. + (merge de9095955 lt/diff-stat-show-0-lines later to maint). + + * When "--stat-count" hides a diffstat for unmerged paths, the total + number of affected files at the bottom of the "diff --stat" output + was computed incorrectly. + (merge de9095955 lt/diff-stat-show-0-lines later to maint). + + * "diff --shortstat" miscounted the total number of affected files + when there were unmerged paths. + (merge de9095955 lt/diff-stat-show-0-lines later to maint). + + * "update-ref -d --deref SYM" to delete a ref through a symbolic ref + that points to it did not remove it correctly. + (merge b274a71 jh/update-ref-d-through-symref later to maint). diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index 0dbf2c9843..3d8b2fe4d1 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -179,7 +179,8 @@ message starts, you can put a "From: " line to name that person. You often want to add additional explanation about the patch, other than the commit message itself. Place such "cover letter" -material between the three dash lines and the diffstat. +material between the three dash lines and the diffstat. Git-notes +can also be inserted using the `--notes` option. Do not attach the patch as a MIME attachment, compressed or not. Do not let your e-mail client send quoted-printable. Do not let diff --git a/Documentation/config.txt b/Documentation/config.txt index 11f320b962..e70216dfe6 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -538,14 +538,14 @@ core.pager:: `LESS` variable to some other value. Alternately, these settings can be overridden on a project or global basis by setting the `core.pager` option. - Setting `core.pager` has no affect on the `LESS` + Setting `core.pager` has no effect on the `LESS` environment variable behaviour above, so if you want to override git's default settings this way, you need to be explicit. For example, to disable the S option in a backward compatible manner, set `core.pager` - to `less -+$LESS -FRX`. This will be passed to the - shell by git, which will translate the final command to - `LESS=FRSX less -+FRSX -FRX`. + to `less -+S`. This will be passed to the shell by + git, which will translate the final command to + `LESS=FRSX less -+S`. core.whitespace:: A comma separated list of common whitespace problems to @@ -962,12 +962,6 @@ difftool..cmd:: difftool.prompt:: Prompt before each invocation of the diff tool. -diff.wordRegex:: - A POSIX Extended Regular Expression used to determine what is a "word" - when performing word-by-word difference calculations. Character - sequences that match the regular expression are "words", all other - characters are *ignorable* whitespace. - fetch.recurseSubmodules:: This option can be either set to a boolean value or to 'on-demand'. Setting it to a boolean changes the behavior of fetch and pull to diff --git a/Documentation/diff-config.txt b/Documentation/diff-config.txt index 67a90a828c..89dd634674 100644 --- a/Documentation/diff-config.txt +++ b/Documentation/diff-config.txt @@ -56,6 +56,10 @@ diff.statGraphWidth:: Limit the width of the graph part in --stat output. If set, applies to all commands generating --stat output except format-patch. +diff.context:: + Generate diffs with lines of context instead of the default of + 3. This value is overridden by the -U option. + diff.external:: If this config variable is set, diff generation is not performed using the internal diff machinery, but using the @@ -103,6 +107,19 @@ diff.suppressBlankEmpty:: A boolean to inhibit the standard behavior of printing a space before each empty output line. Defaults to false. +diff.submodule:: + Specify the format in which differences in submodules are + shown. The "log" format lists the commits in the range like + linkgit:git-submodule[1] `summary` does. The "short" format + format just shows the names of the commits at the beginning + and end of the range. Defaults to short. + +diff.wordRegex:: + A POSIX Extended Regular Expression used to determine what is a "word" + when performing word-by-word difference calculations. Character + sequences that match the regular expression are "words", all other + characters are *ignorable* whitespace. + diff..command:: The custom diff driver command. See linkgit:gitattributes[5] for details. diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index cf4b216598..f4f7e250c5 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -170,7 +170,8 @@ any of those replacements occurred. the commits in the range like linkgit:git-submodule[1] `summary` does. Omitting the `--submodule` option or specifying `--submodule=short`, uses the 'short' format. This format just shows the names of the commits - at the beginning and end of the range. + at the beginning and end of the range. Can be tweaked via the + `diff.submodule` configuration variable. --color[=]:: Show colored diff. diff --git a/Documentation/git-bisect-lk2009.txt b/Documentation/git-bisect-lk2009.txt index 8a2ba37904..ec4497e098 100644 --- a/Documentation/git-bisect-lk2009.txt +++ b/Documentation/git-bisect-lk2009.txt @@ -257,7 +257,7 @@ Date: Sat May 3 11:59:44 2008 -0700 Linux 2.6.26-rc1 -:100644 100644 5cf8258195331a4dbdddff08b8d68642638eea57 4492984efc09ab72ff6219a7bc21fb6a957c4cd5 M Makefile +:100644 100644 5cf82581... 4492984e... M Makefile ------------- At this point we can see what the commit does, check it out (if it's @@ -331,7 +331,7 @@ Date: Sat May 3 11:59:44 2008 -0700 Linux 2.6.26-rc1 -:100644 100644 5cf8258195331a4dbdddff08b8d68642638eea57 4492984efc09ab72ff6219a7bc21fb6a957c4cd5 M Makefile +:100644 100644 5cf82581... 4492984e... M Makefile bisect run success ------------- diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index 6d98ef3d2a..7fefdb0384 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -196,9 +196,9 @@ objects from the source repository into a pack in the cloned repository. `--no-single-branch` is given to fetch the histories near the tips of all branches. Further fetches into the resulting repository will only update the - remote tracking branch for the branch this option was used for the + remote-tracking branch for the branch this option was used for the initial cloning. If the HEAD at the remote did not point at any - branch when `--single-branch` clone was made, no remote tracking + branch when `--single-branch` clone was made, no remote-tracking branch is created. --recursive:: diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 9594ac8e9d..7bdb039d5e 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -13,7 +13,7 @@ SYNOPSIS [-F | -m ] [--reset-author] [--allow-empty] [--allow-empty-message] [--no-verify] [-e] [--author=] [--date=] [--cleanup=] [--status | --no-status] - [-i | -o] [--] [...] + [-i | -o] [-S[]] [--] [...] DESCRIPTION ----------- @@ -109,6 +109,10 @@ OPTIONS format. See linkgit:git-status[1] for details. Implies `--dry-run`. +--long:: + When doing a dry-run, give the output in a the long-format. + Implies `--dry-run`. + -z:: --null:: When showing `short` or `porcelain` status output, terminate @@ -184,6 +188,11 @@ OPTIONS commit log message unmodified. This option lets you further edit the message taken from these sources. +--no-edit:: + Use the selected commit message without launching an editor. + For example, `git commit --amend --no-edit` amends a commit + without changing its commit message. + --amend:: Used to amend the tip of the current branch. Prepare the tree object you would want to replace the latest commit as usual @@ -193,10 +202,6 @@ OPTIONS current tip -- if it was a merge, it will have the parents of the current tip as parents -- so the current top commit is discarded. - ---no-post-rewrite:: - Bypass the post-rewrite hook. - + -- It is a rough equivalent for: @@ -213,6 +218,9 @@ You should understand the implications of rewriting history if you amend a commit that has already been published. (See the "RECOVERING FROM UPSTREAM REBASE" section in linkgit:git-rebase[1].) +--no-post-rewrite:: + Bypass the post-rewrite hook. + -i:: --include:: Before making a commit out of staged contents so far, @@ -276,6 +284,10 @@ configuration variable documented in linkgit:git-config[1]. commit message template when using an editor to prepare the default commit message. +-S[]:: +--gpg-sign[=]:: + GPG-sign commit. + \--:: Do not interpret any more arguments as options. diff --git a/Documentation/git-cvsimport.txt b/Documentation/git-cvsimport.txt index 6695ab3b4b..98d9881d7e 100644 --- a/Documentation/git-cvsimport.txt +++ b/Documentation/git-cvsimport.txt @@ -137,17 +137,19 @@ This option can be used several times to provide several detection regexes. -A :: CVS by default uses the Unix username when writing its commit logs. Using this option and an author-conv-file - in this format + maps the name recorded in CVS to author name, e-mail and + optional timezone: + --------- exon=Andreas Ericsson - spawn=Simon Pawn + spawn=Simon Pawn America/Chicago --------- + 'git cvsimport' will make it appear as those authors had their GIT_AUTHOR_NAME and GIT_AUTHOR_EMAIL set properly -all along. +all along. If a timezone is specified, GIT_AUTHOR_DATE will +have the corresponding offset applied. + For convenience, this data is saved to `$GIT_DIR/cvs-authors` each time the '-A' option is provided and read from that same diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt index 6603a7ab73..d1844ead4a 100644 --- a/Documentation/git-fast-import.txt +++ b/Documentation/git-fast-import.txt @@ -442,7 +442,9 @@ their syntax. ^^^^^^ The `from` command is used to specify the commit to initialize this branch from. This revision will be the first ancestor of the -new commit. +new commit. The state of the tree built at this commit will begin +with the state at the `from` commit, and be altered by the content +modifications in this commit. Omitting the `from` command in the first commit of a new branch will cause fast-import to create that commit with no ancestor. This @@ -492,7 +494,9 @@ existing value of the branch. `merge` ^^^^^^^ -Includes one additional ancestor commit. If the `from` command is +Includes one additional ancestor commit. The additional ancestry +link does not change the way the tree state is built at this commit. +If the `from` command is omitted when creating a new branch, the first `merge` commit will be the first ancestor of the current commit, and the branch will start out with no files. An unlimited number of `merge` commands per @@ -558,8 +562,12 @@ A `` string must use UNIX-style directory separators (forward slash `/`), may contain any byte other than `LF`, and must not start with double quote (`"`). -If an `LF` or double quote must be encoded into `` shell-style -quoting should be used, e.g. `"path/with\n and \" in it"`. +A path can use C-style string quoting; this is accepted in all cases +and mandatory if the filename starts with double quote or contains +`LF`. In C-style quoting, the complete name should be surrounded with +double quotes, and any `LF`, backslash, or double quote characters +must be escaped by preceding them with a backslash (e.g., +`"path/with\n, \\ and \" in it"`). The value of `` must be in canonical form. That is it must not: diff --git a/Documentation/git-fetch-pack.txt b/Documentation/git-fetch-pack.txt index 474fa307a0..8c751202d7 100644 --- a/Documentation/git-fetch-pack.txt +++ b/Documentation/git-fetch-pack.txt @@ -9,7 +9,10 @@ git-fetch-pack - Receive missing objects from another repository SYNOPSIS -------- [verse] -'git fetch-pack' [--all] [--quiet|-q] [--keep|-k] [--thin] [--include-tag] [--upload-pack=] [--depth=] [--no-progress] [-v] [:] [...] +'git fetch-pack' [--all] [--quiet|-q] [--keep|-k] [--thin] [--include-tag] + [--upload-pack=] + [--depth=] [--no-progress] + [-v] [:] [...] DESCRIPTION ----------- diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 6d43f56279..259dce4994 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -20,7 +20,7 @@ SYNOPSIS [--ignore-if-in-upstream] [--subject-prefix=Subject-Prefix] [--to=] [--cc=] - [--cover-letter] [--quiet] + [--cover-letter] [--quiet] [--notes[=]] [] [ | ] @@ -191,6 +191,18 @@ will want to ensure that threading is disabled for `git send-email`. containing the shortlog and the overall diffstat. You can fill in a description in the file before sending it out. +--notes[=]:: + Append the notes (see linkgit:git-notes[1]) for the commit + after the three-dash line. ++ +The expected use case of this is to write supporting explanation for +the commit that does not belong to the commit log message proper, +and include it with the patch submission. While one can simply write +these explanations after `format-patch` has run but before sending, +keeping them as git notes allows them to be maintained between versions +of the patch series (but see the discussion of the `notes.rewrite` +configuration options in linkgit:git-notes[1] to use this workflow). + --[no]-signature=:: Add a signature to each message produced. Per RFC 3676 the signature is separated from the body by a line with '-- ' on it. If the diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index 20f9228511..d34ea3c50b 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -99,7 +99,7 @@ commit or stash your changes before running 'git merge'. more than two parents (affectionately called an Octopus merge). + If no commit is given from the command line, and if `merge.defaultToUpstream` -configuration variable is set, merge the remote tracking branches +configuration variable is set, merge the remote-tracking branches that the current branch is configured to use as its upstream. See also the configuration section of this manual page. diff --git a/Documentation/git-notes.txt b/Documentation/git-notes.txt index b95aafae2d..46ef0466be 100644 --- a/Documentation/git-notes.txt +++ b/Documentation/git-notes.txt @@ -39,6 +39,10 @@ message stored in the commit object, the notes are indented like the message, after an unindented line saying "Notes ():" (or "Notes:" for `refs/notes/commits`). +Notes can also be added to patches prepared with `git format-patch` by +using the `--notes` option. Such notes are added as a patch commentary +after a three dash separator line. + To change which notes are shown by 'git log', see the "notes.displayRef" configuration in linkgit:git-log[1]. diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 22d2580129..8b637d339f 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -175,7 +175,7 @@ useful if you write an alias or script around 'git push'. --recurse-submodules=check|on-demand:: Make sure all submodule commits used by the revisions to be - pushed are available on a remote tracking branch. If 'check' is + pushed are available on a remote-tracking branch. If 'check' is used git will verify that all submodule commits that changed in the revisions to be pushed are available on at least one remote of the submodule. If any commits are missing the push will be @@ -286,7 +286,8 @@ leading to commit A. The history looks like this: ---------------- Further suppose that the other person already pushed changes leading to A -back to the original repository you two obtained the original commit X. +back to the original repository from which you two obtained the original +commit X. The push done by the other person updated the branch that used to point at commit X to point at commit A. It is a fast-forward. @@ -384,11 +385,23 @@ the ones in the examples below) can be configured as the default for A handy way to push the current branch to the same name on the remote. -`git push origin master:satellite/master dev:satellite/dev`:: +`git push mothership master:satellite/master dev:satellite/dev`:: Use the source ref that matches `master` (e.g. `refs/heads/master`) to update the ref that matches `satellite/master` (most probably - `refs/remotes/satellite/master`) in the `origin` repository, then + `refs/remotes/satellite/master`) in the `mothership` repository; do the same for `dev` and `satellite/dev`. ++ +This is to emulate `git fetch` run on the `mothership` using `git +push` that is run in the opposite direction in order to integrate +the work done on `satellite`, and is often necessary when you can +only make connection in one way (i.e. satellite can ssh into +mothership but mothership cannot initiate connection to satellite +because the latter is behind a firewall or does not run sshd). ++ +After running this `git push` on the `satellite` machine, you would +ssh into the `mothership` and run `git merge` there to complete the +emulation of `git pull` that were run on `mothership` to pull changes +made on `satellite`. `git push origin HEAD:master`:: Push the current branch to the remote ref matching `master` in the diff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt index f5836e46d0..5ce4cda8e7 100644 --- a/Documentation/git-remote-helpers.txt +++ b/Documentation/git-remote-helpers.txt @@ -98,6 +98,20 @@ advertised with this capability must cover all refs reported by the list command. If no 'refspec' capability is advertised, there is an implied `refspec *:*`. +'bidi-import':: + The fast-import commands 'cat-blob' and 'ls' can be used by remote-helpers + to retrieve information about blobs and trees that already exist in + fast-import's memory. This requires a channel from fast-import to the + remote-helper. + If it is advertised in addition to "import", git establishes a pipe from + fast-import to the remote-helper's stdin. + It follows that git and fast-import are both connected to the + remote-helper's stdin. Because git can send multiple commands to + the remote-helper it is required that helpers that use 'bidi-import' + buffer all 'import' commands of a batch before sending data to fast-import. + This is to prevent mixing commands and fast-import responses on the + helper's stdin. + Capabilities for Pushing ~~~~~~~~~~~~~~~~~~~~~~~~ 'connect':: @@ -286,7 +300,12 @@ terminated with a blank line. For each batch of 'import', the remote helper should produce a fast-import stream terminated by a 'done' command. + -Supported if the helper has the "import" capability. +Note that if the 'bidi-import' capability is used the complete batch +sequence has to be buffered before starting to send data to fast-import +to prevent mixing of commands and fast-import responses on the helper's +stdin. ++ +Supported if the helper has the 'import' capability. 'connect' :: Connects to given service. Standard input and standard output diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index 117e3743a6..978d8da50c 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -10,7 +10,7 @@ SYNOPSIS [verse] 'git reset' [-q] [] [--] ... 'git reset' (--patch | -p) [] [--] [...] -'git reset' (--soft | --mixed | --hard | --merge | --keep) [-q] [] +'git reset' [--soft | --mixed | --hard | --merge | --keep] [-q] [] DESCRIPTION ----------- @@ -43,11 +43,11 @@ This means that `git reset -p` is the opposite of `git add -p`, i.e. you can use it to selectively reset hunks. See the ``Interactive Mode'' section of linkgit:git-add[1] to learn how to operate the `--patch` mode. -'git reset' -- []:: +'git reset' [] []:: This form resets the current branch head to and possibly updates the index (resetting it to the tree of ) and - the working tree depending on , which - must be one of the following: + the working tree depending on . If is omitted, + defaults to "--mixed". The must be one of the following: + -- --soft:: diff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt index 5d31860eb1..262436b7b1 100644 --- a/Documentation/git-rm.txt +++ b/Documentation/git-rm.txt @@ -134,6 +134,21 @@ use the following command: git diff --name-only --diff-filter=D -z | xargs -0 git rm --cached ---------------- +Submodules +~~~~~~~~~~ +Only submodules using a gitfile (which means they were cloned +with a git version 1.7.8 or newer) will be removed from the work +tree, as their repository lives inside the .git directory of the +superproject. If a submodule (or one of those nested inside it) +still uses a .git directory, `git rm` will fail - no matter if forced +or not - to protect the submodule's history. + +A submodule is considered up-to-date when the HEAD is the same as +recorded in the index, no tracked files are modified and no untracked +files that aren't ignored are present in the submodules work tree. +Ignored files are deemed expendable and won't stop a submodule's work +tree from being removed. + EXAMPLES -------- `git rm Documentation/\*.txt`:: diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index 324117072d..eeb561cf14 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -126,6 +126,10 @@ The --to option must be repeated for each user you want on the to list. + Note that no attempts whatsoever are made to validate the encoding. +--compose-encoding=:: + Specify encoding of compose message. Default is the value of the + 'sendemail.composeencoding'; if that is unspecified, UTF-8 is assumed. + Sending ~~~~~~~ diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index 67e5f53a9e..9f1ef9a463 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -38,6 +38,9 @@ OPTIONS across git versions and regardless of user configuration. See below for details. +--long:: + Give the output in the long-format. This is the default. + -u[]:: --untracked-files[=]:: Show untracked files. diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index b4683bba1b..b1de3bade7 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -9,7 +9,7 @@ git-submodule - Initialize, update or inspect submodules SYNOPSIS -------- [verse] -'git submodule' [--quiet] add [-b branch] [-f|--force] +'git submodule' [--quiet] add [-b ] [-f|--force] [--name ] [--reference ] [--] [] 'git submodule' [--quiet] status [--cached] [--recursive] [--] [...] 'git submodule' [--quiet] init [--] [...] @@ -265,6 +265,11 @@ OPTIONS Initialize all submodules for which "git submodule init" has not been called so far before updating. +--name:: + This option is only valid for the add command. It sets the submodule's + name to the given string instead of defaulting to its path. The name + must be valid as a directory name and may not end with a '/'. + --reference :: This option is only valid for add and update commands. These commands sometimes need to clone a remote repository. In this case, diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index cfe8d2b5df..8b0d3adfed 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -146,6 +146,13 @@ Skip "branches" and "tags" of first level directories;; ------------------------------------------------------------------------ -- +--log-window-size=;; + Fetch log entries per request when scanning Subversion history. + The default is 100. For very large Subversion repositories, larger + values may be needed for 'clone'/'fetch' to complete in reasonable + time. But overly large values may lead to higher memory usage and + request timeouts. + 'clone':: Runs 'init' and 'fetch'. It will automatically create a directory based on the basename of the URL passed to it; diff --git a/Documentation/git-symbolic-ref.txt b/Documentation/git-symbolic-ref.txt index 981d3a8fc1..ef68ad2b71 100644 --- a/Documentation/git-symbolic-ref.txt +++ b/Documentation/git-symbolic-ref.txt @@ -3,13 +3,14 @@ git-symbolic-ref(1) NAME ---- -git-symbolic-ref - Read and modify symbolic refs +git-symbolic-ref - Read, modify and delete symbolic refs SYNOPSIS -------- [verse] 'git symbolic-ref' [-m ] 'git symbolic-ref' [-q] [--short] +'git symbolic-ref' --delete [-q] DESCRIPTION ----------- @@ -21,6 +22,9 @@ argument to see which branch your working tree is on. Given two arguments, creates or updates a symbolic ref to point at the given branch . +Given `--delete` and an additional argument, deletes the given +symbolic ref. + A symbolic ref is a regular file that stores a string that begins with `ref: refs/`. For example, your `.git/HEAD` is a regular file whose contents is `ref: refs/heads/master`. @@ -28,6 +32,10 @@ a regular file whose contents is `ref: refs/heads/master`. OPTIONS ------- +-d:: +--delete:: + Delete the symbolic ref . + -q:: --quiet:: Do not issue an error message if the is not a diff --git a/Documentation/git.txt b/Documentation/git.txt index 4a89a12513..cbe0883534 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,10 +43,11 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.8.0/git.html[documentation for release 1.8.0] +* link:v1.8.0.1/git.html[documentation for release 1.8.0.1] * release notes for - link:RelNotes/1.8.0.txt[1.8.0], + link:RelNotes/1.8.0.1.txt[1.8.0.1], + link:RelNotes/1.8.0.txt[1.8.0]. * link:v1.7.12.4/git.html[documentation for release 1.7.12.4] diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index ba02d4de59..2698f63cf9 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -56,6 +56,7 @@ When more than one pattern matches the path, a later line overrides an earlier line. This overriding is done per attribute. The rules how the pattern matches paths are the same as in `.gitignore` files; see linkgit:gitignore[5]. +Unlike `.gitignore`, negative patterns are forbidden. When deciding what attributes are assigned to a path, git consults `$GIT_DIR/info/attributes` file (which has the highest diff --git a/Documentation/gitmodules.txt b/Documentation/gitmodules.txt index 4effd78902..ab3e91c054 100644 --- a/Documentation/gitmodules.txt +++ b/Documentation/gitmodules.txt @@ -18,7 +18,9 @@ working tree, is a text file with a syntax matching the requirements of linkgit:git-config[1]. The file contains one subsection per submodule, and the subsection value -is the name of the submodule. Each submodule section also contains the +is the name of the submodule. The name is set to the path where the +submodule has been added unless it was customized with the '--name' +option of 'git submodule add'. Each submodule section also contains the following required keys: submodule..path:: diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt index 5c891f1169..9f628862b4 100644 --- a/Documentation/gitrepository-layout.txt +++ b/Documentation/gitrepository-layout.txt @@ -93,6 +93,12 @@ refs/remotes/`name`:: records tip-of-the-tree commit objects of branches copied from a remote repository. +refs/replace/``:: + records the SHA1 of the object that replaces ``. + This is similar to info/grafts and is internally used and + maintained by linkgit:git-replace[1]. Such refs can be exchanged + between repositories while grafts are not. + packed-refs:: records the same information as refs/heads/, refs/tags/, and friends record in a more efficient way. See diff --git a/Documentation/howto/maintain-git.txt b/Documentation/howto/maintain-git.txt index 8823a37067..ea6e4a52c9 100644 --- a/Documentation/howto/maintain-git.txt +++ b/Documentation/howto/maintain-git.txt @@ -5,6 +5,10 @@ Abstract: Imagine that git development is racing along as usual, when our friend neighborhood maintainer is struck down by a wayward bus. Out of the hordes of suckers (loyal developers), you have been tricked (chosen) to step up as the new maintainer. This howto will show you "how to" do it. +Content-type: text/asciidoc + +How to maintain Git +=================== The maintainer's git time is spent on three activities. diff --git a/Documentation/howto/rebase-from-internal-branch.txt b/Documentation/howto/rebase-from-internal-branch.txt index 74a1c0c4ba..4627ee47f2 100644 --- a/Documentation/howto/rebase-from-internal-branch.txt +++ b/Documentation/howto/rebase-from-internal-branch.txt @@ -8,7 +8,12 @@ Abstract: In this article, JC talks about how he rebases the the "master" branch, and how "rebase" works. Also discussed is how this applies to individual developers who sends patches upstream. +Content-type: text/asciidoc +How to rebase from an internal branch +===================================== + +-------------------------------------- Petr Baudis writes: > Dear diary, on Sun, Aug 14, 2005 at 09:57:13AM CEST, I got a letter @@ -19,6 +24,7 @@ Petr Baudis writes: >> > branch to the real branches. >> > Actually, wouldn't this be also precisely for what StGIT is intended to? +-------------------------------------- Exactly my feeling. I was sort of waiting for Catalin to speak up. With its basing philosophical ancestry on quilt, this is @@ -156,8 +162,3 @@ you continue on starting from the new "master" head, which is the #1' commit. -jc - -- -To unsubscribe from this list: send the line "unsubscribe git" in -the body of a message to majordomo@vger.kernel.org -More majordomo info at http://vger.kernel.org/majordomo-info.html diff --git a/Documentation/howto/rebuild-from-update-hook.txt b/Documentation/howto/rebuild-from-update-hook.txt index 48c67568d3..00c1b45b79 100644 --- a/Documentation/howto/rebuild-from-update-hook.txt +++ b/Documentation/howto/rebuild-from-update-hook.txt @@ -5,6 +5,10 @@ Date: Fri, 26 Aug 2005 18:19:10 -0700 Abstract: In this how-to article, JC talks about how he uses the post-update hook to automate git documentation page shown at http://www.kernel.org/pub/software/scm/git/docs/. +Content-type: text/asciidoc + +How to rebuild from update hook +=============================== The pages under http://www.kernel.org/pub/software/scm/git/docs/ are built from Documentation/ directory of the git.git project diff --git a/Documentation/howto/recover-corrupted-blob-object.txt b/Documentation/howto/recover-corrupted-blob-object.txt index 323b513ed0..7484735320 100644 --- a/Documentation/howto/recover-corrupted-blob-object.txt +++ b/Documentation/howto/recover-corrupted-blob-object.txt @@ -3,11 +3,17 @@ From: Linus Torvalds Subject: corrupt object on git-gc Abstract: Some tricks to reconstruct blob objects in order to fix a corrupted repository. +Content-type: text/asciidoc +How to recover a corrupted blob object +====================================== + +----------------------------------------------------------- On Fri, 9 Nov 2007, Yossi Leybovich wrote: > > Did not help still the repository look for this object? > Any one know how can I track this object and understand which file is it +----------------------------------------------------------- So exactly *because* the SHA1 hash is cryptographically secure, the hash itself doesn't actually tell you anything, in order to fix a corrupt @@ -31,19 +37,23 @@ original object, so right now the corrupt object is useless, but it's very interesting for the future, in the hope that you can re-create a non-corrupt version. +----------------------------------------------------------- So: > ib]$ mv .git/objects/4b/9458b3786228369c63936db65827de3cc06200 ../ +----------------------------------------------------------- This is the right thing to do, although it's usually best to save it under it's full SHA1 name (you just dropped the "4b" from the result ;). Let's see what that tells us: +----------------------------------------------------------- > ib]$ git-fsck --full > broken link from tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8 > to blob 4b9458b3786228369c63936db65827de3cc06200 > missing blob 4b9458b3786228369c63936db65827de3cc06200 +----------------------------------------------------------- Ok, I removed the "dangling commit" messages, because they are just messages about the fact that you probably have rebased etc, so they're not diff --git a/Documentation/howto/revert-a-faulty-merge.txt b/Documentation/howto/revert-a-faulty-merge.txt index 6fd711996a..8a685483f4 100644 --- a/Documentation/howto/revert-a-faulty-merge.txt +++ b/Documentation/howto/revert-a-faulty-merge.txt @@ -7,6 +7,10 @@ Abstract: Sometimes a branch that was already merged to the mainline after the offending branch is fixed. Message-ID: <7vocz8a6zk.fsf@gitster.siamese.dyndns.org> References: +Content-type: text/asciidoc + +How to revert a faulty merge +============================ Alan said: diff --git a/Documentation/howto/revert-branch-rebase.txt b/Documentation/howto/revert-branch-rebase.txt index 093c656048..a59ced8d04 100644 --- a/Documentation/howto/revert-branch-rebase.txt +++ b/Documentation/howto/revert-branch-rebase.txt @@ -8,8 +8,8 @@ Date: Mon, 29 Aug 2005 21:39:02 -0700 Content-type: text/asciidoc Message-ID: <7voe7g3uop.fsf@assigned-by-dhcp.cox.net> -Reverting an existing commit -============================ +How to revert an existing commit +================================ One of the changes I pulled into the 'master' branch turns out to break building GIT with GCC 2.95. While they were well intentioned diff --git a/Documentation/howto/separating-topic-branches.txt b/Documentation/howto/separating-topic-branches.txt index 6d3eb8ed00..bd1027433b 100644 --- a/Documentation/howto/separating-topic-branches.txt +++ b/Documentation/howto/separating-topic-branches.txt @@ -1,6 +1,10 @@ From: Junio C Hamano Subject: Separating topic branches Abstract: In this article, JC describes how to separate topic branches. +Content-type: text/asciidoc + +How to separate topic branches +============================== This text was originally a footnote to a discussion about the behaviour of the git diff commands. diff --git a/Documentation/howto/setup-git-server-over-http.txt b/Documentation/howto/setup-git-server-over-http.txt index 622ee5c8dd..a695f01f0e 100644 --- a/Documentation/howto/setup-git-server-over-http.txt +++ b/Documentation/howto/setup-git-server-over-http.txt @@ -1,6 +1,10 @@ From: Rutger Nijlunsing Subject: Setting up a git repository which can be pushed into and pulled from over HTTP(S). Date: Thu, 10 Aug 2006 22:00:26 +0200 +Content-type: text/asciidoc + +How to setup git server over http +================================= Since Apache is one of those packages people like to compile themselves while others prefer the bureaucrat's dream Debian, it is diff --git a/Documentation/howto/update-hook-example.txt b/Documentation/howto/update-hook-example.txt index b7f8d416d6..a5193b1e5c 100644 --- a/Documentation/howto/update-hook-example.txt +++ b/Documentation/howto/update-hook-example.txt @@ -5,6 +5,10 @@ Message-ID: <7vfypumlu3.fsf@assigned-by-dhcp.cox.net> Abstract: An example hooks/update script is presented to implement repository maintenance policies, such as who can push into which branch and who can make a tag. +Content-type: text/asciidoc + +How to use the update hook +========================== When your developer runs git-push into the repository, git-receive-pack is run (either locally or over ssh) as that @@ -32,8 +36,7 @@ like this as your hooks/update script. [jc: editorial note. This is a much improved version by Carl since I posted the original outline] --- >8 -- beginning of script -- >8 -- - +---------------------------------------------------- #!/bin/bash umask 002 @@ -111,12 +114,12 @@ then info "Found matching head pattern: '$head_pattern'" for user_pattern in $user_patterns; do - info "Checking user: '$username' against pattern: '$user_pattern'" - matchlen=$(expr "$username" : "$user_pattern") - if test "$matchlen" = "${#username}" - then - grant "Allowing user: '$username' with pattern: '$user_pattern'" - fi + info "Checking user: '$username' against pattern: '$user_pattern'" + matchlen=$(expr "$username" : "$user_pattern") + if test "$matchlen" = "${#username}" + then + grant "Allowing user: '$username' with pattern: '$user_pattern'" + fi done deny "The user is not in the access list for this branch" done @@ -149,13 +152,13 @@ then info "Found matching head pattern: '$head_pattern'" for group_pattern in $group_patterns; do - for groupname in $groups; do - info "Checking group: '$groupname' against pattern: '$group_pattern'" - matchlen=$(expr "$groupname" : "$group_pattern") - if test "$matchlen" = "${#groupname}" - then - grant "Allowing group: '$groupname' with pattern: '$group_pattern'" - fi + for groupname in $groups; do + info "Checking group: '$groupname' against pattern: '$group_pattern'" + matchlen=$(expr "$groupname" : "$group_pattern") + if test "$matchlen" = "${#groupname}" + then + grant "Allowing group: '$groupname' with pattern: '$group_pattern'" + fi done done deny "None of the user's groups are in the access list for this branch" @@ -169,24 +172,21 @@ then fi deny >/dev/null "There are no more rules to check. Denying access" - --- >8 -- end of script -- >8 -- +---------------------------------------------------- This uses two files, $GIT_DIR/info/allowed-users and allowed-groups, to describe which heads can be pushed into by whom. The format of each file would look like this: - refs/heads/master junio - +refs/heads/pu junio - refs/heads/cogito$ pasky - refs/heads/bw/.* linus - refs/heads/tmp/.* .* - refs/tags/v[0-9].* junio + refs/heads/master junio + +refs/heads/pu junio + refs/heads/cogito$ pasky + refs/heads/bw/.* linus + refs/heads/tmp/.* .* + refs/tags/v[0-9].* junio With this, Linus can push or create "bw/penguin" or "bw/zebra" or "bw/panda" branches, Pasky can do only "cogito", and JC can do master and pu branches and make versioned tags. And anybody can do tmp/blah branches. The '+' sign at the pu record means that JC can make non-fast-forward pushes on it. - ------------- diff --git a/Documentation/howto/use-git-daemon.txt b/Documentation/howto/use-git-daemon.txt index 4e2f75cb61..23cdf35435 100644 --- a/Documentation/howto/use-git-daemon.txt +++ b/Documentation/howto/use-git-daemon.txt @@ -1,4 +1,7 @@ +Content-type: text/asciidoc + How to use git-daemon +===================== Git can be run in inetd mode and in stand alone mode. But all you want is let a coworker pull from you, and therefore need to set up a git server diff --git a/Documentation/howto/using-signed-tag-in-pull-request.txt b/Documentation/howto/using-signed-tag-in-pull-request.txt index 98c0033a55..00f693bde8 100644 --- a/Documentation/howto/using-signed-tag-in-pull-request.txt +++ b/Documentation/howto/using-signed-tag-in-pull-request.txt @@ -7,8 +7,8 @@ Abstract: Beginning v1.7.9, a contributor can push a signed tag to her later validate it. Content-type: text/asciidoc -Using signed tag in pull requests -================================= +How to use a signed tag in pull requests +======================================== A typical distributed workflow using Git is for a contributor to fork a project, build on it, publish the result to her public repository, and ask diff --git a/Documentation/merge-config.txt b/Documentation/merge-config.txt index 861bd6f553..9bb4956ccd 100644 --- a/Documentation/merge-config.txt +++ b/Documentation/merge-config.txt @@ -9,11 +9,11 @@ merge.conflictstyle:: merge.defaultToUpstream:: If merge is called without any commit argument, merge the upstream branches configured for the current branch by using their last - observed values stored in their remote tracking branches. + observed values stored in their remote-tracking branches. The values of the `branch..merge` that name the branches at the remote named by `branch..remote` are consulted, and then they are mapped via `remote..fetch` - to their corresponding remote tracking branches, and the tips of + to their corresponding remote-tracking branches, and the tips of these tracking branches are merged. merge.ff:: diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index ee497430cb..1ec14a068e 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -79,6 +79,11 @@ if it is part of the log message. Match the regexp limiting patterns without regard to letters case. +--basic-regexp:: + + Consider the limiting patterns to be basic regular expressions; + this is the default. + -E:: --extended-regexp:: @@ -91,6 +96,11 @@ if it is part of the log message. Consider the limiting patterns to be fixed strings (don't interpret pattern as a regular expression). +--perl-regexp:: + + Consider the limiting patterns to be Perl-compatible regexp. + Requires libpcre to be compiled in. + --remove-empty:: Stop when a given path disappears from the tree. diff --git a/Documentation/technical/api-argv-array.txt b/Documentation/technical/api-argv-array.txt index 1a797812fb..a959517b23 100644 --- a/Documentation/technical/api-argv-array.txt +++ b/Documentation/technical/api-argv-array.txt @@ -53,3 +53,11 @@ Functions `argv_array_clear`:: Free all memory associated with the array and return it to the initial, empty state. + +`argv_array_detach`:: + Detach the argv array from the `struct argv_array`, transfering + ownership of the allocated array and strings. + +`argv_array_free_detached`:: + Free the memory allocated by a `struct argv_array` that was later + detached and is now no longer needed. diff --git a/Documentation/technical/api-command.txt b/Documentation/technical/api-command.txt new file mode 100644 index 0000000000..ea9b2eda31 --- /dev/null +++ b/Documentation/technical/api-command.txt @@ -0,0 +1,99 @@ +Integrating new subcommands +=========================== + +This is how-to documentation for people who want to add extension +commands to git. It should be read alongside api-builtin.txt. + +Runtime environment +------------------- + +git subcommands are standalone executables that live in the git exec +path, normally /usr/lib/git-core. The git executable itself is a +thin wrapper that knows where the subcommands live, and runs them by +passing command-line arguments to them. + +(If "git foo" is not found in the git exec path, the wrapper +will look in the rest of your $PATH for it. Thus, it's possible +to write local git extensions that don't live in system space.) + +Implementation languages +------------------------ + +Most subcommands are written in C or shell. A few are written in +Perl. + +While we strongly encourage coding in portable C for portability, +these specific scripting languages are also acceptable. We won't +accept more without a very strong technical case, as we don't want +to broaden the git suite's required dependencies. Import utilities, +surgical tools, remote helpers and other code at the edges of the +git suite are more lenient and we allow Python (and even Tcl/tk), +but they should not be used for core functions. + +This may change in the future. Especially Python is not allowed in +core because we need better Python integration in the git Windows +installer before we can be confident people in that environment +won't experience an unacceptably large loss of capability. + +C commands are normally written as single modules, named after the +command, that link a collection of functions called libgit. Thus, +your command 'git-foo' would normally be implemented as a single +"git-foo.c" (or "builtin/foo.c" if it is to be linked to the main +binary); this organization makes it easy for people reading the code +to find things. + +See the CodingGuidelines document for other guidance on what we consider +good practice in C and shell, and api-builtin.txt for the support +functions available to built-in commands written in C. + +What every extension command needs +---------------------------------- + +You must have a man page, written in asciidoc (this is what git help +followed by your subcommand name will display). Be aware that there is +a local asciidoc configuration and macros which you should use. It's +often helpful to start by cloning an existing page and replacing the +text content. + +You must have a test, written to report in TAP (Test Anything Protocol). +Tests are executables (usually shell scripts) that live in the 't' +subdirectory of the tree. Each test name begins with 't' and a sequence +number that controls where in the test sequence it will be executed; +conventionally the rest of the name stem is that of the command +being tested. + +Read the file t/README to learn more about the conventions to be used +in writing tests, and the test support library. + +Integrating a command +--------------------- + +Here are the things you need to do when you want to merge a new +subcommand into the git tree. + +0. Don't forget to sign off your patch! + +1. Append your command name to one of the variables BUILTIN_OBJS, +EXTRA_PROGRAMS, SCRIPT_SH, SCRIPT_PERL or SCRIPT_PYTHON. + +2. Drop its test in the t directory. + +3. If your command is implemented in an interpreted language with a +p-code intermediate form, make sure .gitignore in the main directory +includes a pattern entry that ignores such files. Python .pyc and +.pyo files will already be covered. + +4. If your command has any dependency on a particular version of +your language, document it in the INSTALL file. + +5. There is a file command-list.txt in the distribution main directory +that categorizes commands by type, so they can be listed in appropriate +subsections in the documentation's summary command list. Add an entry +for yours. To understand the categories, look at git-cmmands.txt +in the main directory. + +6. Give the maintainer one paragraph to include in the RelNotes file +to describe the new feature; a good place to do so is in the cover +letter [PATCH 0/n]. + +That's all there is to it. diff --git a/Documentation/technical/api-strbuf.txt b/Documentation/technical/api-strbuf.txt index 95a8bf3846..84686b5c69 100644 --- a/Documentation/technical/api-strbuf.txt +++ b/Documentation/technical/api-strbuf.txt @@ -279,6 +279,22 @@ same behaviour as well. 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 diff --git a/Documentation/technical/api-string-list.txt b/Documentation/technical/api-string-list.txt index 94d7a2bd99..7386bcab3e 100644 --- a/Documentation/technical/api-string-list.txt +++ b/Documentation/technical/api-string-list.txt @@ -38,7 +38,8 @@ member (you need this if you add things later) and you should set the `unsorted_string_list_delete_item`. . Can remove items not matching a criterion from a sorted or unsorted - list using `filter_string_list`. + list using `filter_string_list`, or remove empty strings using + `string_list_remove_empty_items`. . Finally it should free the list using `string_list_clear`. @@ -75,6 +76,12 @@ Functions to be deleted. Preserve the order of the items that are retained. +`string_list_remove_empty_items`:: + + Remove any empty strings from the list. If free_util is true, + call free() on the util members of any items that have to be + deleted. Preserve the order of the items that are retained. + `string_list_longest_prefix`:: Return the longest string within a string_list that is a diff --git a/Documentation/technical/index-format.txt b/Documentation/technical/index-format.txt index 9d25b30178..57d6f915b1 100644 --- a/Documentation/technical/index-format.txt +++ b/Documentation/technical/index-format.txt @@ -1,7 +1,7 @@ GIT index format ================ -= The git index file has the following format +== The git index file has the following format All binary numbers are in network byte order. Version 2 is described here unless stated otherwise. diff --git a/Documentation/technical/pack-format.txt b/Documentation/technical/pack-format.txt index 1803e64e46..a7871fb865 100644 --- a/Documentation/technical/pack-format.txt +++ b/Documentation/technical/pack-format.txt @@ -1,7 +1,7 @@ GIT pack format =============== -= pack-*.pack files have the following format: +== pack-*.pack files have the following format: - A header appears at the beginning and consists of the following: @@ -34,7 +34,7 @@ GIT pack format - The trailer records 20-byte SHA1 checksum of all of the above. -= Original (version 1) pack-*.idx files have the following format: +== Original (version 1) pack-*.idx files have the following format: - The header consists of 256 4-byte network byte order integers. N-th entry of this table records the number of @@ -123,8 +123,8 @@ Pack file entry: <+ -= Version 2 pack-*.idx files support packs larger than 4 GiB, and - have some other reorganizations. They have the format: +== Version 2 pack-*.idx files support packs larger than 4 GiB, and + have some other reorganizations. They have the format: - A 4-byte magic number '\377tOc' which is an unreasonable fanout[0] value. diff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt index d51e20f352..f1a51edf47 100644 --- a/Documentation/technical/pack-protocol.txt +++ b/Documentation/technical/pack-protocol.txt @@ -117,7 +117,7 @@ A few things to remember here: - The repository path is always quoted with single quotes. Fetching Data From a Server -=========================== +--------------------------- When one Git repository wants to get data that a second repository has, the first can 'fetch' from the second. This operation determines @@ -134,7 +134,8 @@ with the object name that each reference currently points to. $ echo -e -n "0039git-upload-pack /schacon/gitbook.git\0host=example.com\0" | nc -v example.com 9418 - 00887217a7c7e582c46cec22a130adf4b9d7d950fba0 HEAD\0multi_ack thin-pack side-band side-band-64k ofs-delta shallow no-progress include-tag + 00887217a7c7e582c46cec22a130adf4b9d7d950fba0 HEAD\0multi_ack thin-pack + side-band side-band-64k ofs-delta shallow no-progress include-tag 00441d3fcd5ced445d1abc402225c0b8a1299641f497 refs/heads/integration 003f7217a7c7e582c46cec22a130adf4b9d7d950fba0 refs/heads/master 003cb88d2441cac0977faf98efc80305012112238d9d refs/tags/v0.9 @@ -421,7 +422,7 @@ entire packfile without multiplexing. Pushing Data To a Server -======================== +------------------------ Pushing data to a server will invoke the 'receive-pack' process on the server, which will allow the client to tell it which references it should diff --git a/Documentation/technical/send-pack-pipeline.txt b/Documentation/technical/send-pack-pipeline.txt index 681efe4219..9b5a0bc186 100644 --- a/Documentation/technical/send-pack-pipeline.txt +++ b/Documentation/technical/send-pack-pipeline.txt @@ -1,5 +1,5 @@ -git-send-pack -============= +Git-send-pack internals +======================= Overall operation ----------------- diff --git a/Documentation/technical/shallow.txt b/Documentation/technical/shallow.txt index 559263af48..0502a5471e 100644 --- a/Documentation/technical/shallow.txt +++ b/Documentation/technical/shallow.txt @@ -1,6 +1,12 @@ -Def.: Shallow commits do have parents, but not in the shallow +Shallow commits +=============== + +.Definition +********************************************************* +Shallow commits do have parents, but not in the shallow repo, and therefore grafts are introduced pretending that these commits have no parents. +********************************************************* The basic idea is to write the SHA1s of shallow commits into $GIT_DIR/shallow, and handle its contents like the contents diff --git a/Documentation/technical/trivial-merge.txt b/Documentation/technical/trivial-merge.txt index 24c84100b0..c79d4a7c47 100644 --- a/Documentation/technical/trivial-merge.txt +++ b/Documentation/technical/trivial-merge.txt @@ -74,24 +74,24 @@ For multiple ancestors, a '+' means that this case applies even if only one ancestor or remote fits; a '^' means all of the ancestors must be the same. -case ancest head remote result ----------------------------------------- -1 (empty)+ (empty) (empty) (empty) -2ALT (empty)+ *empty* remote remote -2 (empty)^ (empty) remote no merge -3ALT (empty)+ head *empty* head -3 (empty)^ head (empty) no merge -4 (empty)^ head remote no merge -5ALT * head head head -6 ancest+ (empty) (empty) no merge -8 ancest^ (empty) ancest no merge -7 ancest+ (empty) remote no merge -10 ancest^ ancest (empty) no merge -9 ancest+ head (empty) no merge -16 anc1/anc2 anc1 anc2 no merge -13 ancest+ head ancest head -14 ancest+ ancest remote remote -11 ancest+ head remote no merge + case ancest head remote result + ---------------------------------------- + 1 (empty)+ (empty) (empty) (empty) + 2ALT (empty)+ *empty* remote remote + 2 (empty)^ (empty) remote no merge + 3ALT (empty)+ head *empty* head + 3 (empty)^ head (empty) no merge + 4 (empty)^ head remote no merge + 5ALT * head head head + 6 ancest+ (empty) (empty) no merge + 8 ancest^ (empty) ancest no merge + 7 ancest+ (empty) remote no merge + 10 ancest^ ancest (empty) no merge + 9 ancest+ head (empty) no merge + 16 anc1/anc2 anc1 anc2 no merge + 13 ancest+ head ancest head + 14 ancest+ ancest remote remote + 11 ancest+ head remote no merge Only #2ALT and #3ALT use *empty*, because these are the only cases where there can be conflicts that didn't exist before. Note that we diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index 85651b57ae..1b377dc207 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -1787,6 +1787,13 @@ $ git format-patch origin will produce a numbered series of files in the current directory, one for each patch in the current branch but not in origin/HEAD. +`git format-patch` can include an initial "cover letter". You can insert +commentary on individual patches after the three dash line which +`format-patch` places after the commit message but before the patch +itself. If you use `git notes` to track your cover letter material, +`git format-patch --notes` will include the commit's notes in a similar +manner. + You can then import these into your mail client and send them by hand. However, if you have a lot to send at once, you may prefer to use the linkgit:git-send-email[1] script to automate the process. diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index b09d264a86..d66e54f12d 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.8.0 +DEF_VER=v1.8.0.1 LF=' ' diff --git a/Makefile b/Makefile index f69979e39e..4ad6fbd64e 100644 --- a/Makefile +++ b/Makefile @@ -495,6 +495,7 @@ PROGRAM_OBJS += sh-i18n--envsubst.o PROGRAM_OBJS += shell.o PROGRAM_OBJS += show-index.o PROGRAM_OBJS += upload-pack.o +PROGRAM_OBJS += remote-testsvn.o # Binary suffix, set to .exe for Windows builds X = @@ -745,6 +746,7 @@ LIB_OBJS += editor.o LIB_OBJS += entry.o LIB_OBJS += environment.o LIB_OBJS += exec_cmd.o +LIB_OBJS += fetch-pack.o LIB_OBJS += fsck.o LIB_OBJS += gettext.o LIB_OBJS += gpg-interface.o @@ -762,6 +764,7 @@ LIB_OBJS += lockfile.o LIB_OBJS += log-tree.o LIB_OBJS += mailmap.o LIB_OBJS += match-trees.o +LIB_OBJS += merge.o LIB_OBJS += merge-file.o LIB_OBJS += merge-recursive.o LIB_OBJS += mergesort.o @@ -796,6 +799,7 @@ LIB_OBJS += rerere.o LIB_OBJS += resolve-undo.o LIB_OBJS += revision.o LIB_OBJS += run-command.o +LIB_OBJS += send-pack.o LIB_OBJS += sequencer.o LIB_OBJS += server-info.o LIB_OBJS += setup.o @@ -1082,6 +1086,7 @@ ifeq ($(uname_O),Cygwin) NO_SYMLINK_HEAD = YesPlease NO_IPV6 = YesPlease OLD_ICONV = UnfortunatelyYes + CYGWIN_V15_WIN32API = YesPlease endif NO_THREAD_SAFE_PREAD = YesPlease NEEDS_LIBICONV = YesPlease @@ -1381,6 +1386,10 @@ ifeq ($(uname_S),NONSTOP_KERNEL) MKDIR_WO_TRAILING_SLASH = YesPlease # RFE 10-120912-4693 submitted to HP NonStop development. NO_SETITIMER = UnfortunatelyYes + SANE_TOOL_PATH=/usr/coreutils/bin:/usr/local/bin + SHELL_PATH=/usr/local/bin/bash + # as of H06.25/J06.14, we might better use this + #SHELL_PATH=/usr/coreutils/bin/bash endif ifneq (,$(findstring MINGW,$(uname_S))) pathsep = ; @@ -1564,7 +1573,7 @@ else REMOTE_CURL_NAMES = $(REMOTE_CURL_PRIMARY) $(REMOTE_CURL_ALIASES) PROGRAM_OBJS += http-fetch.o PROGRAMS += $(REMOTE_CURL_NAMES) - curl_check := $(shell (echo 070908; curl-config --vernum) | sort -r | sed -ne 2p) + curl_check := $(shell (echo 070908; curl-config --vernum) 2>/dev/null | sort -r | sed -ne 2p) ifeq "$(curl_check)" "070908" ifndef NO_EXPAT PROGRAM_OBJS += http-push.o @@ -1889,6 +1898,9 @@ ifdef NO_REGEX COMPAT_CFLAGS += -Icompat/regex COMPAT_OBJS += compat/regex/regex.o endif +ifdef CYGWIN_V15_WIN32API + COMPAT_CFLAGS += -DCYGWIN_V15_WIN32API +endif ifdef USE_NED_ALLOCATOR COMPAT_CFLAGS += -Icompat/nedmalloc @@ -2449,6 +2461,10 @@ git-http-push$X: revision.o http.o http-push.o GIT-LDFLAGS $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) +git-remote-testsvn$X: remote-testsvn.o GIT-LDFLAGS $(GITLIBS) $(VCSSVN_LIB) + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) \ + $(VCSSVN_LIB) + $(REMOTE_CURL_ALIASES): $(REMOTE_CURL_PRIMARY) $(QUIET_LNCP)$(RM) $@ && \ ln $< $@ 2>/dev/null || \ diff --git a/RelNotes b/RelNotes index 2484b38bbb..28607146f3 120000 --- a/RelNotes +++ b/RelNotes @@ -1 +1 @@ -Documentation/RelNotes/1.8.0.txt \ No newline at end of file +Documentation/RelNotes/1.8.1.txt \ No newline at end of file diff --git a/argv-array.c b/argv-array.c index 256741d226..9e960d549c 100644 --- a/argv-array.c +++ b/argv-array.c @@ -68,3 +68,23 @@ void argv_array_clear(struct argv_array *array) } argv_array_init(array); } + +const char **argv_array_detach(struct argv_array *array, int *argc) +{ + const char **argv = + array->argv == empty_argv || array->argc == 0 ? NULL : array->argv; + if (argc) + *argc = array->argc; + argv_array_init(array); + return argv; +} + +void argv_array_free_detached(const char **argv) +{ + if (argv) { + int i; + for (i = 0; argv[i]; i++) + free((char **)argv[i]); + free(argv); + } +} diff --git a/argv-array.h b/argv-array.h index f4b98660f8..40248d424c 100644 --- a/argv-array.h +++ b/argv-array.h @@ -18,5 +18,7 @@ void argv_array_pushf(struct argv_array *, const char *fmt, ...); void argv_array_pushl(struct argv_array *, ...); void argv_array_pop(struct argv_array *); void argv_array_clear(struct argv_array *); +const char **argv_array_detach(struct argv_array *array, int *argc); +void argv_array_free_detached(const char **argv); #endif /* ARGV_ARRAY_H */ diff --git a/attr.c b/attr.c index 887a9ae46b..097ae87f3c 100644 --- a/attr.c +++ b/attr.c @@ -115,6 +115,13 @@ struct attr_state { const char *setto; }; +struct pattern { + const char *pattern; + int patternlen; + int nowildcardlen; + int flags; /* EXC_FLAG_* */ +}; + /* * One rule, as from a .gitattributes file. * @@ -131,7 +138,7 @@ struct attr_state { */ struct match_attr { union { - char *pattern; + struct pattern pat; struct git_attr *attr; } u; char is_macro; @@ -241,9 +248,16 @@ static struct match_attr *parse_attr_line(const char *line, const char *src, if (is_macro) res->u.attr = git_attr_internal(name, namelen); else { - res->u.pattern = (char *)&(res->state[num_attr]); - memcpy(res->u.pattern, name, namelen); - res->u.pattern[namelen] = 0; + char *p = (char *)&(res->state[num_attr]); + memcpy(p, name, namelen); + res->u.pat.pattern = p; + parse_exclude_pattern(&res->u.pat.pattern, + &res->u.pat.patternlen, + &res->u.pat.flags, + &res->u.pat.nowildcardlen); + if (res->u.pat.flags & EXC_FLAG_NEGATIVE) + die(_("Negative patterns are forbidden in git attributes\n" + "Use '\\!' for literal leading exclamation.")); } res->is_macro = is_macro; res->num_attr = num_attr; @@ -277,6 +291,7 @@ static struct match_attr *parse_attr_line(const char *line, const char *src, static struct attr_stack { struct attr_stack *prev; char *origin; + size_t originlen; unsigned num_matches; unsigned alloc; struct match_attr **attrs; @@ -535,6 +550,7 @@ static void bootstrap_attr_stack(void) if (!is_bare_repository() || direction == GIT_ATTR_INDEX) { elem = read_attr(GITATTRIBUTES_FILE, 1); elem->origin = xstrdup(""); + elem->originlen = 0; elem->prev = attr_stack; attr_stack = elem; debug_push(elem); @@ -628,7 +644,7 @@ static void prepare_attr_stack(const char *path) strbuf_addstr(&pathbuf, GITATTRIBUTES_FILE); elem = read_attr(pathbuf.buf, 0); strbuf_setlen(&pathbuf, cp - path); - elem->origin = strbuf_detach(&pathbuf, NULL); + elem->origin = strbuf_detach(&pathbuf, &elem->originlen); elem->prev = attr_stack; attr_stack = elem; debug_push(elem); @@ -645,28 +661,22 @@ static void prepare_attr_stack(const char *path) } static int path_matches(const char *pathname, int pathlen, - const char *pattern, + const char *basename, + const struct pattern *pat, const char *base, int baselen) { - if (!strchr(pattern, '/')) { - /* match basename */ - const char *basename = strrchr(pathname, '/'); - basename = basename ? basename + 1 : pathname; - return (fnmatch_icase(pattern, basename, 0) == 0); + const char *pattern = pat->pattern; + int prefix = pat->nowildcardlen; + + if (pat->flags & EXC_FLAG_NODIR) { + return match_basename(basename, + pathlen - (basename - pathname), + pattern, prefix, + pat->patternlen, pat->flags); } - /* - * match with FNM_PATHNAME; the pattern has base implicitly - * in front of it. - */ - if (*pattern == '/') - pattern++; - if (pathlen < baselen || - (baselen && pathname[baselen] != '/') || - strncmp(pathname, base, baselen)) - return 0; - if (baselen != 0) - baselen++; - return fnmatch_icase(pattern, pathname + baselen, FNM_PATHNAME) == 0; + return match_pathname(pathname, pathlen, + base, baselen, + pattern, prefix, pat->patternlen, pat->flags); } static int macroexpand_one(int attr_nr, int rem); @@ -693,7 +703,8 @@ static int fill_one(const char *what, struct match_attr *a, int rem) return rem; } -static int fill(const char *path, int pathlen, struct attr_stack *stk, int rem) +static int fill(const char *path, int pathlen, const char *basename, + struct attr_stack *stk, int rem) { int i; const char *base = stk->origin ? stk->origin : ""; @@ -702,8 +713,8 @@ static int fill(const char *path, int pathlen, struct attr_stack *stk, int rem) struct match_attr *a = stk->attrs[i]; if (a->is_macro) continue; - if (path_matches(path, pathlen, - a->u.pattern, base, strlen(base))) + if (path_matches(path, pathlen, basename, + &a->u.pat, base, stk->originlen)) rem = fill_one("fill", a, rem); } return rem; @@ -741,15 +752,19 @@ static void collect_all_attrs(const char *path) { struct attr_stack *stk; int i, pathlen, rem; + const char *basename; prepare_attr_stack(path); for (i = 0; i < attr_nr; i++) check_all_attr[i].value = ATTR__UNKNOWN; + basename = strrchr(path, '/'); + basename = basename ? basename + 1 : path; + pathlen = strlen(path); rem = attr_nr; for (stk = attr_stack; 0 < rem && stk; stk = stk->prev) - rem = fill(path, pathlen, stk, rem); + rem = fill(path, pathlen, basename, stk, rem); } int git_check_attr(const char *path, int num, struct git_attr_check *check) diff --git a/bisect.c b/bisect.c index 1aad49b1a6..bd1b7b5dac 100644 --- a/bisect.c +++ b/bisect.c @@ -956,3 +956,41 @@ int bisect_next_all(const char *prefix, int no_checkout) return bisect_checkout(bisect_rev_hex, no_checkout); } +static inline int log2i(int n) +{ + int log2 = 0; + + for (; n > 1; n >>= 1) + log2++; + + return log2; +} + +static inline int exp2i(int n) +{ + return 1 << n; +} + +/* + * Estimate the number of bisect steps left (after the current step) + * + * For any x between 0 included and 2^n excluded, the probability for + * n - 1 steps left looks like: + * + * P(2^n + x) == (2^n - x) / (2^n + x) + * + * and P(2^n + x) < 0.5 means 2^n < 3x + */ +int estimate_bisect_steps(int all) +{ + int n, x, e; + + if (all < 3) + return 0; + + n = log2i(all); + e = exp2i(n); + x = all - e; + + return (e < 3 * x) ? n : n - 1; +} diff --git a/bisect.h b/bisect.h index ec3c3ff007..2a6c831f3e 100644 --- a/bisect.h +++ b/bisect.h @@ -11,10 +11,6 @@ extern struct commit_list *filter_skipped(struct commit_list *list, int *count, int *skipped_first); -extern void print_commit_list(struct commit_list *list, - const char *format_cur, - const char *format_last); - #define BISECT_SHOW_ALL (1<<0) #define REV_LIST_QUIET (1<<1) diff --git a/builtin.h b/builtin.h index 95116b8535..3faf9d6691 100644 --- a/builtin.h +++ b/builtin.h @@ -37,10 +37,6 @@ int copy_note_for_rewrite(struct notes_rewrite_cfg *c, const unsigned char *from_obj, const unsigned char *to_obj); void finish_copy_notes_for_rewrite(struct notes_rewrite_cfg *c); -extern int check_pager_config(const char *cmd); -struct diff_options; -extern void setup_diff_pager(struct diff_options *); - extern int textconv_object(const char *path, unsigned mode, const unsigned char *sha1, int sha1_valid, char **buf, unsigned long *buf_size); extern int cmd_add(int argc, const char **argv, const char *prefix); diff --git a/builtin/blame.c b/builtin/blame.c index c27ef21c23..cfae569905 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -1425,7 +1425,7 @@ static void get_commit_info(struct commit *commit, int detailed) { int len; - const char *subject; + const char *subject, *encoding; char *reencoded, *message; static char author_name[1024]; static char author_mail[1024]; @@ -1446,7 +1446,8 @@ static void get_commit_info(struct commit *commit, die("Cannot read commit %s", sha1_to_hex(commit->object.sha1)); } - reencoded = reencode_commit_message(commit, NULL); + encoding = get_log_output_encoding(); + reencoded = logmsg_reencode(commit, encoding); message = reencoded ? reencoded : commit->buffer; ret->author = author_name; ret->author_mail = author_mail; diff --git a/builtin/branch.c b/builtin/branch.c index ffd26849c7..1ec9c02612 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -154,10 +154,37 @@ static int branch_merged(int kind, const char *name, return merged; } +static int check_branch_commit(const char *branchname, const char *refname, + unsigned char *sha1, struct commit *head_rev, + int kinds, int force) +{ + struct commit *rev = lookup_commit_reference(sha1); + if (!rev) { + error(_("Couldn't look up commit object for '%s'"), refname); + return -1; + } + if (!force && !branch_merged(kinds, branchname, rev, head_rev)) { + error(_("The branch '%s' is not fully merged.\n" + "If you are sure you want to delete it, " + "run 'git branch -D %s'."), branchname, branchname); + return -1; + } + return 0; +} + +static void delete_branch_config(const char *branchname) +{ + struct strbuf buf = STRBUF_INIT; + strbuf_addf(&buf, "branch.%s", branchname); + if (git_config_rename_section(buf.buf, NULL) < 0) + warning(_("Update of config-file failed")); + strbuf_release(&buf); +} + static int delete_branches(int argc, const char **argv, int force, int kinds, int quiet) { - struct commit *rev, *head_rev = NULL; + struct commit *head_rev = NULL; unsigned char sha1[20]; char *name = NULL; const char *fmt; @@ -187,6 +214,9 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, die(_("Couldn't look up commit object for HEAD")); } for (i = 0; i < argc; i++, strbuf_release(&bname)) { + const char *target; + int flags = 0; + strbuf_branchname(&bname, argv[i]); if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) { error(_("Cannot delete the branch '%s' " @@ -198,7 +228,9 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, free(name); name = mkpathdup(fmt, bname.buf); - if (read_ref(name, sha1)) { + target = resolve_ref_unsafe(name, sha1, 0, &flags); + if (!target || + (!(flags & REF_ISSYMREF) && is_null_sha1(sha1))) { error(remote_branch ? _("remote branch '%s' not found.") : _("branch '%s' not found."), bname.buf); @@ -206,40 +238,31 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, continue; } - rev = lookup_commit_reference(sha1); - if (!rev) { - error(_("Couldn't look up commit object for '%s'"), name); - ret = 1; - continue; - } - - if (!force && !branch_merged(kinds, bname.buf, rev, head_rev)) { - error(_("The branch '%s' is not fully merged.\n" - "If you are sure you want to delete it, " - "run 'git branch -D %s'."), bname.buf, bname.buf); + if (!(flags & REF_ISSYMREF) && + check_branch_commit(bname.buf, name, sha1, head_rev, kinds, + force)) { ret = 1; continue; } - if (delete_ref(name, sha1, 0)) { + if (delete_ref(name, sha1, REF_NODEREF)) { error(remote_branch ? _("Error deleting remote branch '%s'") : _("Error deleting branch '%s'"), bname.buf); ret = 1; - } else { - struct strbuf buf = STRBUF_INIT; - if (!quiet) - printf(remote_branch - ? _("Deleted remote branch %s (was %s).\n") - : _("Deleted branch %s (was %s).\n"), - bname.buf, - find_unique_abbrev(sha1, DEFAULT_ABBREV)); - strbuf_addf(&buf, "branch.%s", bname.buf); - if (git_config_rename_section(buf.buf, NULL) < 0) - warning(_("Update of config-file failed")); - strbuf_release(&buf); + continue; + } + if (!quiet) { + printf(remote_branch + ? _("Deleted remote branch %s (was %s).\n") + : _("Deleted branch %s (was %s).\n"), + bname.buf, + (flags & REF_ISSYMREF) + ? target + : find_unique_abbrev(sha1, DEFAULT_ABBREV)); } + delete_branch_config(bname.buf); } free(name); diff --git a/builtin/checkout.c b/builtin/checkout.c index 781295b2c9..a9c1b5a95f 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -951,6 +951,9 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts) strbuf_addf(&branch_ref, "refs/heads/%s", opts->new_branch); status = create_symref("HEAD", branch_ref.buf, "checkout -b"); strbuf_release(&branch_ref); + if (!opts->quiet) + fprintf(stderr, _("Switched to a new branch '%s'\n"), + opts->new_branch); return status; } diff --git a/builtin/commit.c b/builtin/commit.c index a17a5df449..d6dd3df8b1 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -112,10 +112,11 @@ static const char *only_include_assumed; static struct strbuf message = STRBUF_INIT; static enum { + STATUS_FORMAT_NONE = 0, STATUS_FORMAT_LONG, STATUS_FORMAT_SHORT, STATUS_FORMAT_PORCELAIN -} status_format = STATUS_FORMAT_LONG; +} status_format; static int opt_parse_m(const struct option *opt, const char *arg, int unset) { @@ -454,6 +455,7 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int case STATUS_FORMAT_PORCELAIN: wt_porcelain_print(s); break; + case STATUS_FORMAT_NONE: case STATUS_FORMAT_LONG: wt_status_print(s); break; @@ -753,7 +755,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, ident_shown++ ? "" : "\n", author_ident->buf); - if (!user_ident_sufficiently_given()) + if (!committer_ident_sufficiently_given()) status_printf_ln(s, GIT_COLOR_NORMAL, _("%s" "Committer: %s"), @@ -1058,9 +1060,13 @@ static int parse_and_validate_options(int argc, const char *argv[], if (all && argc > 0) die(_("Paths with -a does not make sense.")); - if (s->null_termination && status_format == STATUS_FORMAT_LONG) - status_format = STATUS_FORMAT_PORCELAIN; - if (status_format != STATUS_FORMAT_LONG) + if (s->null_termination) { + if (status_format == STATUS_FORMAT_NONE) + status_format = STATUS_FORMAT_PORCELAIN; + else if (status_format == STATUS_FORMAT_LONG) + die(_("--long and -z are incompatible")); + } + if (status_format != STATUS_FORMAT_NONE) dry_run = 1; return argc; @@ -1159,6 +1165,9 @@ int cmd_status(int argc, const char **argv, const char *prefix) OPT_SET_INT(0, "porcelain", &status_format, N_("machine-readable output"), STATUS_FORMAT_PORCELAIN), + OPT_SET_INT(0, "long", &status_format, + N_("show status in long format (default)"), + STATUS_FORMAT_LONG), OPT_BOOLEAN('z', "null", &s.null_termination, N_("terminate entries with NUL")), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, @@ -1186,8 +1195,12 @@ int cmd_status(int argc, const char **argv, const char *prefix) builtin_status_usage, 0); finalize_colopts(&s.colopts, -1); - if (s.null_termination && status_format == STATUS_FORMAT_LONG) - status_format = STATUS_FORMAT_PORCELAIN; + if (s.null_termination) { + if (status_format == STATUS_FORMAT_NONE) + status_format = STATUS_FORMAT_PORCELAIN; + else if (status_format == STATUS_FORMAT_LONG) + die(_("--long and -z are incompatible")); + } handle_untracked_files_arg(&s); if (show_ignored_in_status) @@ -1216,6 +1229,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) case STATUS_FORMAT_PORCELAIN: wt_porcelain_print(&s); break; + case STATUS_FORMAT_NONE: case STATUS_FORMAT_LONG: s.verbose = verbose; s.ignore_submodule_arg = ignore_submodule_arg; @@ -1251,7 +1265,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1, strbuf_addstr(&format, "\n Author: "); strbuf_addbuf_percentquote(&format, &author_ident); } - if (!user_ident_sufficiently_given()) { + if (!committer_ident_sufficiently_given()) { strbuf_addstr(&format, "\n Committer: "); strbuf_addbuf_percentquote(&format, &committer_ident); if (advice_implicit_identity) { @@ -1386,6 +1400,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix) OPT_BOOLEAN(0, "branch", &s.show_branch, N_("show branch information")), OPT_SET_INT(0, "porcelain", &status_format, N_("machine-readable output"), STATUS_FORMAT_PORCELAIN), + OPT_SET_INT(0, "long", &status_format, + N_("show status in long format (default)"), + STATUS_FORMAT_LONG), OPT_BOOLEAN('z', "null", &s.null_termination, N_("terminate entries with NUL")), OPT_BOOLEAN(0, "amend", &amend, N_("amend previous commit")), diff --git a/builtin/config.c b/builtin/config.c index e1c33e0691..33c9bf9d84 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -15,7 +15,6 @@ static int show_keys; static int use_key_regexp; static int do_all; static int do_not_match; -static int seen; static char delim = '='; static char key_delim = ' '; static char term = '\n'; @@ -95,12 +94,19 @@ static int show_all_config(const char *key_, const char *value_, void *cb) return 0; } -static int show_config(const char *key_, const char *value_, void *cb) +struct strbuf_list { + struct strbuf *items; + int nr; + int alloc; +}; + +static int collect_config(const char *key_, const char *value_, void *cb) { + struct strbuf_list *values = cb; + struct strbuf *buf; char value[256]; const char *vptr = value; int must_free_vptr = 0; - int dup_error = 0; int must_print_delim = 0; if (!use_key_regexp && strcmp(key_, key)) @@ -111,12 +117,14 @@ static int show_config(const char *key_, const char *value_, void *cb) (do_not_match ^ !!regexec(regexp, (value_?value_:""), 0, NULL, 0))) return 0; + ALLOC_GROW(values->items, values->nr + 1, values->alloc); + buf = &values->items[values->nr++]; + strbuf_init(buf, 0); + if (show_keys) { - printf("%s", key_); + strbuf_addstr(buf, key_); must_print_delim = 1; } - if (seen && !do_all) - dup_error = 1; if (types == TYPE_INT) sprintf(value, "%d", git_config_int(key_, value_?value_:"")); else if (types == TYPE_BOOL) @@ -129,7 +137,8 @@ static int show_config(const char *key_, const char *value_, void *cb) else sprintf(value, "%d", v); } else if (types == TYPE_PATH) { - git_config_pathname(&vptr, key_, value_); + if (git_config_pathname(&vptr, key_, value_) < 0) + return -1; must_free_vptr = 1; } else if (value_) { vptr = value_; @@ -138,16 +147,12 @@ static int show_config(const char *key_, const char *value_, void *cb) vptr = ""; must_print_delim = 0; } - seen++; - if (dup_error) { - error("More than one value for the key %s: %s", - key_, vptr); - } - else { - if (must_print_delim) - printf("%c", key_delim); - printf("%s%c", vptr, term); - } + + if (must_print_delim) + strbuf_addch(buf, key_delim); + strbuf_addstr(buf, vptr); + strbuf_addch(buf, term); + if (must_free_vptr) /* If vptr must be freed, it's a pointer to a * dynamically allocated buffer, it's safe to cast to @@ -161,19 +166,8 @@ static int show_config(const char *key_, const char *value_, void *cb) static int get_value(const char *key_, const char *regex_) { int ret = CONFIG_GENERIC_ERROR; - char *global = NULL, *xdg = NULL, *repo_config = NULL; - const char *system_wide = NULL, *local; - struct config_include_data inc = CONFIG_INCLUDE_INIT; - config_fn_t fn; - void *data; - - local = given_config_file; - if (!local) { - local = repo_config = git_pathdup("config"); - if (git_config_system()) - system_wide = git_etc_gitconfig(); - home_config_paths(&global, &xdg, "config"); - } + struct strbuf_list values = {NULL}; + int i; if (use_key_regexp) { char *tl; @@ -195,7 +189,8 @@ static int get_value(const char *key_, const char *regex_) key_regexp = (regex_t*)xmalloc(sizeof(regex_t)); if (regcomp(key_regexp, key, REG_EXTENDED)) { fprintf(stderr, "Invalid key pattern: %s\n", key_); - free(key); + free(key_regexp); + key_regexp = NULL; ret = CONFIG_INVALID_PATTERN; goto free_strings; } @@ -215,53 +210,37 @@ static int get_value(const char *key_, const char *regex_) regexp = (regex_t*)xmalloc(sizeof(regex_t)); if (regcomp(regexp, regex_, REG_EXTENDED)) { fprintf(stderr, "Invalid pattern: %s\n", regex_); + free(regexp); + regexp = NULL; ret = CONFIG_INVALID_PATTERN; goto free_strings; } } - fn = show_config; - data = NULL; - if (respect_includes) { - inc.fn = fn; - inc.data = data; - fn = git_config_include; - data = &inc; - } - - if (do_all && system_wide) - git_config_from_file(fn, system_wide, data); - if (do_all && xdg) - git_config_from_file(fn, xdg, data); - if (do_all && global) - git_config_from_file(fn, global, data); - if (do_all) - git_config_from_file(fn, local, data); - git_config_from_parameters(fn, data); - if (!do_all && !seen) - git_config_from_file(fn, local, data); - if (!do_all && !seen && global) - git_config_from_file(fn, global, data); - if (!do_all && !seen && xdg) - git_config_from_file(fn, xdg, data); - if (!do_all && !seen && system_wide) - git_config_from_file(fn, system_wide, data); + git_config_with_options(collect_config, &values, + given_config_file, respect_includes); + + ret = !values.nr; + for (i = 0; i < values.nr; i++) { + struct strbuf *buf = values.items + i; + if (do_all || i == values.nr - 1) + fwrite(buf->buf, 1, buf->len, stdout); + strbuf_release(buf); + } + free(values.items); + +free_strings: free(key); + if (key_regexp) { + regfree(key_regexp); + free(key_regexp); + } if (regexp) { regfree(regexp); free(regexp); } - if (do_all) - ret = !seen; - else - ret = (seen == 1) ? 0 : seen > 1 ? 2 : 1; - -free_strings: - free(repo_config); - free(global); - free(xdg); return ret; } diff --git a/builtin/describe.c b/builtin/describe.c index 9fe11ed9de..04c185b1fb 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -144,7 +144,7 @@ static int get_name(const char *path, const unsigned char *sha1, int flag, void if (!all && !might_be_tag) return 0; - if (!peel_ref(path, peeled) && !is_null_sha1(peeled)) { + if (!peel_ref(path, peeled)) { is_tag = !!hashcmp(sha1, peeled); } else { hashcpy(peeled, sha1); diff --git a/builtin/diff-index.c b/builtin/diff-index.c index 2eb32bd9da..1c737f7921 100644 --- a/builtin/diff-index.c +++ b/builtin/diff-index.c @@ -41,9 +41,13 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix) if (rev.pending.nr != 1 || rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1) usage(diff_cache_usage); - if (!cached) + if (!cached) { setup_work_tree(); - if (read_cache() < 0) { + if (read_cache_preload(rev.diffopt.pathspec.raw) < 0) { + perror("read_cache_preload"); + return -1; + } + } else if (read_cache() < 0) { perror("read_cache"); return -1; } diff --git a/builtin/diff.c b/builtin/diff.c index 9650be2c50..8c2af6cb43 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -130,8 +130,6 @@ static int builtin_diff_index(struct rev_info *revs, usage(builtin_diff_usage); argv++; argc--; } - if (!cached) - setup_work_tree(); /* * Make sure there is one revision (i.e. pending object), * and there is no revision filtering parameters. @@ -140,8 +138,14 @@ static int builtin_diff_index(struct rev_info *revs, revs->max_count != -1 || revs->min_age != -1 || revs->max_age != -1) usage(builtin_diff_usage); - if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) { - perror("read_cache_preload"); + if (!cached) { + setup_work_tree(); + if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) { + perror("read_cache_preload"); + return -1; + } + } else if (read_cache() < 0) { + perror("read_cache"); return -1; } return run_diff_index(revs, cached); @@ -418,19 +422,3 @@ int cmd_diff(int argc, const char **argv, const char *prefix) refresh_index_quietly(); return result; } - -void setup_diff_pager(struct diff_options *opt) -{ - /* - * If the user asked for our exit code, then either they want --quiet - * or --exit-code. We should definitely not bother with a pager in the - * former case, as we will generate no output. Since we still properly - * report our exit code even when a pager is run, we _could_ run a - * pager with --exit-code. But since we have not done so historically, - * and because it is easy to find people oneline advising "git diff - * --exit-code" in hooks and other scripts, we do not do so. - */ - if (!DIFF_OPT_TST(opt, EXIT_WITH_STATUS) && - check_pager_config("diff") != 0) - setup_pager(); -} diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index e6443986b8..940ae35dc2 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -1,895 +1,12 @@ #include "builtin.h" -#include "refs.h" #include "pkt-line.h" -#include "commit.h" -#include "tag.h" -#include "exec_cmd.h" -#include "pack.h" -#include "sideband.h" #include "fetch-pack.h" -#include "remote.h" -#include "run-command.h" -#include "transport.h" -#include "version.h" - -static int transfer_unpack_limit = -1; -static int fetch_unpack_limit = -1; -static int unpack_limit = 100; -static int prefer_ofs_delta = 1; -static int no_done; -static int fetch_fsck_objects = -1; -static int transfer_fsck_objects = -1; -static int agent_supported; -static struct fetch_pack_args args = { - /* .uploadpack = */ "git-upload-pack", -}; static const char fetch_pack_usage[] = "git fetch-pack [--all] [--stdin] [--quiet|-q] [--keep|-k] [--thin] " "[--include-tag] [--upload-pack=] [--depth=] " "[--no-progress] [-v] [:] [...]"; -#define COMPLETE (1U << 0) -#define COMMON (1U << 1) -#define COMMON_REF (1U << 2) -#define SEEN (1U << 3) -#define POPPED (1U << 4) - -static int marked; - -/* - * After sending this many "have"s if we do not get any new ACK , we - * give up traversing our history. - */ -#define MAX_IN_VAIN 256 - -static struct commit_list *rev_list; -static int non_common_revs, multi_ack, use_sideband; - -static void rev_list_push(struct commit *commit, int mark) -{ - if (!(commit->object.flags & mark)) { - commit->object.flags |= mark; - - if (!(commit->object.parsed)) - if (parse_commit(commit)) - return; - - commit_list_insert_by_date(commit, &rev_list); - - if (!(commit->object.flags & COMMON)) - non_common_revs++; - } -} - -static int rev_list_insert_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) -{ - struct object *o = deref_tag(parse_object(sha1), refname, 0); - - if (o && o->type == OBJ_COMMIT) - rev_list_push((struct commit *)o, SEEN); - - return 0; -} - -static int clear_marks(const char *refname, const unsigned char *sha1, int flag, void *cb_data) -{ - struct object *o = deref_tag(parse_object(sha1), refname, 0); - - if (o && o->type == OBJ_COMMIT) - clear_commit_marks((struct commit *)o, - COMMON | COMMON_REF | SEEN | POPPED); - return 0; -} - -/* - This function marks a rev and its ancestors as common. - In some cases, it is desirable to mark only the ancestors (for example - when only the server does not yet know that they are common). -*/ - -static void mark_common(struct commit *commit, - int ancestors_only, int dont_parse) -{ - if (commit != NULL && !(commit->object.flags & COMMON)) { - struct object *o = (struct object *)commit; - - if (!ancestors_only) - o->flags |= COMMON; - - if (!(o->flags & SEEN)) - rev_list_push(commit, SEEN); - else { - struct commit_list *parents; - - if (!ancestors_only && !(o->flags & POPPED)) - non_common_revs--; - if (!o->parsed && !dont_parse) - if (parse_commit(commit)) - return; - - for (parents = commit->parents; - parents; - parents = parents->next) - mark_common(parents->item, 0, dont_parse); - } - } -} - -/* - Get the next rev to send, ignoring the common. -*/ - -static const unsigned char *get_rev(void) -{ - struct commit *commit = NULL; - - while (commit == NULL) { - unsigned int mark; - struct commit_list *parents; - - if (rev_list == NULL || non_common_revs == 0) - return NULL; - - commit = rev_list->item; - if (!commit->object.parsed) - parse_commit(commit); - parents = commit->parents; - - commit->object.flags |= POPPED; - if (!(commit->object.flags & COMMON)) - non_common_revs--; - - if (commit->object.flags & COMMON) { - /* do not send "have", and ignore ancestors */ - commit = NULL; - mark = COMMON | SEEN; - } else if (commit->object.flags & COMMON_REF) - /* send "have", and ignore ancestors */ - mark = COMMON | SEEN; - else - /* send "have", also for its ancestors */ - mark = SEEN; - - while (parents) { - if (!(parents->item->object.flags & SEEN)) - rev_list_push(parents->item, mark); - if (mark & COMMON) - mark_common(parents->item, 1, 0); - parents = parents->next; - } - - rev_list = rev_list->next; - } - - return commit->object.sha1; -} - -enum ack_type { - NAK = 0, - ACK, - ACK_continue, - ACK_common, - ACK_ready -}; - -static void consume_shallow_list(int fd) -{ - if (args.stateless_rpc && args.depth > 0) { - /* If we sent a depth we will get back "duplicate" - * shallow and unshallow commands every time there - * is a block of have lines exchanged. - */ - char line[1000]; - while (packet_read_line(fd, line, sizeof(line))) { - if (!prefixcmp(line, "shallow ")) - continue; - if (!prefixcmp(line, "unshallow ")) - continue; - die("git fetch-pack: expected shallow list"); - } - } -} - -struct write_shallow_data { - struct strbuf *out; - int use_pack_protocol; - int count; -}; - -static int write_one_shallow(const struct commit_graft *graft, void *cb_data) -{ - struct write_shallow_data *data = cb_data; - const char *hex = sha1_to_hex(graft->sha1); - data->count++; - if (data->use_pack_protocol) - packet_buf_write(data->out, "shallow %s", hex); - else { - strbuf_addstr(data->out, hex); - strbuf_addch(data->out, '\n'); - } - return 0; -} - -static int write_shallow_commits(struct strbuf *out, int use_pack_protocol) -{ - struct write_shallow_data data; - data.out = out; - data.use_pack_protocol = use_pack_protocol; - data.count = 0; - for_each_commit_graft(write_one_shallow, &data); - return data.count; -} - -static enum ack_type get_ack(int fd, unsigned char *result_sha1) -{ - static char line[1000]; - int len = packet_read_line(fd, line, sizeof(line)); - - if (!len) - die("git fetch-pack: expected ACK/NAK, got EOF"); - if (line[len-1] == '\n') - line[--len] = 0; - if (!strcmp(line, "NAK")) - return NAK; - if (!prefixcmp(line, "ACK ")) { - if (!get_sha1_hex(line+4, result_sha1)) { - if (strstr(line+45, "continue")) - return ACK_continue; - if (strstr(line+45, "common")) - return ACK_common; - if (strstr(line+45, "ready")) - return ACK_ready; - return ACK; - } - } - die("git fetch_pack: expected ACK/NAK, got '%s'", line); -} - -static void send_request(int fd, struct strbuf *buf) -{ - if (args.stateless_rpc) { - send_sideband(fd, -1, buf->buf, buf->len, LARGE_PACKET_MAX); - packet_flush(fd); - } else - safe_write(fd, buf->buf, buf->len); -} - -static void insert_one_alternate_ref(const struct ref *ref, void *unused) -{ - rev_list_insert_ref(NULL, ref->old_sha1, 0, NULL); -} - -#define INITIAL_FLUSH 16 -#define PIPESAFE_FLUSH 32 -#define LARGE_FLUSH 1024 - -static int next_flush(int count) -{ - int flush_limit = args.stateless_rpc ? LARGE_FLUSH : PIPESAFE_FLUSH; - - if (count < flush_limit) - count <<= 1; - else - count += flush_limit; - return count; -} - -static int find_common(int fd[2], unsigned char *result_sha1, - struct ref *refs) -{ - int fetching; - int count = 0, flushes = 0, flush_at = INITIAL_FLUSH, retval; - const unsigned char *sha1; - unsigned in_vain = 0; - int got_continue = 0; - int got_ready = 0; - struct strbuf req_buf = STRBUF_INIT; - size_t state_len = 0; - - if (args.stateless_rpc && multi_ack == 1) - die("--stateless-rpc requires multi_ack_detailed"); - if (marked) - for_each_ref(clear_marks, NULL); - marked = 1; - - for_each_ref(rev_list_insert_ref, NULL); - for_each_alternate_ref(insert_one_alternate_ref, NULL); - - fetching = 0; - for ( ; refs ; refs = refs->next) { - unsigned char *remote = refs->old_sha1; - const char *remote_hex; - struct object *o; - - /* - * If that object is complete (i.e. it is an ancestor of a - * local ref), we tell them we have it but do not have to - * tell them about its ancestors, which they already know - * about. - * - * We use lookup_object here because we are only - * interested in the case we *know* the object is - * reachable and we have already scanned it. - */ - if (((o = lookup_object(remote)) != NULL) && - (o->flags & COMPLETE)) { - continue; - } - - remote_hex = sha1_to_hex(remote); - if (!fetching) { - struct strbuf c = STRBUF_INIT; - if (multi_ack == 2) strbuf_addstr(&c, " multi_ack_detailed"); - if (multi_ack == 1) strbuf_addstr(&c, " multi_ack"); - if (no_done) strbuf_addstr(&c, " no-done"); - if (use_sideband == 2) strbuf_addstr(&c, " side-band-64k"); - if (use_sideband == 1) strbuf_addstr(&c, " side-band"); - if (args.use_thin_pack) strbuf_addstr(&c, " thin-pack"); - if (args.no_progress) strbuf_addstr(&c, " no-progress"); - if (args.include_tag) strbuf_addstr(&c, " include-tag"); - if (prefer_ofs_delta) strbuf_addstr(&c, " ofs-delta"); - if (agent_supported) strbuf_addf(&c, " agent=%s", - git_user_agent_sanitized()); - packet_buf_write(&req_buf, "want %s%s\n", remote_hex, c.buf); - strbuf_release(&c); - } else - packet_buf_write(&req_buf, "want %s\n", remote_hex); - fetching++; - } - - if (!fetching) { - strbuf_release(&req_buf); - packet_flush(fd[1]); - return 1; - } - - if (is_repository_shallow()) - write_shallow_commits(&req_buf, 1); - if (args.depth > 0) - packet_buf_write(&req_buf, "deepen %d", args.depth); - packet_buf_flush(&req_buf); - state_len = req_buf.len; - - if (args.depth > 0) { - char line[1024]; - unsigned char sha1[20]; - - send_request(fd[1], &req_buf); - while (packet_read_line(fd[0], line, sizeof(line))) { - if (!prefixcmp(line, "shallow ")) { - if (get_sha1_hex(line + 8, sha1)) - die("invalid shallow line: %s", line); - register_shallow(sha1); - continue; - } - if (!prefixcmp(line, "unshallow ")) { - if (get_sha1_hex(line + 10, sha1)) - die("invalid unshallow line: %s", line); - if (!lookup_object(sha1)) - die("object not found: %s", line); - /* make sure that it is parsed as shallow */ - if (!parse_object(sha1)) - die("error in object: %s", line); - if (unregister_shallow(sha1)) - die("no shallow found: %s", line); - continue; - } - die("expected shallow/unshallow, got %s", line); - } - } else if (!args.stateless_rpc) - send_request(fd[1], &req_buf); - - if (!args.stateless_rpc) { - /* If we aren't using the stateless-rpc interface - * we don't need to retain the headers. - */ - strbuf_setlen(&req_buf, 0); - state_len = 0; - } - - flushes = 0; - retval = -1; - while ((sha1 = get_rev())) { - packet_buf_write(&req_buf, "have %s\n", sha1_to_hex(sha1)); - if (args.verbose) - fprintf(stderr, "have %s\n", sha1_to_hex(sha1)); - in_vain++; - if (flush_at <= ++count) { - int ack; - - packet_buf_flush(&req_buf); - send_request(fd[1], &req_buf); - strbuf_setlen(&req_buf, state_len); - flushes++; - flush_at = next_flush(count); - - /* - * We keep one window "ahead" of the other side, and - * will wait for an ACK only on the next one - */ - if (!args.stateless_rpc && count == INITIAL_FLUSH) - continue; - - consume_shallow_list(fd[0]); - do { - ack = get_ack(fd[0], result_sha1); - if (args.verbose && ack) - fprintf(stderr, "got ack %d %s\n", ack, - sha1_to_hex(result_sha1)); - switch (ack) { - case ACK: - flushes = 0; - multi_ack = 0; - retval = 0; - goto done; - case ACK_common: - case ACK_ready: - case ACK_continue: { - struct commit *commit = - lookup_commit(result_sha1); - if (!commit) - die("invalid commit %s", sha1_to_hex(result_sha1)); - if (args.stateless_rpc - && ack == ACK_common - && !(commit->object.flags & COMMON)) { - /* We need to replay the have for this object - * on the next RPC request so the peer knows - * it is in common with us. - */ - const char *hex = sha1_to_hex(result_sha1); - packet_buf_write(&req_buf, "have %s\n", hex); - state_len = req_buf.len; - } - mark_common(commit, 0, 1); - retval = 0; - in_vain = 0; - got_continue = 1; - if (ack == ACK_ready) { - rev_list = NULL; - got_ready = 1; - } - break; - } - } - } while (ack); - flushes--; - if (got_continue && MAX_IN_VAIN < in_vain) { - if (args.verbose) - fprintf(stderr, "giving up\n"); - break; /* give up */ - } - } - } -done: - if (!got_ready || !no_done) { - packet_buf_write(&req_buf, "done\n"); - send_request(fd[1], &req_buf); - } - if (args.verbose) - fprintf(stderr, "done\n"); - if (retval != 0) { - multi_ack = 0; - flushes++; - } - strbuf_release(&req_buf); - - consume_shallow_list(fd[0]); - while (flushes || multi_ack) { - int ack = get_ack(fd[0], result_sha1); - if (ack) { - if (args.verbose) - fprintf(stderr, "got ack (%d) %s\n", ack, - sha1_to_hex(result_sha1)); - if (ack == ACK) - return 0; - multi_ack = 1; - continue; - } - flushes--; - } - /* it is no error to fetch into a completely empty repo */ - return count ? retval : 0; -} - -static struct commit_list *complete; - -static int mark_complete(const char *refname, const unsigned char *sha1, int flag, void *cb_data) -{ - struct object *o = parse_object(sha1); - - while (o && o->type == OBJ_TAG) { - struct tag *t = (struct tag *) o; - if (!t->tagged) - break; /* broken repository */ - o->flags |= COMPLETE; - o = parse_object(t->tagged->sha1); - } - if (o && o->type == OBJ_COMMIT) { - struct commit *commit = (struct commit *)o; - if (!(commit->object.flags & COMPLETE)) { - commit->object.flags |= COMPLETE; - commit_list_insert_by_date(commit, &complete); - } - } - return 0; -} - -static void mark_recent_complete_commits(unsigned long cutoff) -{ - while (complete && cutoff <= complete->item->date) { - if (args.verbose) - fprintf(stderr, "Marking %s as complete\n", - sha1_to_hex(complete->item->object.sha1)); - pop_most_recent_commit(&complete, COMPLETE); - } -} - -static int non_matching_ref(struct string_list_item *item, void *unused) -{ - if (item->util) { - item->util = NULL; - return 0; - } - else - return 1; -} - -static void filter_refs(struct ref **refs, struct string_list *sought) -{ - struct ref *newlist = NULL; - struct ref **newtail = &newlist; - struct ref *ref, *next; - int sought_pos; - - sought_pos = 0; - for (ref = *refs; ref; ref = next) { - int keep = 0; - next = ref->next; - if (!memcmp(ref->name, "refs/", 5) && - check_refname_format(ref->name + 5, 0)) - ; /* trash */ - else { - while (sought_pos < sought->nr) { - int cmp = strcmp(ref->name, sought->items[sought_pos].string); - if (cmp < 0) - break; /* definitely do not have it */ - else if (cmp == 0) { - keep = 1; /* definitely have it */ - sought->items[sought_pos++].util = "matched"; - break; - } - else - sought_pos++; /* might have it; keep looking */ - } - } - - if (! keep && args.fetch_all && - (!args.depth || prefixcmp(ref->name, "refs/tags/"))) - keep = 1; - - if (keep) { - *newtail = ref; - ref->next = NULL; - newtail = &ref->next; - } else { - free(ref); - } - } - - filter_string_list(sought, 0, non_matching_ref, NULL); - *refs = newlist; -} - -static void mark_alternate_complete(const struct ref *ref, void *unused) -{ - mark_complete(NULL, ref->old_sha1, 0, NULL); -} - -static int everything_local(struct ref **refs, struct string_list *sought) -{ - struct ref *ref; - int retval; - unsigned long cutoff = 0; - - save_commit_buffer = 0; - - for (ref = *refs; ref; ref = ref->next) { - struct object *o; - - o = parse_object(ref->old_sha1); - if (!o) - continue; - - /* We already have it -- which may mean that we were - * in sync with the other side at some time after - * that (it is OK if we guess wrong here). - */ - if (o->type == OBJ_COMMIT) { - struct commit *commit = (struct commit *)o; - if (!cutoff || cutoff < commit->date) - cutoff = commit->date; - } - } - - if (!args.depth) { - for_each_ref(mark_complete, NULL); - for_each_alternate_ref(mark_alternate_complete, NULL); - if (cutoff) - mark_recent_complete_commits(cutoff); - } - - /* - * Mark all complete remote refs as common refs. - * Don't mark them common yet; the server has to be told so first. - */ - for (ref = *refs; ref; ref = ref->next) { - struct object *o = deref_tag(lookup_object(ref->old_sha1), - NULL, 0); - - if (!o || o->type != OBJ_COMMIT || !(o->flags & COMPLETE)) - continue; - - if (!(o->flags & SEEN)) { - rev_list_push((struct commit *)o, COMMON_REF | SEEN); - - mark_common((struct commit *)o, 1, 1); - } - } - - filter_refs(refs, sought); - - for (retval = 1, ref = *refs; ref ; ref = ref->next) { - const unsigned char *remote = ref->old_sha1; - unsigned char local[20]; - struct object *o; - - o = lookup_object(remote); - if (!o || !(o->flags & COMPLETE)) { - retval = 0; - if (!args.verbose) - continue; - fprintf(stderr, - "want %s (%s)\n", sha1_to_hex(remote), - ref->name); - continue; - } - - hashcpy(ref->new_sha1, local); - if (!args.verbose) - continue; - fprintf(stderr, - "already have %s (%s)\n", sha1_to_hex(remote), - ref->name); - } - return retval; -} - -static int sideband_demux(int in, int out, void *data) -{ - int *xd = data; - - int ret = recv_sideband("fetch-pack", xd[0], out); - close(out); - return ret; -} - -static int get_pack(int xd[2], char **pack_lockfile) -{ - struct async demux; - const char *argv[20]; - char keep_arg[256]; - char hdr_arg[256]; - const char **av; - int do_keep = args.keep_pack; - struct child_process cmd; - - memset(&demux, 0, sizeof(demux)); - if (use_sideband) { - /* xd[] is talking with upload-pack; subprocess reads from - * xd[0], spits out band#2 to stderr, and feeds us band#1 - * through demux->out. - */ - demux.proc = sideband_demux; - demux.data = xd; - demux.out = -1; - if (start_async(&demux)) - die("fetch-pack: unable to fork off sideband" - " demultiplexer"); - } - else - demux.out = xd[0]; - - memset(&cmd, 0, sizeof(cmd)); - cmd.argv = argv; - av = argv; - *hdr_arg = 0; - if (!args.keep_pack && unpack_limit) { - struct pack_header header; - - if (read_pack_header(demux.out, &header)) - die("protocol error: bad pack header"); - snprintf(hdr_arg, sizeof(hdr_arg), - "--pack_header=%"PRIu32",%"PRIu32, - ntohl(header.hdr_version), ntohl(header.hdr_entries)); - if (ntohl(header.hdr_entries) < unpack_limit) - do_keep = 0; - else - do_keep = 1; - } - - if (do_keep) { - if (pack_lockfile) - cmd.out = -1; - *av++ = "index-pack"; - *av++ = "--stdin"; - if (!args.quiet && !args.no_progress) - *av++ = "-v"; - if (args.use_thin_pack) - *av++ = "--fix-thin"; - if (args.lock_pack || unpack_limit) { - int s = sprintf(keep_arg, - "--keep=fetch-pack %"PRIuMAX " on ", (uintmax_t) getpid()); - if (gethostname(keep_arg + s, sizeof(keep_arg) - s)) - strcpy(keep_arg + s, "localhost"); - *av++ = keep_arg; - } - } - else { - *av++ = "unpack-objects"; - if (args.quiet || args.no_progress) - *av++ = "-q"; - } - if (*hdr_arg) - *av++ = hdr_arg; - if (fetch_fsck_objects >= 0 - ? fetch_fsck_objects - : transfer_fsck_objects >= 0 - ? transfer_fsck_objects - : 0) - *av++ = "--strict"; - *av++ = NULL; - - cmd.in = demux.out; - cmd.git_cmd = 1; - if (start_command(&cmd)) - die("fetch-pack: unable to fork off %s", argv[0]); - if (do_keep && pack_lockfile) { - *pack_lockfile = index_pack_lockfile(cmd.out); - close(cmd.out); - } - - if (finish_command(&cmd)) - die("%s failed", argv[0]); - if (use_sideband && finish_async(&demux)) - die("error in sideband demultiplexer"); - return 0; -} - -static struct ref *do_fetch_pack(int fd[2], - const struct ref *orig_ref, - struct string_list *sought, - char **pack_lockfile) -{ - struct ref *ref = copy_ref_list(orig_ref); - unsigned char sha1[20]; - const char *agent_feature; - int agent_len; - - sort_ref_list(&ref, ref_compare_name); - - if (is_repository_shallow() && !server_supports("shallow")) - die("Server does not support shallow clients"); - if (server_supports("multi_ack_detailed")) { - if (args.verbose) - fprintf(stderr, "Server supports multi_ack_detailed\n"); - multi_ack = 2; - if (server_supports("no-done")) { - if (args.verbose) - fprintf(stderr, "Server supports no-done\n"); - if (args.stateless_rpc) - no_done = 1; - } - } - else if (server_supports("multi_ack")) { - if (args.verbose) - fprintf(stderr, "Server supports multi_ack\n"); - multi_ack = 1; - } - if (server_supports("side-band-64k")) { - if (args.verbose) - fprintf(stderr, "Server supports side-band-64k\n"); - use_sideband = 2; - } - else if (server_supports("side-band")) { - if (args.verbose) - fprintf(stderr, "Server supports side-band\n"); - use_sideband = 1; - } - if (!server_supports("thin-pack")) - args.use_thin_pack = 0; - if (!server_supports("no-progress")) - args.no_progress = 0; - if (!server_supports("include-tag")) - args.include_tag = 0; - if (server_supports("ofs-delta")) { - if (args.verbose) - fprintf(stderr, "Server supports ofs-delta\n"); - } else - prefer_ofs_delta = 0; - - if ((agent_feature = server_feature_value("agent", &agent_len))) { - agent_supported = 1; - if (args.verbose && agent_len) - fprintf(stderr, "Server version is %.*s\n", - agent_len, agent_feature); - } - - if (everything_local(&ref, sought)) { - packet_flush(fd[1]); - goto all_done; - } - if (find_common(fd, sha1, ref) < 0) - if (!args.keep_pack) - /* When cloning, it is not unusual to have - * no common commit. - */ - warning("no common commits"); - - if (args.stateless_rpc) - packet_flush(fd[1]); - if (get_pack(fd, pack_lockfile)) - die("git fetch-pack: fetch failed."); - - all_done: - return ref; -} - -static int fetch_pack_config(const char *var, const char *value, void *cb) -{ - if (strcmp(var, "fetch.unpacklimit") == 0) { - fetch_unpack_limit = git_config_int(var, value); - return 0; - } - - if (strcmp(var, "transfer.unpacklimit") == 0) { - transfer_unpack_limit = git_config_int(var, value); - return 0; - } - - if (strcmp(var, "repack.usedeltabaseoffset") == 0) { - prefer_ofs_delta = git_config_bool(var, value); - return 0; - } - - if (!strcmp(var, "fetch.fsckobjects")) { - fetch_fsck_objects = git_config_bool(var, value); - return 0; - } - - if (!strcmp(var, "transfer.fsckobjects")) { - transfer_fsck_objects = git_config_bool(var, value); - return 0; - } - - return git_default_config(var, value, cb); -} - -static struct lock_file lock; - -static void fetch_pack_setup(void) -{ - static int did_setup; - if (did_setup) - return; - git_config(fetch_pack_config, NULL); - if (0 <= transfer_unpack_limit) - unpack_limit = transfer_unpack_limit; - else if (0 <= fetch_unpack_limit) - unpack_limit = fetch_unpack_limit; - did_setup = 1; -} - int cmd_fetch_pack(int argc, const char **argv, const char *prefix) { int i, ret; @@ -900,9 +17,13 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) char *pack_lockfile = NULL; char **pack_lockfile_ptr = NULL; struct child_process *conn; + struct fetch_pack_args args; packet_trace_identity("fetch-pack"); + memset(&args, 0, sizeof(args)); + args.uploadpack = "git-upload-pack"; + for (i = 1; i < argc && *argv[i] == '-'; i++) { const char *arg = argv[i]; @@ -1038,66 +159,3 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) return ret; } - -struct ref *fetch_pack(struct fetch_pack_args *my_args, - int fd[], struct child_process *conn, - const struct ref *ref, - const char *dest, - struct string_list *sought, - char **pack_lockfile) -{ - struct stat st; - struct ref *ref_cpy; - - fetch_pack_setup(); - if (&args != my_args) - memcpy(&args, my_args, sizeof(args)); - if (args.depth > 0) { - if (stat(git_path("shallow"), &st)) - st.st_mtime = 0; - } - - if (sought->nr) { - sort_string_list(sought); - string_list_remove_duplicates(sought, 0); - } - - if (!ref) { - packet_flush(fd[1]); - die("no matching remote head"); - } - ref_cpy = do_fetch_pack(fd, ref, sought, pack_lockfile); - - if (args.depth > 0) { - struct cache_time mtime; - struct strbuf sb = STRBUF_INIT; - char *shallow = git_path("shallow"); - int fd; - - mtime.sec = st.st_mtime; - mtime.nsec = ST_MTIME_NSEC(st); - if (stat(shallow, &st)) { - if (mtime.sec) - die("shallow file was removed during fetch"); - } else if (st.st_mtime != mtime.sec -#ifdef USE_NSEC - || ST_MTIME_NSEC(st) != mtime.nsec -#endif - ) - die("shallow file was changed during fetch"); - - fd = hold_lock_file_for_update(&lock, shallow, - LOCK_DIE_ON_ERROR); - if (!write_shallow_commits(&sb, 0) - || write_in_full(fd, sb.buf, sb.len) != sb.len) { - unlink_or_warn(shallow); - rollback_lock_file(&lock); - } else { - commit_lock_file(&lock); - } - strbuf_release(&sb); - } - - reprepare_packed_git(); - return ref_cpy; -} diff --git a/builtin/grep.c b/builtin/grep.c index 82530a61b4..0e1b6c860e 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -86,7 +86,7 @@ static pthread_cond_t cond_result; static int skip_first_line; static void add_work(struct grep_opt *opt, enum grep_source_type type, - const char *name, const void *id) + const char *name, const char *path, const void *id) { grep_lock(); @@ -94,7 +94,7 @@ static void add_work(struct grep_opt *opt, enum grep_source_type type, pthread_cond_wait(&cond_write, &grep_mutex); } - grep_source_init(&todo[todo_end].source, type, name, id); + grep_source_init(&todo[todo_end].source, type, name, path, id); if (opt->binary != GREP_BINARY_TEXT) grep_source_load_driver(&todo[todo_end].source); todo[todo_end].done = 0; @@ -261,103 +261,12 @@ static int wait_all(void) } #endif -static int parse_pattern_type_arg(const char *opt, const char *arg) +static int grep_cmd_config(const char *var, const char *value, void *cb) { - if (!strcmp(arg, "default")) - return GREP_PATTERN_TYPE_UNSPECIFIED; - else if (!strcmp(arg, "basic")) - return GREP_PATTERN_TYPE_BRE; - else if (!strcmp(arg, "extended")) - return GREP_PATTERN_TYPE_ERE; - else if (!strcmp(arg, "fixed")) - return GREP_PATTERN_TYPE_FIXED; - else if (!strcmp(arg, "perl")) - return GREP_PATTERN_TYPE_PCRE; - die("bad %s argument: %s", opt, arg); -} - -static void grep_pattern_type_options(const int pattern_type, struct grep_opt *opt) -{ - switch (pattern_type) { - case GREP_PATTERN_TYPE_UNSPECIFIED: - /* fall through */ - - case GREP_PATTERN_TYPE_BRE: - opt->fixed = 0; - opt->pcre = 0; - opt->regflags &= ~REG_EXTENDED; - break; - - case GREP_PATTERN_TYPE_ERE: - opt->fixed = 0; - opt->pcre = 0; - opt->regflags |= REG_EXTENDED; - break; - - case GREP_PATTERN_TYPE_FIXED: - opt->fixed = 1; - opt->pcre = 0; - opt->regflags &= ~REG_EXTENDED; - break; - - case GREP_PATTERN_TYPE_PCRE: - opt->fixed = 0; - opt->pcre = 1; - opt->regflags &= ~REG_EXTENDED; - break; - } -} - -static int grep_config(const char *var, const char *value, void *cb) -{ - struct grep_opt *opt = cb; - char *color = NULL; - - if (userdiff_config(var, value) < 0) - return -1; - - if (!strcmp(var, "grep.extendedregexp")) { - if (git_config_bool(var, value)) - opt->extended_regexp_option = 1; - else - opt->extended_regexp_option = 0; - return 0; - } - - if (!strcmp(var, "grep.patterntype")) { - opt->pattern_type_option = parse_pattern_type_arg(var, value); - return 0; - } - - if (!strcmp(var, "grep.linenumber")) { - opt->linenum = git_config_bool(var, value); - return 0; - } - - if (!strcmp(var, "color.grep")) - opt->color = git_config_colorbool(var, value); - else if (!strcmp(var, "color.grep.context")) - color = opt->color_context; - else if (!strcmp(var, "color.grep.filename")) - color = opt->color_filename; - else if (!strcmp(var, "color.grep.function")) - color = opt->color_function; - else if (!strcmp(var, "color.grep.linenumber")) - color = opt->color_lineno; - else if (!strcmp(var, "color.grep.match")) - color = opt->color_match; - else if (!strcmp(var, "color.grep.selected")) - color = opt->color_selected; - else if (!strcmp(var, "color.grep.separator")) - color = opt->color_sep; - else - return git_color_default_config(var, value, cb); - if (color) { - if (!value) - return config_error_nonbool(var); - color_parse(value, var, color); - } - return 0; + int st = grep_config(var, value, cb); + if (git_color_default_config(var, value, cb) < 0) + st = -1; + return st; } static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size) @@ -371,7 +280,8 @@ static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type } static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, - const char *filename, int tree_name_len) + const char *filename, int tree_name_len, + const char *path) { struct strbuf pathbuf = STRBUF_INIT; @@ -385,7 +295,7 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, #ifndef NO_PTHREADS if (use_threads) { - add_work(opt, GREP_SOURCE_SHA1, pathbuf.buf, sha1); + add_work(opt, GREP_SOURCE_SHA1, pathbuf.buf, path, sha1); strbuf_release(&pathbuf); return 0; } else @@ -394,7 +304,7 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, struct grep_source gs; int hit; - grep_source_init(&gs, GREP_SOURCE_SHA1, pathbuf.buf, sha1); + grep_source_init(&gs, GREP_SOURCE_SHA1, pathbuf.buf, path, sha1); strbuf_release(&pathbuf); hit = grep_source(opt, &gs); @@ -414,7 +324,7 @@ static int grep_file(struct grep_opt *opt, const char *filename) #ifndef NO_PTHREADS if (use_threads) { - add_work(opt, GREP_SOURCE_FILE, buf.buf, filename); + add_work(opt, GREP_SOURCE_FILE, buf.buf, filename, filename); strbuf_release(&buf); return 0; } else @@ -423,7 +333,7 @@ static int grep_file(struct grep_opt *opt, const char *filename) struct grep_source gs; int hit; - grep_source_init(&gs, GREP_SOURCE_FILE, buf.buf, filename); + grep_source_init(&gs, GREP_SOURCE_FILE, buf.buf, filename, filename); strbuf_release(&buf); hit = grep_source(opt, &gs); @@ -479,7 +389,7 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int if (cached || (ce->ce_flags & CE_VALID) || ce_skip_worktree(ce)) { if (ce_stage(ce)) continue; - hit |= grep_sha1(opt, ce->sha1, ce->name, 0); + hit |= grep_sha1(opt, ce->sha1, ce->name, 0, ce->name); } else hit |= grep_file(opt, ce->name); @@ -497,7 +407,8 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int } static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, - struct tree_desc *tree, struct strbuf *base, int tn_len) + struct tree_desc *tree, struct strbuf *base, int tn_len, + int check_attr) { int hit = 0; enum interesting match = entry_not_interesting; @@ -518,7 +429,8 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, strbuf_add(base, entry.path, te_len); if (S_ISREG(entry.mode)) { - hit |= grep_sha1(opt, entry.sha1, base->buf, tn_len); + hit |= grep_sha1(opt, entry.sha1, base->buf, tn_len, + check_attr ? base->buf + tn_len : NULL); } else if (S_ISDIR(entry.mode)) { enum object_type type; @@ -533,7 +445,8 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, strbuf_addch(base, '/'); init_tree_desc(&sub, data, size); - hit |= grep_tree(opt, pathspec, &sub, base, tn_len); + hit |= grep_tree(opt, pathspec, &sub, base, tn_len, + check_attr); free(data); } strbuf_setlen(base, old_baselen); @@ -548,7 +461,7 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, struct object *obj, const char *name) { if (obj->type == OBJ_BLOB) - return grep_sha1(opt, obj->sha1, name, 0); + return grep_sha1(opt, obj->sha1, name, 0, NULL); if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) { struct tree_desc tree; void *data; @@ -571,7 +484,8 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, strbuf_addch(&base, ':'); } init_tree_desc(&tree, data, size); - hit = grep_tree(opt, pathspec, &tree, &base, base.len); + hit = grep_tree(opt, pathspec, &tree, &base, base.len, + obj->type == OBJ_COMMIT); strbuf_release(&base); free(data); return hit; @@ -839,27 +753,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(grep_usage, options); - memset(&opt, 0, sizeof(opt)); - opt.prefix = prefix; - opt.prefix_length = (prefix && *prefix) ? strlen(prefix) : 0; - opt.relative = 1; - opt.pathname = 1; - opt.pattern_tail = &opt.pattern_list; - opt.header_tail = &opt.header_list; - opt.regflags = REG_NEWLINE; - opt.max_depth = -1; - opt.pattern_type_option = GREP_PATTERN_TYPE_UNSPECIFIED; - opt.extended_regexp_option = 0; - - strcpy(opt.color_context, ""); - strcpy(opt.color_filename, ""); - strcpy(opt.color_function, ""); - strcpy(opt.color_lineno, ""); - strcpy(opt.color_match, GIT_COLOR_BOLD_RED); - strcpy(opt.color_selected, ""); - strcpy(opt.color_sep, GIT_COLOR_CYAN); - opt.color = -1; - git_config(grep_config, &opt); + init_grep_defaults(); + git_config(grep_cmd_config, NULL); + grep_init(&opt, prefix); /* * If there is no -- then the paths must exist in the working @@ -875,13 +771,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_STOP_AT_NON_OPTION | PARSE_OPT_NO_INTERNAL_HELP); - - if (pattern_type_arg != GREP_PATTERN_TYPE_UNSPECIFIED) - grep_pattern_type_options(pattern_type_arg, &opt); - else if (opt.pattern_type_option != GREP_PATTERN_TYPE_UNSPECIFIED) - grep_pattern_type_options(opt.pattern_type_option, &opt); - else if (opt.extended_regexp_option) - grep_pattern_type_options(GREP_PATTERN_TYPE_ERE, &opt); + grep_commit_pattern_type(pattern_type_arg, &opt); if (use_index && !startup_info->have_repository) /* die the same way as if we did it at the beginning */ diff --git a/builtin/log.c b/builtin/log.c index 09cf43e6d4..e7b7db1cac 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -351,7 +351,8 @@ static int git_log_config(const char *var, const char *value, void *cb) } if (!prefixcmp(var, "color.decorate.")) return parse_decorate_color_config(var, 15, value); - + if (grep_config(var, value, cb) < 0) + return -1; return git_diff_ui_config(var, value, cb); } @@ -360,6 +361,7 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix) struct rev_info rev; struct setup_revision_opt opt; + init_grep_defaults(); git_config(git_log_config, NULL); init_revisions(&rev, prefix); @@ -450,6 +452,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) struct pathspec match_all; int i, count, ret = 0; + init_grep_defaults(); git_config(git_log_config, NULL); init_pathspec(&match_all, NULL); @@ -530,6 +533,7 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix) struct rev_info rev; struct setup_revision_opt opt; + init_grep_defaults(); git_config(git_log_config, NULL); init_revisions(&rev, prefix); @@ -552,6 +556,7 @@ int cmd_log(int argc, const char **argv, const char *prefix) struct rev_info rev; struct setup_revision_opt opt; + init_grep_defaults(); git_config(git_log_config, NULL); init_revisions(&rev, prefix); @@ -1121,6 +1126,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) extra_hdr.strdup_strings = 1; extra_to.strdup_strings = 1; extra_cc.strdup_strings = 1; + init_grep_defaults(); git_config(git_format_config, NULL); init_revisions(&rev, prefix); rev.commit_format = CMIT_FMT_EMAIL; diff --git a/builtin/mailinfo.c b/builtin/mailinfo.c index da231400b3..24a772d8e1 100644 --- a/builtin/mailinfo.c +++ b/builtin/mailinfo.c @@ -483,7 +483,8 @@ static void convert_to_utf8(struct strbuf *line, const char *charset) if (!charset || !*charset) return; - if (!strcasecmp(metainfo_charset, charset)) + + if (same_encoding(metainfo_charset, charset)) return; out = reencode_string(line->buf, metainfo_charset, charset); if (!out) diff --git a/builtin/merge.c b/builtin/merge.c index 0ec8f0d449..a96e8eac19 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -628,59 +628,6 @@ static void write_tree_trivial(unsigned char *sha1) die(_("git write-tree failed to write a tree")); } -static const char *merge_argument(struct commit *commit) -{ - if (commit) - return sha1_to_hex(commit->object.sha1); - else - return EMPTY_TREE_SHA1_HEX; -} - -int try_merge_command(const char *strategy, size_t xopts_nr, - const char **xopts, struct commit_list *common, - const char *head_arg, struct commit_list *remotes) -{ - const char **args; - int i = 0, x = 0, ret; - struct commit_list *j; - struct strbuf buf = STRBUF_INIT; - - args = xmalloc((4 + xopts_nr + commit_list_count(common) + - commit_list_count(remotes)) * sizeof(char *)); - strbuf_addf(&buf, "merge-%s", strategy); - args[i++] = buf.buf; - for (x = 0; x < xopts_nr; x++) { - char *s = xmalloc(strlen(xopts[x])+2+1); - strcpy(s, "--"); - strcpy(s+2, xopts[x]); - args[i++] = s; - } - for (j = common; j; j = j->next) - args[i++] = xstrdup(merge_argument(j->item)); - args[i++] = "--"; - args[i++] = head_arg; - for (j = remotes; j; j = j->next) - args[i++] = xstrdup(merge_argument(j->item)); - args[i] = NULL; - ret = run_command_v_opt(args, RUN_GIT_CMD); - strbuf_release(&buf); - i = 1; - for (x = 0; x < xopts_nr; x++) - free((void *)args[i++]); - for (j = common; j; j = j->next) - free((void *)args[i++]); - i += 2; - for (j = remotes; j; j = j->next) - free((void *)args[i++]); - free(args); - discard_cache(); - if (read_cache() < 0) - die(_("failed to read the cache")); - resolve_undo_clear(); - - return ret; -} - static int try_merge_strategy(const char *strategy, struct commit_list *common, struct commit_list *remoteheads, struct commit *head, const char *head_arg) @@ -762,56 +709,6 @@ static int count_unmerged_entries(void) return ret; } -int checkout_fast_forward(const unsigned char *head, const unsigned char *remote) -{ - struct tree *trees[MAX_UNPACK_TREES]; - struct unpack_trees_options opts; - struct tree_desc t[MAX_UNPACK_TREES]; - int i, fd, nr_trees = 0; - struct dir_struct dir; - struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); - - refresh_cache(REFRESH_QUIET); - - fd = hold_locked_index(lock_file, 1); - - memset(&trees, 0, sizeof(trees)); - memset(&opts, 0, sizeof(opts)); - memset(&t, 0, sizeof(t)); - if (overwrite_ignore) { - memset(&dir, 0, sizeof(dir)); - dir.flags |= DIR_SHOW_IGNORED; - setup_standard_excludes(&dir); - opts.dir = &dir; - } - - opts.head_idx = 1; - opts.src_index = &the_index; - opts.dst_index = &the_index; - opts.update = 1; - opts.verbose_update = 1; - opts.merge = 1; - opts.fn = twoway_merge; - setup_unpack_trees_porcelain(&opts, "merge"); - - trees[nr_trees] = parse_tree_indirect(head); - if (!trees[nr_trees++]) - return -1; - trees[nr_trees] = parse_tree_indirect(remote); - if (!trees[nr_trees++]) - return -1; - for (i = 0; i < nr_trees; i++) { - parse_tree(trees[i]); - init_tree_desc(t+i, trees[i]->buffer, trees[i]->size); - } - if (unpack_trees(nr_trees, t, &opts)) - return -1; - if (write_cache(fd, active_cache, active_nr) || - commit_locked_index(lock_file)) - die(_("unable to write new index file")); - return 0; -} - static void split_merge_strategies(const char *string, struct strategy **list, int *nr, int *alloc) { @@ -1424,7 +1321,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } if (checkout_fast_forward(head_commit->object.sha1, - commit->object.sha1)) { + commit->object.sha1, + overwrite_ignore)) { ret = 1; goto done; } diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 5e14064094..f069462cb0 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -2033,7 +2033,6 @@ static int add_ref_tag(const char *path, const unsigned char *sha1, int flag, vo if (!prefixcmp(path, "refs/tags/") && /* is a tag? */ !peel_ref(path, peeled) && /* peelable? */ - !is_null_sha1(peeled) && /* annotated tag? */ locate_object_entry(peeled)) /* object packed? */ add_object_entry(sha1, OBJ_TAG, NULL, 0); return 0; diff --git a/builtin/remote.c b/builtin/remote.c index a5a4b23231..937484d7c7 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -39,7 +39,7 @@ static const char * const builtin_remote_rm_usage[] = { }; static const char * const builtin_remote_sethead_usage[] = { - N_("git remote set-head (-a | -d | ])"), + N_("git remote set-head (-a | -d | )"), NULL }; diff --git a/builtin/replace.c b/builtin/replace.c index e3aaf70203..398ccd5eaa 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -46,24 +46,27 @@ typedef int (*each_replace_name_fn)(const char *name, const char *ref, static int for_each_replace_name(const char **argv, each_replace_name_fn fn) { - const char **p; + const char **p, *full_hex; char ref[PATH_MAX]; int had_error = 0; unsigned char sha1[20]; for (p = argv; *p; p++) { - if (snprintf(ref, sizeof(ref), "refs/replace/%s", *p) - >= sizeof(ref)) { - error("replace ref name too long: %.*s...", 50, *p); + if (get_sha1(*p, sha1)) { + error("Failed to resolve '%s' as a valid ref.", *p); had_error = 1; continue; } + full_hex = sha1_to_hex(sha1); + snprintf(ref, sizeof(ref), "refs/replace/%s", full_hex); + /* read_ref() may reuse the buffer */ + full_hex = ref + strlen("refs/replace/"); if (read_ref(ref, sha1)) { - error("replace ref '%s' not found.", *p); + error("replace ref '%s' not found.", full_hex); had_error = 1; continue; } - if (fn(*p, ref, sha1)) + if (fn(full_hex, ref, sha1)) had_error = 1; } return had_error; diff --git a/builtin/rev-list.c b/builtin/rev-list.c index ff5a38372d..67701be551 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -201,55 +201,6 @@ static void show_edge(struct commit *commit) printf("-%s\n", sha1_to_hex(commit->object.sha1)); } -static inline int log2i(int n) -{ - int log2 = 0; - - for (; n > 1; n >>= 1) - log2++; - - return log2; -} - -static inline int exp2i(int n) -{ - return 1 << n; -} - -/* - * Estimate the number of bisect steps left (after the current step) - * - * For any x between 0 included and 2^n excluded, the probability for - * n - 1 steps left looks like: - * - * P(2^n + x) == (2^n - x) / (2^n + x) - * - * and P(2^n + x) < 0.5 means 2^n < 3x - */ -int estimate_bisect_steps(int all) -{ - int n, x, e; - - if (all < 3) - return 0; - - n = log2i(all); - e = exp2i(n); - x = all - e; - - return (e < 3 * x) ? n : n - 1; -} - -void print_commit_list(struct commit_list *list, - const char *format_cur, - const char *format_last) -{ - for ( ; list; list = list->next) { - const char *format = list->next ? format_cur : format_last; - printf(format, sha1_to_hex(list->item->object.sha1)); - } -} - static void print_var_str(const char *var, const char *val) { printf("%s='%s'\n", var, val); diff --git a/builtin/rm.c b/builtin/rm.c index b384c4c3cf..dabfcf6890 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -9,6 +9,7 @@ #include "cache-tree.h" #include "tree-walk.h" #include "parse-options.h" +#include "submodule.h" static const char * const builtin_rm_usage[] = { N_("git rm [options] [--] ..."), @@ -17,9 +18,58 @@ static const char * const builtin_rm_usage[] = { static struct { int nr, alloc; - const char **name; + struct { + const char *name; + char is_submodule; + } *entry; } list; +static int get_ours_cache_pos(const char *path, int pos) +{ + int i = -pos - 1; + + while ((i < active_nr) && !strcmp(active_cache[i]->name, path)) { + if (ce_stage(active_cache[i]) == 2) + return i; + i++; + } + return -1; +} + +static int check_submodules_use_gitfiles(void) +{ + int i; + int errs = 0; + + for (i = 0; i < list.nr; i++) { + const char *name = list.entry[i].name; + int pos; + struct cache_entry *ce; + struct stat st; + + pos = cache_name_pos(name, strlen(name)); + if (pos < 0) { + pos = get_ours_cache_pos(name, pos); + if (pos < 0) + continue; + } + ce = active_cache[pos]; + + if (!S_ISGITLINK(ce->ce_mode) || + (lstat(ce->name, &st) < 0) || + is_empty_dir(name)) + continue; + + if (!submodule_uses_gitfile(name)) + errs = error(_("submodule '%s' (or one of its nested " + "submodules) uses a .git directory\n" + "(use 'rm -rf' if you really want to remove " + "it including all of its history)"), name); + } + + return errs; +} + static int check_local_mod(unsigned char *head, int index_only) { /* @@ -37,15 +87,26 @@ static int check_local_mod(unsigned char *head, int index_only) struct stat st; int pos; struct cache_entry *ce; - const char *name = list.name[i]; + const char *name = list.entry[i].name; unsigned char sha1[20]; unsigned mode; int local_changes = 0; int staged_changes = 0; pos = cache_name_pos(name, strlen(name)); - if (pos < 0) - continue; /* removing unmerged entry */ + if (pos < 0) { + /* + * Skip unmerged entries except for populated submodules + * that could lose history when removed. + */ + pos = get_ours_cache_pos(name, pos); + if (pos < 0) + continue; + + if (!S_ISGITLINK(active_cache[pos]->ce_mode) || + is_empty_dir(name)) + continue; + } ce = active_cache[pos]; if (lstat(ce->name, &st) < 0) { @@ -58,9 +119,10 @@ static int check_local_mod(unsigned char *head, int index_only) /* if a file was removed and it is now a * directory, that is the same as ENOENT as * far as git is concerned; we do not track - * directories. + * directories unless they are submodules. */ - continue; + if (!S_ISGITLINK(ce->ce_mode)) + continue; } /* @@ -80,8 +142,11 @@ static int check_local_mod(unsigned char *head, int index_only) /* * Is the index different from the file in the work tree? + * If it's a submodule, is its work tree modified? */ - if (ce_match_stat(ce, &st, 0)) + if (ce_match_stat(ce, &st, 0) || + (S_ISGITLINK(ce->ce_mode) && + !ok_to_remove_submodule(ce->name))) local_changes = 1; /* @@ -115,10 +180,18 @@ static int check_local_mod(unsigned char *head, int index_only) errs = error(_("'%s' has changes staged in the index\n" "(use --cached to keep the file, " "or -f to force removal)"), name); - if (local_changes) - errs = error(_("'%s' has local modifications\n" - "(use --cached to keep the file, " - "or -f to force removal)"), name); + if (local_changes) { + if (S_ISGITLINK(ce->ce_mode) && + !submodule_uses_gitfile(name)) { + errs = error(_("submodule '%s' (or one of its nested " + "submodules) uses a .git directory\n" + "(use 'rm -rf' if you really want to remove " + "it including all of its history)"), name); + } else + errs = error(_("'%s' has local modifications\n" + "(use --cached to keep the file, " + "or -f to force removal)"), name); + } } } return errs; @@ -161,6 +234,21 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (read_cache() < 0) die(_("index file corrupt")); + /* + * Drop trailing directory separators from directories so we'll find + * submodules in the index. + */ + for (i = 0; i < argc; i++) { + size_t pathlen = strlen(argv[i]); + if (pathlen && is_dir_sep(argv[i][pathlen - 1]) && + is_directory(argv[i])) { + do { + pathlen--; + } while (pathlen && is_dir_sep(argv[i][pathlen - 1])); + argv[i] = xmemdupz(argv[i], pathlen); + } + } + pathspec = get_pathspec(prefix, argv); refresh_index(&the_index, REFRESH_QUIET, pathspec, NULL, NULL); @@ -173,8 +261,9 @@ int cmd_rm(int argc, const char **argv, const char *prefix) struct cache_entry *ce = active_cache[i]; if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen)) continue; - ALLOC_GROW(list.name, list.nr + 1, list.alloc); - list.name[list.nr++] = ce->name; + ALLOC_GROW(list.entry, list.nr + 1, list.alloc); + list.entry[list.nr].name = ce->name; + list.entry[list.nr++].is_submodule = S_ISGITLINK(ce->ce_mode); } if (pathspec) { @@ -215,6 +304,9 @@ int cmd_rm(int argc, const char **argv, const char *prefix) hashclr(sha1); if (check_local_mod(sha1, index_only)) exit(1); + } else if (!index_only) { + if (check_submodules_use_gitfiles()) + exit(1); } /* @@ -222,7 +314,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) * the index unless all of them succeed. */ for (i = 0; i < list.nr; i++) { - const char *path = list.name[i]; + const char *path = list.entry[i].name; if (!quiet) printf("rm '%s'\n", path); @@ -244,7 +336,25 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (!index_only) { int removed = 0; for (i = 0; i < list.nr; i++) { - const char *path = list.name[i]; + const char *path = list.entry[i].name; + if (list.entry[i].is_submodule) { + if (is_empty_dir(path)) { + if (!rmdir(path)) { + removed = 1; + continue; + } + } else { + struct strbuf buf = STRBUF_INIT; + strbuf_addstr(&buf, path); + if (!remove_dir_recursively(&buf, 0)) { + removed = 1; + strbuf_release(&buf); + continue; + } + strbuf_release(&buf); + /* Fallthrough and let remove_path() fail. */ + } + } if (!remove_path(path)) { removed = 1; continue; diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 7d05064218..d34201372d 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -16,164 +16,6 @@ static const char send_pack_usage[] = static struct send_pack_args args; -static int feed_object(const unsigned char *sha1, int fd, int negative) -{ - char buf[42]; - - if (negative && !has_sha1_file(sha1)) - return 1; - - memcpy(buf + negative, sha1_to_hex(sha1), 40); - if (negative) - buf[0] = '^'; - buf[40 + negative] = '\n'; - return write_or_whine(fd, buf, 41 + negative, "send-pack: send refs"); -} - -/* - * Make a pack stream and spit it out into file descriptor fd - */ -static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *extra, struct send_pack_args *args) -{ - /* - * The child becomes pack-objects --revs; we feed - * the revision parameters to it via its stdin and - * let its stdout go back to the other end. - */ - const char *argv[] = { - "pack-objects", - "--all-progress-implied", - "--revs", - "--stdout", - NULL, - NULL, - NULL, - NULL, - NULL, - }; - struct child_process po; - int i; - - i = 4; - if (args->use_thin_pack) - argv[i++] = "--thin"; - if (args->use_ofs_delta) - argv[i++] = "--delta-base-offset"; - if (args->quiet || !args->progress) - argv[i++] = "-q"; - if (args->progress) - argv[i++] = "--progress"; - memset(&po, 0, sizeof(po)); - po.argv = argv; - po.in = -1; - po.out = args->stateless_rpc ? -1 : fd; - po.git_cmd = 1; - if (start_command(&po)) - die_errno("git pack-objects failed"); - - /* - * We feed the pack-objects we just spawned with revision - * parameters by writing to the pipe. - */ - for (i = 0; i < extra->nr; i++) - if (!feed_object(extra->array[i], po.in, 1)) - break; - - while (refs) { - if (!is_null_sha1(refs->old_sha1) && - !feed_object(refs->old_sha1, po.in, 1)) - break; - if (!is_null_sha1(refs->new_sha1) && - !feed_object(refs->new_sha1, po.in, 0)) - break; - refs = refs->next; - } - - close(po.in); - - if (args->stateless_rpc) { - char *buf = xmalloc(LARGE_PACKET_MAX); - while (1) { - ssize_t n = xread(po.out, buf, LARGE_PACKET_MAX); - if (n <= 0) - break; - send_sideband(fd, -1, buf, n, LARGE_PACKET_MAX); - } - free(buf); - close(po.out); - po.out = -1; - } - - if (finish_command(&po)) - return -1; - return 0; -} - -static int receive_status(int in, struct ref *refs) -{ - struct ref *hint; - char line[1000]; - int ret = 0; - int len = packet_read_line(in, line, sizeof(line)); - if (len < 10 || memcmp(line, "unpack ", 7)) - return error("did not receive remote status"); - if (memcmp(line, "unpack ok\n", 10)) { - char *p = line + strlen(line) - 1; - if (*p == '\n') - *p = '\0'; - error("unpack failed: %s", line + 7); - ret = -1; - } - hint = NULL; - while (1) { - char *refname; - char *msg; - len = packet_read_line(in, line, sizeof(line)); - if (!len) - break; - if (len < 3 || - (memcmp(line, "ok ", 3) && memcmp(line, "ng ", 3))) { - fprintf(stderr, "protocol error: %s\n", line); - ret = -1; - break; - } - - line[strlen(line)-1] = '\0'; - refname = line + 3; - msg = strchr(refname, ' '); - if (msg) - *msg++ = '\0'; - - /* first try searching at our hint, falling back to all refs */ - if (hint) - hint = find_ref_by_name(hint, refname); - if (!hint) - hint = find_ref_by_name(refs, refname); - if (!hint) { - warning("remote reported status on unknown ref: %s", - refname); - continue; - } - if (hint->status != REF_STATUS_EXPECTING_REPORT) { - warning("remote reported status on unexpected ref: %s", - refname); - continue; - } - - if (line[0] == 'o' && line[1] == 'k') - hint->status = REF_STATUS_OK; - else { - hint->status = REF_STATUS_REMOTE_REJECT; - ret = -1; - } - if (msg) - hint->remote_status = xstrdup(msg); - /* start our next search from the next ref */ - hint = hint->next; - } - return ret; -} - static void print_helper_status(struct ref *ref) { struct strbuf buf = STRBUF_INIT; @@ -227,181 +69,6 @@ static void print_helper_status(struct ref *ref) strbuf_release(&buf); } -static int sideband_demux(int in, int out, void *data) -{ - int *fd = data, ret; -#ifdef NO_PTHREADS - close(fd[1]); -#endif - ret = recv_sideband("send-pack", fd[0], out); - close(out); - return ret; -} - -int send_pack(struct send_pack_args *args, - int fd[], struct child_process *conn, - struct ref *remote_refs, - struct extra_have_objects *extra_have) -{ - int in = fd[0]; - int out = fd[1]; - struct strbuf req_buf = STRBUF_INIT; - struct ref *ref; - int new_refs; - int allow_deleting_refs = 0; - int status_report = 0; - int use_sideband = 0; - int quiet_supported = 0; - int agent_supported = 0; - unsigned cmds_sent = 0; - int ret; - struct async demux; - - /* Does the other end support the reporting? */ - if (server_supports("report-status")) - status_report = 1; - if (server_supports("delete-refs")) - allow_deleting_refs = 1; - if (server_supports("ofs-delta")) - args->use_ofs_delta = 1; - if (server_supports("side-band-64k")) - use_sideband = 1; - if (server_supports("quiet")) - quiet_supported = 1; - if (server_supports("agent")) - agent_supported = 1; - - if (!remote_refs) { - fprintf(stderr, "No refs in common and none specified; doing nothing.\n" - "Perhaps you should specify a branch such as 'master'.\n"); - return 0; - } - - /* - * Finally, tell the other end! - */ - new_refs = 0; - for (ref = remote_refs; ref; ref = ref->next) { - if (!ref->peer_ref && !args->send_mirror) - continue; - - /* Check for statuses set by set_ref_status_for_push() */ - switch (ref->status) { - case REF_STATUS_REJECT_NONFASTFORWARD: - case REF_STATUS_UPTODATE: - continue; - default: - ; /* do nothing */ - } - - if (ref->deletion && !allow_deleting_refs) { - ref->status = REF_STATUS_REJECT_NODELETE; - continue; - } - - if (!ref->deletion) - new_refs++; - - if (args->dry_run) { - ref->status = REF_STATUS_OK; - } else { - char *old_hex = sha1_to_hex(ref->old_sha1); - char *new_hex = sha1_to_hex(ref->new_sha1); - int quiet = quiet_supported && (args->quiet || !args->progress); - - if (!cmds_sent && (status_report || use_sideband || - quiet || agent_supported)) { - packet_buf_write(&req_buf, - "%s %s %s%c%s%s%s%s%s", - old_hex, new_hex, ref->name, 0, - status_report ? " report-status" : "", - use_sideband ? " side-band-64k" : "", - quiet ? " quiet" : "", - agent_supported ? " agent=" : "", - agent_supported ? git_user_agent_sanitized() : "" - ); - } - else - packet_buf_write(&req_buf, "%s %s %s", - old_hex, new_hex, ref->name); - ref->status = status_report ? - REF_STATUS_EXPECTING_REPORT : - REF_STATUS_OK; - cmds_sent++; - } - } - - if (args->stateless_rpc) { - if (!args->dry_run && cmds_sent) { - packet_buf_flush(&req_buf); - send_sideband(out, -1, req_buf.buf, req_buf.len, LARGE_PACKET_MAX); - } - } else { - safe_write(out, req_buf.buf, req_buf.len); - packet_flush(out); - } - strbuf_release(&req_buf); - - if (use_sideband && cmds_sent) { - memset(&demux, 0, sizeof(demux)); - demux.proc = sideband_demux; - demux.data = fd; - demux.out = -1; - if (start_async(&demux)) - die("send-pack: unable to fork off sideband demultiplexer"); - in = demux.out; - } - - if (new_refs && cmds_sent) { - if (pack_objects(out, remote_refs, extra_have, args) < 0) { - for (ref = remote_refs; ref; ref = ref->next) - ref->status = REF_STATUS_NONE; - if (args->stateless_rpc) - close(out); - if (git_connection_is_socket(conn)) - shutdown(fd[0], SHUT_WR); - if (use_sideband) - finish_async(&demux); - return -1; - } - } - if (args->stateless_rpc && cmds_sent) - packet_flush(out); - - if (status_report && cmds_sent) - ret = receive_status(in, remote_refs); - else - ret = 0; - if (args->stateless_rpc) - packet_flush(out); - - if (use_sideband && cmds_sent) { - if (finish_async(&demux)) { - error("error in sideband demultiplexer"); - ret = -1; - } - close(demux.out); - } - - if (ret < 0) - return ret; - - if (args->porcelain) - return 0; - - for (ref = remote_refs; ref; ref = ref->next) { - switch (ref->status) { - case REF_STATUS_NONE: - case REF_STATUS_UPTODATE: - case REF_STATUS_OK: - break; - default: - return -1; - } - } - return 0; -} - int cmd_send_pack(int argc, const char **argv, const char *prefix) { int i, nr_refspecs = 0; diff --git a/builtin/show-ref.c b/builtin/show-ref.c index 4eb016d6e5..8d9b76a02f 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -28,7 +28,6 @@ static void show_one(const char *refname, const unsigned char *sha1) static int show_ref(const char *refname, const unsigned char *sha1, int flag, void *cbdata) { - struct object *obj; const char *hex; unsigned char peeled[20]; @@ -79,25 +78,9 @@ static int show_ref(const char *refname, const unsigned char *sha1, int flag, vo if (!deref_tags) return 0; - if ((flag & REF_ISPACKED) && !peel_ref(refname, peeled)) { - if (!is_null_sha1(peeled)) { - hex = find_unique_abbrev(peeled, abbrev); - printf("%s %s^{}\n", hex, refname); - } - } - else { - obj = parse_object(sha1); - if (!obj) - die("git show-ref: bad ref %s (%s)", refname, - sha1_to_hex(sha1)); - if (obj->type == OBJ_TAG) { - obj = deref_tag(obj, refname, 0); - if (!obj) - die("git show-ref: bad tag at ref %s (%s)", refname, - sha1_to_hex(sha1)); - hex = find_unique_abbrev(obj->sha1, abbrev); - printf("%s %s^{}\n", hex, refname); - } + if (!peel_ref(refname, peeled)) { + hex = find_unique_abbrev(peeled, abbrev); + printf("%s %s^{}\n", hex, refname); } return 0; } diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c index 9e92828b3a..f481959421 100644 --- a/builtin/symbolic-ref.c +++ b/builtin/symbolic-ref.c @@ -5,12 +5,11 @@ static const char * const git_symbolic_ref_usage[] = { N_("git symbolic-ref [options] name [ref]"), + N_("git symbolic-ref -d [-q] name"), NULL }; -static int shorten; - -static void check_symref(const char *HEAD, int quiet) +static int check_symref(const char *HEAD, int quiet, int shorten, int print) { unsigned char sha1[20]; int flag; @@ -22,20 +21,24 @@ static void check_symref(const char *HEAD, int quiet) if (!quiet) die("ref %s is not a symbolic ref", HEAD); else - exit(1); + return 1; + } + if (print) { + if (shorten) + refname = shorten_unambiguous_ref(refname, 0); + puts(refname); } - if (shorten) - refname = shorten_unambiguous_ref(refname, 0); - puts(refname); + return 0; } int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) { - int quiet = 0; + int quiet = 0, delete = 0, shorten = 0, ret = 0; const char *msg = NULL; struct option options[] = { OPT__QUIET(&quiet, N_("suppress error message for non-symbolic (detached) refs")), + OPT_BOOL('d', "delete", &delete, N_("delete symbolic ref")), OPT_BOOL(0, "short", &shorten, N_("shorten ref output")), OPT_STRING('m', NULL, &msg, N_("reason"), N_("reason of the update")), OPT_END(), @@ -46,9 +49,19 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) git_symbolic_ref_usage, 0); if (msg &&!*msg) die("Refusing to perform update with empty message"); + + if (delete) { + if (argc != 1) + usage_with_options(git_symbolic_ref_usage, options); + ret = check_symref(argv[0], 1, 0, 0); + if (ret) + die("Cannot delete %s, not a symbolic ref", argv[0]); + return delete_ref(argv[0], NULL, REF_NODEREF); + } + switch (argc) { case 1: - check_symref(argv[0], quiet); + ret = check_symref(argv[0], quiet, shorten, 1); break; case 2: if (!strcmp(argv[0], "HEAD") && @@ -59,5 +72,5 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) default: usage_with_options(git_symbolic_ref_usage, options); } - return 0; + return ret; } diff --git a/builtin/update-index.c b/builtin/update-index.c index 74986bf163..ada1dff846 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -593,6 +593,7 @@ struct refresh_params { static int refresh(struct refresh_params *o, unsigned int flag) { setup_work_tree(); + read_cache_preload(NULL); *o->has_errors |= refresh_cache(o->flags | flag); return 0; } diff --git a/cache.h b/cache.h index a58df84bd3..18fdd18f36 100644 --- a/cache.h +++ b/cache.h @@ -1149,11 +1149,8 @@ struct config_include_data { #define CONFIG_INCLUDE_INIT { 0 } extern int git_config_include(const char *name, const char *value, void *data); -#define IDENT_NAME_GIVEN 01 -#define IDENT_MAIL_GIVEN 02 -#define IDENT_ALL_GIVEN (IDENT_NAME_GIVEN|IDENT_MAIL_GIVEN) -extern int user_ident_explicitly_given; -extern int user_ident_sufficiently_given(void); +extern int committer_ident_sufficiently_given(void); +extern int author_ident_sufficiently_given(void); extern const char *git_commit_encoding; extern const char *git_log_output_encoding; @@ -1183,6 +1180,7 @@ extern int pager_in_use(void); extern int pager_use_color; extern int term_columns(void); extern int decimal_width(int); +extern int check_pager_config(const char *cmd); extern const char *editor_program; extern const char *askpass_program; @@ -1265,8 +1263,15 @@ struct startup_info { }; extern struct startup_info *startup_info; -/* builtin/merge.c */ -int checkout_fast_forward(const unsigned char *from, const unsigned char *to); +/* merge.c */ +struct commit_list; +int try_merge_command(const char *strategy, size_t xopts_nr, + const char **xopts, struct commit_list *common, + const char *head_arg, struct commit_list *remotes); +int checkout_fast_forward(const unsigned char *from, + const unsigned char *to, + int overwrite_ignore); + int sane_execvp(const char *file, char *const argv[]); diff --git a/commit.c b/commit.c index 213bc98c18..e8eb0aec55 100644 --- a/commit.c +++ b/commit.c @@ -1347,3 +1347,13 @@ struct commit_list **commit_list_append(struct commit *commit, new->next = NULL; return &new->next; } + +void print_commit_list(struct commit_list *list, + const char *format_cur, + const char *format_last) +{ + for ( ; list; list = list->next) { + const char *format = list->next ? format_cur : format_last; + printf(format, sha1_to_hex(list->item->object.sha1)); + } +} diff --git a/commit.h b/commit.h index 9f2131318d..b6ad8f3f30 100644 --- a/commit.h +++ b/commit.h @@ -86,7 +86,7 @@ struct pretty_print_context { enum date_mode date_mode; unsigned date_mode_explicit:1; int need_8bit_cte; - int show_notes; + char *notes_message; struct reflog_walk_info *reflog_info; const char *output_encoding; }; @@ -99,8 +99,6 @@ extern int has_non_ascii(const char *text); struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */ extern char *logmsg_reencode(const struct commit *commit, const char *output_encoding); -extern char *reencode_commit_message(const struct commit *commit, - const char **encoding_p); extern void get_commit_format(const char *arg, struct rev_info *); extern const char *format_subject(struct strbuf *sb, const char *msg, const char *line_separator); @@ -222,4 +220,8 @@ struct commit *get_merge_parent(const char *name); extern int parse_signed_commit(const unsigned char *sha1, struct strbuf *message, struct strbuf *signature); +extern void print_commit_list(struct commit_list *list, + const char *format_cur, + const char *format_last); + #endif /* COMMIT_H */ diff --git a/compat/cygwin.c b/compat/cygwin.c index dfe9b3084f..5428858875 100644 --- a/compat/cygwin.c +++ b/compat/cygwin.c @@ -1,6 +1,13 @@ #define WIN32_LEAN_AND_MEAN +#ifdef CYGWIN_V15_WIN32API #include "../git-compat-util.h" #include "win32.h" +#else +#include +#include +#include "win32.h" +#include "../git-compat-util.h" +#endif #include "../cache.h" /* to read configuration */ static inline void filetime_to_timespec(const FILETIME *ft, struct timespec *ts) diff --git a/compat/fnmatch/fnmatch.c b/compat/fnmatch/fnmatch.c index 9473aed2bb..0ff1d273a5 100644 --- a/compat/fnmatch/fnmatch.c +++ b/compat/fnmatch/fnmatch.c @@ -345,7 +345,7 @@ internal_fnmatch (pattern, string, no_leading_period, flags) for (;;) { - if (c1 == CHAR_CLASS_MAX_LENGTH) + if (c1 > CHAR_CLASS_MAX_LENGTH) /* The name is too long and therefore the pattern is ill-formed. */ return FNM_NOMATCH; diff --git a/compat/mingw.c b/compat/mingw.c index afc892d6b1..4e6383898c 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -335,6 +335,28 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream) return freopen(filename, otype, stream); } +#undef fflush +int mingw_fflush(FILE *stream) +{ + int ret = fflush(stream); + + /* + * write() is used behind the scenes of stdio output functions. + * Since git code does not check for errors after each stdio write + * operation, it can happen that write() is called by a later + * stdio function even if an earlier write() call failed. In the + * case of a pipe whose readable end was closed, only the first + * call to write() reports EPIPE on Windows. Subsequent write() + * calls report EINVAL. It is impossible to notice whether this + * fflush invocation triggered such a case, therefore, we have to + * catch all EINVAL errors whole-sale. + */ + if (ret && errno == EINVAL) + errno = EPIPE; + + return ret; +} + /* * The unit of FILETIME is 100-nanoseconds since January 1, 1601, UTC. * Returns the 100-nanoseconds ("hekto nanoseconds") since the epoch. diff --git a/compat/mingw.h b/compat/mingw.h index 61a652138a..eeb08d120b 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -185,6 +185,9 @@ FILE *mingw_fopen (const char *filename, const char *otype); FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream); #define freopen mingw_freopen +int mingw_fflush(FILE *stream); +#define fflush mingw_fflush + char *mingw_getcwd(char *pointer, int len); #define getcwd mingw_getcwd diff --git a/config.c b/config.c index 08e47e2e48..fb3f8681ee 100644 --- a/config.c +++ b/config.c @@ -10,8 +10,6 @@ #include "strbuf.h" #include "quote.h" -#define MAXNAME (256) - typedef struct config_file { struct config_file *prev; FILE *f; @@ -19,7 +17,7 @@ typedef struct config_file { int linenr; int eof; struct strbuf value; - char var[MAXNAME]; + struct strbuf var; } config_file; static config_file *cf; @@ -260,7 +258,7 @@ static inline int iskeychar(int c) return isalnum(c) || c == '-'; } -static int get_value(config_fn_t fn, void *data, char *name, unsigned int len) +static int get_value(config_fn_t fn, void *data, struct strbuf *name) { int c; char *value; @@ -272,11 +270,9 @@ static int get_value(config_fn_t fn, void *data, char *name, unsigned int len) break; if (!iskeychar(c)) break; - name[len++] = tolower(c); - if (len >= MAXNAME) - return -1; + strbuf_addch(name, tolower(c)); } - name[len] = 0; + while (c == ' ' || c == '\t') c = get_next_char(); @@ -288,10 +284,10 @@ static int get_value(config_fn_t fn, void *data, char *name, unsigned int len) if (!value) return -1; } - return fn(name, value, data); + return fn(name->buf, value, data); } -static int get_extended_base_var(char *name, int baselen, int c) +static int get_extended_base_var(struct strbuf *name, int c) { do { if (c == '\n') @@ -302,7 +298,7 @@ static int get_extended_base_var(char *name, int baselen, int c) /* We require the format to be '[base "extension"]' */ if (c != '"') return -1; - name[baselen++] = '.'; + strbuf_addch(name, '.'); for (;;) { int c = get_next_char(); @@ -315,37 +311,31 @@ static int get_extended_base_var(char *name, int baselen, int c) if (c == '\n') goto error_incomplete_line; } - name[baselen++] = c; - if (baselen > MAXNAME / 2) - return -1; + strbuf_addch(name, c); } /* Final ']' */ if (get_next_char() != ']') return -1; - return baselen; + return 0; error_incomplete_line: cf->linenr--; return -1; } -static int get_base_var(char *name) +static int get_base_var(struct strbuf *name) { - int baselen = 0; - for (;;) { int c = get_next_char(); if (cf->eof) return -1; if (c == ']') - return baselen; + return 0; if (isspace(c)) - return get_extended_base_var(name, baselen, c); + return get_extended_base_var(name, c); if (!iskeychar(c) && c != '.') return -1; - if (baselen > MAXNAME / 2) - return -1; - name[baselen++] = tolower(c); + strbuf_addch(name, tolower(c)); } } @@ -353,7 +343,7 @@ static int git_parse_file(config_fn_t fn, void *data) { int comment = 0; int baselen = 0; - char *var = cf->var; + struct strbuf *var = &cf->var; /* U+FEFF Byte Order Mark in UTF8 */ static const unsigned char *utf8_bom = (unsigned char *) "\xef\xbb\xbf"; @@ -389,17 +379,24 @@ static int git_parse_file(config_fn_t fn, void *data) continue; } if (c == '[') { - baselen = get_base_var(var); - if (baselen <= 0) + /* Reset prior to determining a new stem */ + strbuf_reset(var); + if (get_base_var(var) < 0 || var->len < 1) break; - var[baselen++] = '.'; - var[baselen] = 0; + strbuf_addch(var, '.'); + baselen = var->len; continue; } if (!isalpha(c)) break; - var[baselen] = tolower(c); - if (get_value(fn, data, var, baselen+1) < 0) + /* + * Truncate the var name back to the section header + * stem prior to grabbing the suffix part of the name + * and the value. + */ + strbuf_setlen(var, baselen); + strbuf_addch(var, tolower(c)); + if (get_value(fn, data, var) < 0) break; } die("bad config file line %d in %s", cf->linenr, cf->name); @@ -899,12 +896,14 @@ int git_config_from_file(config_fn_t fn, const char *filename, void *data) top.linenr = 1; top.eof = 0; strbuf_init(&top.value, 1024); + strbuf_init(&top.var, 1024); cf = ⊤ ret = git_parse_file(fn, data); /* pop config-file parsing state stack */ strbuf_release(&top.value); + strbuf_release(&top.var); cf = top.prev; fclose(f); @@ -1280,6 +1279,7 @@ int git_config_parse_key(const char *key, char **store_key, int *baselen_) out_free_ret_1: free(*store_key); + *store_key = NULL; return -CONFIG_INVALID_KEY; } diff --git a/configure.ac b/configure.ac index c85888c851..ad215cc4a1 100644 --- a/configure.ac +++ b/configure.ac @@ -1024,7 +1024,7 @@ elif test -z "$PTHREAD_CFLAGS"; then for opt in -mt -pthread -lpthread; do old_CFLAGS="$CFLAGS" CFLAGS="$opt $CFLAGS" - AC_MSG_CHECKING([Checking for POSIX Threads with '$opt']) + AC_MSG_CHECKING([for POSIX Threads with '$opt']) AC_LINK_IFELSE([PTHREADTEST_SRC], [AC_MSG_RESULT([yes]) NO_PTHREADS= @@ -1044,7 +1044,7 @@ elif test -z "$PTHREAD_CFLAGS"; then else old_CFLAGS="$CFLAGS" CFLAGS="$PTHREAD_CFLAGS $CFLAGS" - AC_MSG_CHECKING([Checking for POSIX Threads with '$PTHREAD_CFLAGS']) + AC_MSG_CHECKING([for POSIX Threads with '$PTHREAD_CFLAGS']) AC_LINK_IFELSE([PTHREADTEST_SRC], [AC_MSG_RESULT([yes]) NO_PTHREADS= diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index be800e09bd..0b77eb1fa4 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -23,10 +23,6 @@ # 3) Consider changing your PS1 to also show the current branch, # see git-prompt.sh for details. -if [[ -n ${ZSH_VERSION-} ]]; then - autoload -U +X bashcompinit && bashcompinit -fi - case "$COMP_WORDBREAKS" in *:*) : great ;; *) COMP_WORDBREAKS="$COMP_WORDBREAKS:" @@ -169,7 +165,6 @@ __git_reassemble_comp_words_by_ref() } if ! type _get_comp_words_by_ref >/dev/null 2>&1; then -if [[ -z ${ZSH_VERSION:+set} ]]; then _get_comp_words_by_ref () { local exclude cur_ words_ cword_ @@ -197,32 +192,6 @@ _get_comp_words_by_ref () shift done } -else -_get_comp_words_by_ref () -{ - while [ $# -gt 0 ]; do - case "$1" in - cur) - cur=${COMP_WORDS[COMP_CWORD]} - ;; - prev) - prev=${COMP_WORDS[COMP_CWORD-1]} - ;; - words) - words=("${COMP_WORDS[@]}") - ;; - cword) - cword=$COMP_CWORD - ;; - -n) - # assume COMP_WORDBREAKS is already set sanely - shift - ;; - esac - shift - done -} -fi fi # Generates completion reply with compgen, appending a space to possible @@ -321,7 +290,7 @@ __git_refs () if [[ "$ref" == "$cur"* ]]; then echo "$ref" fi - done | uniq -u + done | sort | uniq -u fi return fi @@ -585,7 +554,7 @@ __git_list_porcelain_commands () { local i IFS=" "$'\n' __git_compute_all_commands - for i in "help" $__git_all_commands + for i in $__git_all_commands do case $i in *--*) : helper pattern;; @@ -989,6 +958,8 @@ _git_clone () --upload-pack --template= --depth + --single-branch + --branch " return ;; @@ -1116,6 +1087,14 @@ _git_fetch () __git_complete_remote_or_refspec } +__git_format_patch_options=" + --stdout --attach --no-attach --thread --thread= --output-directory + --numbered --start-number --numbered-files --keep-subject --signoff + --signature --no-signature --in-reply-to= --cc= --full-index --binary + --not --all --cover-letter --no-prefix --src-prefix= --dst-prefix= + --inline --suffix= --ignore-if-in-upstream --subject-prefix= +" + _git_format_patch () { case "$cur" in @@ -1126,21 +1105,7 @@ _git_format_patch () return ;; --*) - __gitcomp " - --stdout --attach --no-attach --thread --thread= - --output-directory - --numbered --start-number - --numbered-files - --keep-subject - --signoff --signature --no-signature - --in-reply-to= --cc= - --full-index --binary - --not --all - --cover-letter - --no-prefix --src-prefix= --dst-prefix= - --inline --suffix= --ignore-if-in-upstream - --subject-prefix= - " + __gitcomp "$__git_format_patch_options" return ;; esac @@ -1554,6 +1519,12 @@ _git_send_email () __gitcomp "ssl tls" "" "${cur##--smtp-encryption=}" return ;; + --thread=*) + __gitcomp " + deep shallow + " "" "${cur##--thread=}" + return + ;; --*) __gitcomp "--annotate --bcc --cc --cc-cmd --chain-reply-to --compose --confirm= --dry-run --envelope-sender @@ -1563,11 +1534,12 @@ _git_send_email () --signed-off-by-cc --smtp-pass --smtp-server --smtp-server-port --smtp-encryption= --smtp-user --subject --suppress-cc= --suppress-from --thread --to - --validate --no-validate" + --validate --no-validate + $__git_format_patch_options" return ;; esac - COMPREPLY=() + __git_complete_revlist } _git_stage () @@ -2429,20 +2401,71 @@ __gitk_main () __git_complete_revlist } -__git_func_wrap () -{ - if [[ -n ${ZSH_VERSION-} ]]; then - emulate -L bash - setopt KSH_TYPESET +if [[ -n ${ZSH_VERSION-} ]]; then + echo "WARNING: this script is deprecated, please see git-completion.zsh" 1>&2 - # workaround zsh's bug that leaves 'words' as a special - # variable in versions < 4.3.12 - typeset -h words + autoload -U +X compinit && compinit - # workaround zsh's bug that quotes spaces in the COMPREPLY - # array if IFS doesn't contain spaces. - typeset -h IFS - fi + __gitcomp () + { + emulate -L zsh + + local cur_="${3-$cur}" + + case "$cur_" in + --*=) + ;; + *) + local c IFS=$' \t\n' + local -a array + for c in ${=1}; do + c="$c${4-}" + case $c in + --*=*|*.) ;; + *) c="$c " ;; + esac + array+=("$c") + done + compset -P '*[=:]' + compadd -Q -S '' -p "${2-}" -a -- array && _ret=0 + ;; + esac + } + + __gitcomp_nl () + { + emulate -L zsh + + local IFS=$'\n' + compset -P '*[=:]' + compadd -Q -S "${4- }" -p "${2-}" -- ${=1} && _ret=0 + } + + __git_zsh_helper () + { + emulate -L ksh + local cur cword prev + cur=${words[CURRENT-1]} + prev=${words[CURRENT-2]} + let cword=CURRENT-1 + __${service}_main + } + + _git () + { + emulate -L zsh + local _ret=1 + __git_zsh_helper + let _ret && _default -S '' && _ret=0 + return _ret + } + + compdef _git git gitk + return +fi + +__git_func_wrap () +{ local cur words cword prev _get_comp_words_by_ref -n =: cur words cword prev $1 diff --git a/contrib/completion/git-completion.tcsh b/contrib/completion/git-completion.tcsh new file mode 100644 index 0000000000..471f47b404 --- /dev/null +++ b/contrib/completion/git-completion.tcsh @@ -0,0 +1,68 @@ +#!tcsh +# +# tcsh completion support for core Git. +# +# Copyright (C) 2012 Marc Khouzam +# Distributed under the GNU General Public License, version 2.0. +# +# When sourced, this script will generate a new script that uses +# the git-completion.bash script provided by core Git. This new +# script can be used by tcsh to perform git completion. +# The current script also issues the necessary tcsh 'complete' +# commands. +# +# To use this completion script: +# +# 1) Copy both this file and the bash completion script to ${HOME}. +# You _must_ use the name ${HOME}/.git-completion.bash for the +# bash script. +# (e.g. ~/.git-completion.tcsh and ~/.git-completion.bash). +# 2) Add the following line to your .tcshrc/.cshrc: +# source ~/.git-completion.tcsh + +set __git_tcsh_completion_original_script = ${HOME}/.git-completion.bash +set __git_tcsh_completion_script = ${HOME}/.git-completion.tcsh.bash + +# Check that the user put the script in the right place +if ( ! -e ${__git_tcsh_completion_original_script} ) then + echo "git-completion.tcsh: Cannot find: ${__git_tcsh_completion_original_script}. Git completion will not work." + exit +endif + +cat << EOF > ${__git_tcsh_completion_script} +#!bash +# +# This script is GENERATED and will be overwritten automatically. +# Do not modify it directly. Instead, modify the git-completion.tcsh +# script provided by Git core. +# + +source ${__git_tcsh_completion_original_script} + +# Set COMP_WORDS in a way that can be handled by the bash script. +COMP_WORDS=(\$2) + +# The cursor is at the end of parameter #1. +# We must check for a space as the last character which will +# tell us that the previous word is complete and the cursor +# is on the next word. +if [ "\${2: -1}" == " " ]; then + # The last character is a space, so our location is at the end + # of the command-line array + COMP_CWORD=\${#COMP_WORDS[@]} +else + # The last character is not a space, so our location is on the + # last word of the command-line array, so we must decrement the + # count by 1 + COMP_CWORD=\$((\${#COMP_WORDS[@]}-1)) +fi + +# Call _git() or _gitk() of the bash script, based on the first argument +_\${1} + +IFS=\$'\n' +echo "\${COMPREPLY[*]}" | sort | uniq +EOF + +complete git 'p/*/`bash ${__git_tcsh_completion_script} git "${COMMAND_LINE}"`/' +complete gitk 'p/*/`bash ${__git_tcsh_completion_script} gitk "${COMMAND_LINE}"`/' diff --git a/contrib/completion/git-completion.zsh b/contrib/completion/git-completion.zsh new file mode 100644 index 0000000000..45775021ff --- /dev/null +++ b/contrib/completion/git-completion.zsh @@ -0,0 +1,78 @@ +#compdef git gitk + +# zsh completion wrapper for git +# +# You need git's bash completion script installed somewhere, by default on the +# same directory as this script. +# +# If your script is on ~/.git-completion.sh instead, you can configure it on +# your ~/.zshrc: +# +# zstyle ':completion:*:*:git:*' script ~/.git-completion.sh +# +# The recommended way to install this script is to copy to +# '~/.zsh/completion/_git', and then add the following to your ~/.zshrc file: +# +# fpath=(~/.zsh/completion $fpath) + +complete () +{ + # do nothing + return 0 +} + +zstyle -s ":completion:*:*:git:*" script script +test -z "$script" && script="$(dirname ${funcsourcetrace[1]%:*})"/git-completion.bash +ZSH_VERSION='' . "$script" + +__gitcomp () +{ + emulate -L zsh + + local cur_="${3-$cur}" + + case "$cur_" in + --*=) + ;; + *) + local c IFS=$' \t\n' + local -a array + for c in ${=1}; do + c="$c${4-}" + case $c in + --*=*|*.) ;; + *) c="$c " ;; + esac + array+=("$c") + done + compset -P '*[=:]' + compadd -Q -S '' -p "${2-}" -a -- array && _ret=0 + ;; + esac +} + +__gitcomp_nl () +{ + emulate -L zsh + + local IFS=$'\n' + compset -P '*[=:]' + compadd -Q -S "${4- }" -p "${2-}" -- ${=1} && _ret=0 +} + +_git () +{ + local _ret=1 + () { + emulate -L ksh + local cur cword prev + cur=${words[CURRENT-1]} + prev=${words[CURRENT-2]} + let cword=CURRENT-1 + __${service}_main + } + let _ret && _default -S '' && _ret=0 + return _ret +} + +_git diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh index bf20491ec3..00fc099b8d 100644 --- a/contrib/completion/git-prompt.sh +++ b/contrib/completion/git-prompt.sh @@ -10,9 +10,14 @@ # 1) Copy this file to somewhere (e.g. ~/.git-prompt.sh). # 2) Add the following line to your .bashrc/.zshrc: # source ~/.git-prompt.sh -# 3) Change your PS1 to also show the current branch: -# Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ ' -# ZSH: PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ ' +# 3a) In ~/.bashrc set PROMPT_COMMAND=__git_ps1 +# To customize the prompt, provide start/end arguments +# PROMPT_COMMAND='__git_ps1 "\u@\h:\w" "\\\$ "' +# 3b) Alternatively change your PS1 to call __git_ps1 as +# command-substitution: +# Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ ' +# ZSH: PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ ' +# the optional argument will be used as format string # # The argument to __git_ps1 will be displayed only if you are currently # in a git repository. The %s token will be the name of the current @@ -49,7 +54,11 @@ # find one, or @{upstream} otherwise. Once you have set # GIT_PS1_SHOWUPSTREAM, you can override it on a per-repository basis by # setting the bash.showUpstream config variable. - +# +# If you would like a colored hint about the current dirty state, set +# GIT_PS1_SHOWCOLORHINTS to a nonempty value. The colors are based on +# the colored output of "git status -sb". +# # __gitdir accepts 0 or 1 arguments (i.e., location) # returns location of .git repo __gitdir () @@ -195,11 +204,40 @@ __git_ps1_show_upstream () # __git_ps1 accepts 0 or 1 arguments (i.e., format string) -# returns text to add to bash PS1 prompt (includes branch name) +# when called from PS1 using command substitution +# in this mode it prints text to add to bash PS1 prompt (includes branch name) +# +# __git_ps1 requires 2 arguments when called from PROMPT_COMMAND (pc) +# in that case it _sets_ PS1. The arguments are parts of a PS1 string. +# when both arguments are given, the first is prepended and the second appended +# to the state string when assigned to PS1. +# In this mode you can request colored hints using GIT_PS1_SHOWCOLORHINTS=true __git_ps1 () { + local pcmode=no + local detached=no + local ps1pc_start='\u@\h:\w ' + local ps1pc_end='\$ ' + local printf_format=' (%s)' + + case "$#" in + 2) pcmode=yes + ps1pc_start="$1" + ps1pc_end="$2" + ;; + 0|1) printf_format="${1:-$printf_format}" + ;; + *) return + ;; + esac + local g="$(__gitdir)" - if [ -n "$g" ]; then + if [ -z "$g" ]; then + if [ $pcmode = yes ]; then + #In PC mode PS1 always needs to be set + PS1="$ps1pc_start$ps1pc_end" + fi + else local r="" local b="" if [ -f "$g/rebase-merge/interactive" ]; then @@ -226,7 +264,7 @@ __git_ps1 () fi b="$(git symbolic-ref HEAD 2>/dev/null)" || { - + detached=yes b="$( case "${GIT_PS1_DESCRIBE_STYLE-}" in (contains) @@ -285,6 +323,50 @@ __git_ps1 () fi local f="$w$i$s$u" - printf -- "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r$p" + if [ $pcmode = yes ]; then + if [ -n "${GIT_PS1_SHOWCOLORHINTS-}" ]; then + local c_red='\e[31m' + local c_green='\e[32m' + local c_lblue='\e[1;34m' + local c_clear='\e[0m' + local bad_color=$c_red + local ok_color=$c_green + local branch_color="$c_clear" + local flags_color="$c_lblue" + local branchstring="$c${b##refs/heads/}" + + if [ $detached = no ]; then + branch_color="$ok_color" + else + branch_color="$bad_color" + fi + + # Setting PS1 directly with \[ and \] around colors + # is necessary to prevent wrapping issues! + PS1="$ps1pc_start (\[$branch_color\]$branchstring\[$c_clear\]" + + if [ -n "$w$i$s$u$r$p" ]; then + PS1="$PS1 " + fi + if [ "$w" = "*" ]; then + PS1="$PS1\[$bad_color\]$w" + fi + if [ -n "$i" ]; then + PS1="$PS1\[$ok_color\]$i" + fi + if [ -n "$s" ]; then + PS1="$PS1\[$flags_color\]$s" + fi + if [ -n "$u" ]; then + PS1="$PS1\[$bad_color\]$u" + fi + PS1="$PS1\[$c_clear\]$r$p)$ps1pc_end" + else + PS1="$ps1pc_start ($c${b##refs/heads/}${f:+ $f}$r$p)$ps1pc_end" + fi + else + # NO color option unless in PROMPT_COMMAND mode + printf -- "$printf_format" "$c${b##refs/heads/}${f:+ $f}$r$p" + fi fi } diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el index 65c95d9d5a..5ffc506f6d 100644 --- a/contrib/emacs/git.el +++ b/contrib/emacs/git.el @@ -1671,7 +1671,7 @@ Commands: "Entry point into git-status mode." (interactive "DSelect directory: ") (setq dir (git-get-top-dir dir)) - (if (file-directory-p (concat (file-name-as-directory dir) ".git")) + (if (file-exists-p (concat (file-name-as-directory dir) ".git")) (let ((buffer (or (and git-reuse-status-buffer (git-find-status-buffer dir)) (create-file-buffer (expand-file-name "*git-status*" dir))))) (switch-to-buffer buffer) diff --git a/contrib/mw-to-git/git-remote-mediawiki b/contrib/mw-to-git/git-remote-mediawiki index 68555d4265..094129de09 100755 --- a/contrib/mw-to-git/git-remote-mediawiki +++ b/contrib/mw-to-git/git-remote-mediawiki @@ -711,6 +711,14 @@ sub fetch_mw_revisions { return ($n, @revisions); } +sub fe_escape_path { + my $path = shift; + $path =~ s/\\/\\\\/g; + $path =~ s/"/\\"/g; + $path =~ s/\n/\\n/g; + return '"' . $path . '"'; +} + sub import_file_revision { my $commit = shift; my %commit = %{$commit}; @@ -738,15 +746,17 @@ sub import_file_revision { print STDOUT "from refs/mediawiki/$remotename/master^0\n"; } if ($content ne DELETED_CONTENT) { - print STDOUT "M 644 inline $title.mw\n"; + print STDOUT "M 644 inline " . + fe_escape_path($title . ".mw") . "\n"; literal_data($content); if (%mediafile) { - print STDOUT "M 644 inline $mediafile{title}\n"; + print STDOUT "M 644 inline " + . fe_escape_path($mediafile{title}) . "\n"; literal_data_raw($mediafile{content}); } print STDOUT "\n\n"; } else { - print STDOUT "D $title.mw\n"; + print STDOUT "D " . fe_escape_path($title . ".mw") . "\n"; } # mediawiki revision number in the git note diff --git a/contrib/mw-to-git/t/t9362-mw-to-git-utf8.sh b/contrib/mw-to-git/t/t9362-mw-to-git-utf8.sh index 246d47d8fb..b6405ce262 100755 --- a/contrib/mw-to-git/t/t9362-mw-to-git-utf8.sh +++ b/contrib/mw-to-git/t/t9362-mw-to-git-utf8.sh @@ -318,4 +318,30 @@ test_expect_success 'git push with \ in format control' ' ' +test_expect_success 'fast-import meta-characters in page name (mw -> git)' ' + wiki_reset && + wiki_editpage \"file\"_\\_foo "expect to be called \"file\"_\\_foo" false && + git clone mediawiki::'"$WIKI_URL"' mw_dir_21 && + test_path_is_file mw_dir_21/\"file\"_\\_foo.mw && + wiki_getallpage ref_page_21 && + test_diff_directories mw_dir_21 ref_page_21 +' + + +test_expect_success 'fast-import meta-characters in page name (git -> mw) ' ' + wiki_reset && + git clone mediawiki::'"$WIKI_URL"' mw_dir_22 && + ( + cd mw_dir_22 && + echo "this file is called \"file\"_\\_foo.mw" >\"file\"_\\_foo && + git add . && + git commit -am "file \"file\"_\\_foo" && + git pull && + git push + ) && + wiki_getallpage ref_page_22 && + test_diff_directories mw_dir_22 ref_page_22 +' + + test_done diff --git a/contrib/remote-helpers/Makefile b/contrib/remote-helpers/Makefile new file mode 100644 index 0000000000..9a76575f78 --- /dev/null +++ b/contrib/remote-helpers/Makefile @@ -0,0 +1,13 @@ +TESTS := $(wildcard test*.sh) + +export T := $(addprefix $(CURDIR)/,$(TESTS)) +export MAKE := $(MAKE) -e +export PATH := $(CURDIR):$(PATH) + +test: + $(MAKE) -C ../../t $@ + +$(TESTS): + $(MAKE) -C ../../t $(CURDIR)/$@ + +.PHONY: $(TESTS) diff --git a/contrib/remote-helpers/git-remote-hg b/contrib/remote-helpers/git-remote-hg new file mode 100755 index 0000000000..016cdadb4d --- /dev/null +++ b/contrib/remote-helpers/git-remote-hg @@ -0,0 +1,794 @@ +#!/usr/bin/env python +# +# Copyright (c) 2012 Felipe Contreras +# + +# Inspired by Rocco Rutte's hg-fast-export + +# Just copy to your ~/bin, or anywhere in your $PATH. +# Then you can clone with: +# git clone hg::/path/to/mercurial/repo/ + +from mercurial import hg, ui, bookmarks, context, util, encoding + +import re +import sys +import os +import json +import shutil +import subprocess +import urllib + +# +# If you want to switch to hg-git compatibility mode: +# git config --global remote-hg.hg-git-compat true +# +# git: +# Sensible defaults for git. +# hg bookmarks are exported as git branches, hg branches are prefixed +# with 'branches/', HEAD is a special case. +# +# hg: +# Emulate hg-git. +# Only hg bookmarks are exported as git branches. +# Commits are modified to preserve hg information and allow biridectionality. +# + +NAME_RE = re.compile('^([^<>]+)') +AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]*)>$') +AUTHOR_HG_RE = re.compile('^(.*?) ?<(.*?)(?:>(.+)?)?$') +RAW_AUTHOR_RE = re.compile('^(\w+) (?:(.+)? )?<(.*)> (\d+) ([+-]\d+)') + +def die(msg, *args): + sys.stderr.write('ERROR: %s\n' % (msg % args)) + sys.exit(1) + +def warn(msg, *args): + sys.stderr.write('WARNING: %s\n' % (msg % args)) + +def gitmode(flags): + return 'l' in flags and '120000' or 'x' in flags and '100755' or '100644' + +def gittz(tz): + return '%+03d%02d' % (-tz / 3600, -tz % 3600 / 60) + +def hgmode(mode): + m = { '0100755': 'x', '0120000': 'l' } + return m.get(mode, '') + +def get_config(config): + cmd = ['git', 'config', '--get', config] + process = subprocess.Popen(cmd, stdout=subprocess.PIPE) + output, _ = process.communicate() + return output + +class Marks: + + def __init__(self, path): + self.path = path + self.tips = {} + self.marks = {} + self.rev_marks = {} + self.last_mark = 0 + + self.load() + + def load(self): + if not os.path.exists(self.path): + return + + tmp = json.load(open(self.path)) + + self.tips = tmp['tips'] + self.marks = tmp['marks'] + self.last_mark = tmp['last-mark'] + + for rev, mark in self.marks.iteritems(): + self.rev_marks[mark] = int(rev) + + def dict(self): + return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark } + + def store(self): + json.dump(self.dict(), open(self.path, 'w')) + + def __str__(self): + return str(self.dict()) + + def from_rev(self, rev): + return self.marks[str(rev)] + + def to_rev(self, mark): + return self.rev_marks[mark] + + def get_mark(self, rev): + self.last_mark += 1 + self.marks[str(rev)] = self.last_mark + return self.last_mark + + def new_mark(self, rev, mark): + self.marks[str(rev)] = mark + self.rev_marks[mark] = rev + self.last_mark = mark + + def is_marked(self, rev): + return self.marks.has_key(str(rev)) + + def get_tip(self, branch): + return self.tips.get(branch, 0) + + def set_tip(self, branch, tip): + self.tips[branch] = tip + +class Parser: + + def __init__(self, repo): + self.repo = repo + self.line = self.get_line() + + def get_line(self): + return sys.stdin.readline().strip() + + def __getitem__(self, i): + return self.line.split()[i] + + def check(self, word): + return self.line.startswith(word) + + def each_block(self, separator): + while self.line != separator: + yield self.line + self.line = self.get_line() + + def __iter__(self): + return self.each_block('') + + def next(self): + self.line = self.get_line() + if self.line == 'done': + self.line = None + + def get_mark(self): + i = self.line.index(':') + 1 + return int(self.line[i:]) + + def get_data(self): + if not self.check('data'): + return None + i = self.line.index(' ') + 1 + size = int(self.line[i:]) + return sys.stdin.read(size) + + def get_author(self): + global bad_mail + + ex = None + m = RAW_AUTHOR_RE.match(self.line) + if not m: + return None + _, name, email, date, tz = m.groups() + if name and 'ext:' in name: + m = re.match('^(.+?) ext:\((.+)\)$', name) + if m: + name = m.group(1) + ex = urllib.unquote(m.group(2)) + + if email != bad_mail: + if name: + user = '%s <%s>' % (name, email) + else: + user = '<%s>' % (email) + else: + user = name + + if ex: + user += ex + + tz = int(tz) + tz = ((tz / 100) * 3600) + ((tz % 100) * 60) + return (user, int(date), -tz) + +def export_file(fc): + d = fc.data() + print "M %s inline %s" % (gitmode(fc.flags()), fc.path()) + print "data %d" % len(d) + print d + +def get_filechanges(repo, ctx, parent): + modified = set() + added = set() + removed = set() + + cur = ctx.manifest() + prev = repo[parent].manifest().copy() + + for fn in cur: + if fn in prev: + if (cur.flags(fn) != prev.flags(fn) or cur[fn] != prev[fn]): + modified.add(fn) + del prev[fn] + else: + added.add(fn) + removed |= set(prev.keys()) + + return added | modified, removed + +def fixup_user_git(user): + name = mail = None + user = user.replace('"', '') + m = AUTHOR_RE.match(user) + if m: + name = m.group(1) + mail = m.group(2).strip() + else: + m = NAME_RE.match(user) + if m: + name = m.group(1).strip() + return (name, mail) + +def fixup_user_hg(user): + def sanitize(name): + # stole this from hg-git + return re.sub('[<>\n]', '?', name.lstrip('< ').rstrip('> ')) + + m = AUTHOR_HG_RE.match(user) + if m: + name = sanitize(m.group(1)) + mail = sanitize(m.group(2)) + ex = m.group(3) + if ex: + name += ' ext:(' + urllib.quote(ex) + ')' + else: + name = sanitize(user) + if '@' in user: + mail = name + else: + mail = None + + return (name, mail) + +def fixup_user(user): + global mode, bad_mail + + if mode == 'git': + name, mail = fixup_user_git(user) + else: + name, mail = fixup_user_hg(user) + + if not name: + name = bad_name + if not mail: + mail = bad_mail + + return '%s <%s>' % (name, mail) + +def get_repo(url, alias): + global dirname, peer + + myui = ui.ui() + myui.setconfig('ui', 'interactive', 'off') + + if hg.islocal(url): + repo = hg.repository(myui, url) + else: + local_path = os.path.join(dirname, 'clone') + if not os.path.exists(local_path): + peer, dstpeer = hg.clone(myui, {}, url, local_path, update=False, pull=True) + repo = dstpeer.local() + else: + repo = hg.repository(myui, local_path) + peer = hg.peer(myui, {}, url) + repo.pull(peer, heads=None, force=True) + + return repo + +def rev_to_mark(rev): + global marks + return marks.from_rev(rev) + +def mark_to_rev(mark): + global marks + return marks.to_rev(mark) + +def export_ref(repo, name, kind, head): + global prefix, marks, mode + + ename = '%s/%s' % (kind, name) + tip = marks.get_tip(ename) + + # mercurial takes too much time checking this + if tip and tip == head.rev(): + # nothing to do + return + revs = xrange(tip, head.rev() + 1) + count = 0 + + revs = [rev for rev in revs if not marks.is_marked(rev)] + + for rev in revs: + + c = repo[rev] + (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(c.node()) + rev_branch = extra['branch'] + + author = "%s %d %s" % (fixup_user(user), time, gittz(tz)) + if 'committer' in extra: + user, time, tz = extra['committer'].rsplit(' ', 2) + committer = "%s %s %s" % (user, time, gittz(int(tz))) + else: + committer = author + + parents = [p for p in repo.changelog.parentrevs(rev) if p >= 0] + + if len(parents) == 0: + modified = c.manifest().keys() + removed = [] + else: + modified, removed = get_filechanges(repo, c, parents[0]) + + if mode == 'hg': + extra_msg = '' + + if rev_branch != 'default': + extra_msg += 'branch : %s\n' % rev_branch + + renames = [] + for f in c.files(): + if f not in c.manifest(): + continue + rename = c.filectx(f).renamed() + if rename: + renames.append((rename[0], f)) + + for e in renames: + extra_msg += "rename : %s => %s\n" % e + + for key, value in extra.iteritems(): + if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'): + continue + else: + extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value)) + + desc += '\n' + if extra_msg: + desc += '\n--HG--\n' + extra_msg + + if len(parents) == 0 and rev: + print 'reset %s/%s' % (prefix, ename) + + print "commit %s/%s" % (prefix, ename) + print "mark :%d" % (marks.get_mark(rev)) + print "author %s" % (author) + print "committer %s" % (committer) + print "data %d" % (len(desc)) + print desc + + if len(parents) > 0: + print "from :%s" % (rev_to_mark(parents[0])) + if len(parents) > 1: + print "merge :%s" % (rev_to_mark(parents[1])) + + for f in modified: + export_file(c.filectx(f)) + for f in removed: + print "D %s" % (f) + print + + count += 1 + if (count % 100 == 0): + print "progress revision %d '%s' (%d/%d)" % (rev, name, count, len(revs)) + print "#############################################################" + + # make sure the ref is updated + print "reset %s/%s" % (prefix, ename) + print "from :%u" % rev_to_mark(rev) + print + + marks.set_tip(ename, rev) + +def export_tag(repo, tag): + export_ref(repo, tag, 'tags', repo[tag]) + +def export_bookmark(repo, bmark): + head = bmarks[bmark] + export_ref(repo, bmark, 'bookmarks', head) + +def export_branch(repo, branch): + tip = get_branch_tip(repo, branch) + head = repo[tip] + export_ref(repo, branch, 'branches', head) + +def export_head(repo): + global g_head + export_ref(repo, g_head[0], 'bookmarks', g_head[1]) + +def do_capabilities(parser): + global prefix, dirname + + print "import" + print "export" + print "refspec refs/heads/branches/*:%s/branches/*" % prefix + print "refspec refs/heads/*:%s/bookmarks/*" % prefix + print "refspec refs/tags/*:%s/tags/*" % prefix + + path = os.path.join(dirname, 'marks-git') + + if os.path.exists(path): + print "*import-marks %s" % path + print "*export-marks %s" % path + + print + +def get_branch_tip(repo, branch): + global branches + + heads = branches.get(branch, None) + if not heads: + return None + + # verify there's only one head + if (len(heads) > 1): + warn("Branch '%s' has more than one head, consider merging" % branch) + # older versions of mercurial don't have this + if hasattr(repo, "branchtip"): + return repo.branchtip(branch) + + return heads[0] + +def list_head(repo, cur): + global g_head, bmarks + + head = bookmarks.readcurrent(repo) + if head: + node = repo[head] + else: + # fake bookmark from current branch + head = cur + node = repo['.'] + if not node: + node = repo['tip'] + if not node: + return + if head == 'default': + head = 'master' + bmarks[head] = node + + print "@refs/heads/%s HEAD" % head + g_head = (head, node) + +def do_list(parser): + global branches, bmarks, mode, track_branches + + repo = parser.repo + for bmark, node in bookmarks.listbookmarks(repo).iteritems(): + bmarks[bmark] = repo[node] + + cur = repo.dirstate.branch() + + list_head(repo, cur) + + if track_branches: + for branch in repo.branchmap(): + heads = repo.branchheads(branch) + if len(heads): + branches[branch] = heads + + for branch in branches: + print "? refs/heads/branches/%s" % branch + + for bmark in bmarks: + print "? refs/heads/%s" % bmark + + for tag, node in repo.tagslist(): + if tag == 'tip': + continue + print "? refs/tags/%s" % tag + + print + +def do_import(parser): + repo = parser.repo + + path = os.path.join(dirname, 'marks-git') + + print "feature done" + if os.path.exists(path): + print "feature import-marks=%s" % path + print "feature export-marks=%s" % path + sys.stdout.flush() + + tmp = encoding.encoding + encoding.encoding = 'utf-8' + + # lets get all the import lines + while parser.check('import'): + ref = parser[1] + + if (ref == 'HEAD'): + export_head(repo) + elif ref.startswith('refs/heads/branches/'): + branch = ref[len('refs/heads/branches/'):] + export_branch(repo, branch) + elif ref.startswith('refs/heads/'): + bmark = ref[len('refs/heads/'):] + export_bookmark(repo, bmark) + elif ref.startswith('refs/tags/'): + tag = ref[len('refs/tags/'):] + export_tag(repo, tag) + + parser.next() + + encoding.encoding = tmp + + print 'done' + +def parse_blob(parser): + global blob_marks + + parser.next() + mark = parser.get_mark() + parser.next() + data = parser.get_data() + blob_marks[mark] = data + parser.next() + return + +def get_merge_files(repo, p1, p2, files): + for e in repo[p1].files(): + if e not in files: + if e not in repo[p1].manifest(): + continue + f = { 'ctx' : repo[p1][e] } + files[e] = f + +def parse_commit(parser): + global marks, blob_marks, bmarks, parsed_refs + global mode + + from_mark = merge_mark = None + + ref = parser[1] + parser.next() + + commit_mark = parser.get_mark() + parser.next() + author = parser.get_author() + parser.next() + committer = parser.get_author() + parser.next() + data = parser.get_data() + parser.next() + if parser.check('from'): + from_mark = parser.get_mark() + parser.next() + if parser.check('merge'): + merge_mark = parser.get_mark() + parser.next() + if parser.check('merge'): + die('octopus merges are not supported yet') + + files = {} + + for line in parser: + if parser.check('M'): + t, m, mark_ref, path = line.split(' ', 3) + mark = int(mark_ref[1:]) + f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] } + elif parser.check('D'): + t, path = line.split(' ') + f = { 'deleted' : True } + else: + die('Unknown file command: %s' % line) + files[path] = f + + def getfilectx(repo, memctx, f): + of = files[f] + if 'deleted' in of: + raise IOError + if 'ctx' in of: + return of['ctx'] + is_exec = of['mode'] == 'x' + is_link = of['mode'] == 'l' + rename = of.get('rename', None) + return context.memfilectx(f, of['data'], + is_link, is_exec, rename) + + repo = parser.repo + + user, date, tz = author + extra = {} + + if committer != author: + extra['committer'] = "%s %u %u" % committer + + if from_mark: + p1 = repo.changelog.node(mark_to_rev(from_mark)) + else: + p1 = '\0' * 20 + + if merge_mark: + p2 = repo.changelog.node(mark_to_rev(merge_mark)) + else: + p2 = '\0' * 20 + + # + # If files changed from any of the parents, hg wants to know, but in git if + # nothing changed from the first parent, nothing changed. + # + if merge_mark: + get_merge_files(repo, p1, p2, files) + + if mode == 'hg': + i = data.find('\n--HG--\n') + if i >= 0: + tmp = data[i + len('\n--HG--\n'):].strip() + for k, v in [e.split(' : ') for e in tmp.split('\n')]: + if k == 'rename': + old, new = v.split(' => ', 1) + files[new]['rename'] = old + elif k == 'branch': + extra[k] = v + elif k == 'extra': + ek, ev = v.split(' : ', 1) + extra[ek] = urllib.unquote(ev) + data = data[:i] + + ctx = context.memctx(repo, (p1, p2), data, + files.keys(), getfilectx, + user, (date, tz), extra) + + tmp = encoding.encoding + encoding.encoding = 'utf-8' + + node = repo.commitctx(ctx) + + encoding.encoding = tmp + + rev = repo[node].rev() + + parsed_refs[ref] = node + + marks.new_mark(rev, commit_mark) + +def parse_reset(parser): + ref = parser[1] + parser.next() + # ugh + if parser.check('commit'): + parse_commit(parser) + return + if not parser.check('from'): + return + from_mark = parser.get_mark() + parser.next() + + node = parser.repo.changelog.node(mark_to_rev(from_mark)) + parsed_refs[ref] = node + +def parse_tag(parser): + name = parser[1] + parser.next() + from_mark = parser.get_mark() + parser.next() + tagger = parser.get_author() + parser.next() + data = parser.get_data() + parser.next() + + # nothing to do + +def do_export(parser): + global parsed_refs, bmarks, peer + + parser.next() + + for line in parser.each_block('done'): + if parser.check('blob'): + parse_blob(parser) + elif parser.check('commit'): + parse_commit(parser) + elif parser.check('reset'): + parse_reset(parser) + elif parser.check('tag'): + parse_tag(parser) + elif parser.check('feature'): + pass + else: + die('unhandled export command: %s' % line) + + for ref, node in parsed_refs.iteritems(): + if ref.startswith('refs/heads/branches'): + pass + elif ref.startswith('refs/heads/'): + bmark = ref[len('refs/heads/'):] + if bmark in bmarks: + old = bmarks[bmark].hex() + else: + old = '' + if not bookmarks.pushbookmark(parser.repo, bmark, old, node): + continue + elif ref.startswith('refs/tags/'): + tag = ref[len('refs/tags/'):] + parser.repo.tag([tag], node, None, True, None, {}) + else: + # transport-helper/fast-export bugs + continue + print "ok %s" % ref + + print + + if peer: + parser.repo.push(peer, force=False) + +def main(args): + global prefix, dirname, branches, bmarks + global marks, blob_marks, parsed_refs + global peer, mode, bad_mail, bad_name + global track_branches + + alias = args[1] + url = args[2] + peer = None + + hg_git_compat = False + track_branches = True + try: + if get_config('remote-hg.hg-git-compat') == 'true\n': + hg_git_compat = True + track_branches = False + if get_config('remote-hg.track-branches') == 'false\n': + track_branches = False + except subprocess.CalledProcessError: + pass + + if hg_git_compat: + mode = 'hg' + bad_mail = 'none@none' + bad_name = '' + else: + mode = 'git' + bad_mail = 'unknown' + bad_name = 'Unknown' + + if alias[4:] == url: + is_tmp = True + alias = util.sha1(alias).hexdigest() + else: + is_tmp = False + + gitdir = os.environ['GIT_DIR'] + dirname = os.path.join(gitdir, 'hg', alias) + branches = {} + bmarks = {} + blob_marks = {} + parsed_refs = {} + + repo = get_repo(url, alias) + prefix = 'refs/hg/%s' % alias + + if not os.path.exists(dirname): + os.makedirs(dirname) + + marks_path = os.path.join(dirname, 'marks-hg') + marks = Marks(marks_path) + + parser = Parser(repo) + for line in parser: + if parser.check('capabilities'): + do_capabilities(parser) + elif parser.check('list'): + do_list(parser) + elif parser.check('import'): + do_import(parser) + elif parser.check('export'): + do_export(parser) + else: + die('unhandled command: %s' % line) + sys.stdout.flush() + + if not is_tmp: + marks.store() + else: + shutil.rmtree(dirname) + +sys.exit(main(sys.argv)) diff --git a/contrib/remote-helpers/test-hg-bidi.sh b/contrib/remote-helpers/test-hg-bidi.sh new file mode 100755 index 0000000000..a94eb28092 --- /dev/null +++ b/contrib/remote-helpers/test-hg-bidi.sh @@ -0,0 +1,243 @@ +#!/bin/sh +# +# Copyright (c) 2012 Felipe Contreras +# +# Base commands from hg-git tests: +# https://bitbucket.org/durin42/hg-git/src +# + +test_description='Test biridectionality of remote-hg' + +. ./test-lib.sh + +if ! test_have_prereq PYTHON; then + skip_all='skipping remote-hg tests; python not available' + test_done +fi + +if ! "$PYTHON_PATH" -c 'import mercurial'; then + skip_all='skipping remote-hg tests; mercurial not available' + test_done +fi + +# clone to a git repo +git_clone () { + hg -R $1 bookmark -f -r tip master && + git clone -q "hg::$PWD/$1" $2 +} + +# clone to an hg repo +hg_clone () { + ( + hg init $2 && + cd $1 && + git push -q "hg::$PWD/../$2" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' + ) && + + (cd $2 && hg -q update) +} + +# push an hg repo +hg_push () { + ( + cd $2 + old=$(git symbolic-ref --short HEAD) + git checkout -q -b tmp && + git fetch -q "hg::$PWD/../$1" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' && + git checkout -q $old && + git branch -q -D tmp 2> /dev/null || true + ) +} + +hg_log () { + hg -R $1 log --graph --debug | grep -v 'tag: *default/' +} + +setup () { + ( + echo "[ui]" + echo "username = A U Thor " + echo "[defaults]" + echo "backout = -d \"0 0\"" + echo "commit = -d \"0 0\"" + echo "debugrawcommit = -d \"0 0\"" + echo "tag = -d \"0 0\"" + ) >> "$HOME"/.hgrc && + git config --global remote-hg.hg-git-compat true + + export HGEDITOR=/usr/bin/true + + export GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0230" + export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" +} + +setup + +test_expect_success 'encoding' ' + mkdir -p tmp && cd tmp && + test_when_finished "cd .. && rm -rf tmp" && + + ( + git init -q gitrepo && + cd gitrepo && + + echo alpha > alpha && + git add alpha && + git commit -m "add älphà" && + + export GIT_AUTHOR_NAME="tést èncödîng" && + echo beta > beta && + git add beta && + git commit -m "add beta" && + + echo gamma > gamma && + git add gamma && + git commit -m "add gämmâ" && + + : TODO git config i18n.commitencoding latin-1 && + echo delta > delta && + git add delta && + git commit -m "add déltà" + ) && + + hg_clone gitrepo hgrepo && + git_clone hgrepo gitrepo2 && + hg_clone gitrepo2 hgrepo2 && + + HGENCODING=utf-8 hg_log hgrepo > expected && + HGENCODING=utf-8 hg_log hgrepo2 > actual && + + test_cmp expected actual +' + +test_expect_success 'file removal' ' + mkdir -p tmp && cd tmp && + test_when_finished "cd .. && rm -rf tmp" && + + ( + git init -q gitrepo && + cd gitrepo && + echo alpha > alpha && + git add alpha && + git commit -m "add alpha" && + echo beta > beta && + git add beta && + git commit -m "add beta" + mkdir foo && + echo blah > foo/bar && + git add foo && + git commit -m "add foo" && + git rm alpha && + git commit -m "remove alpha" && + git rm foo/bar && + git commit -m "remove foo/bar" + ) && + + hg_clone gitrepo hgrepo && + git_clone hgrepo gitrepo2 && + hg_clone gitrepo2 hgrepo2 && + + hg_log hgrepo > expected && + hg_log hgrepo2 > actual && + + test_cmp expected actual +' + +test_expect_success 'git tags' ' + mkdir -p tmp && cd tmp && + test_when_finished "cd .. && rm -rf tmp" && + + ( + git init -q gitrepo && + cd gitrepo && + git config receive.denyCurrentBranch ignore && + echo alpha > alpha && + git add alpha && + git commit -m "add alpha" && + git tag alpha && + + echo beta > beta && + git add beta && + git commit -m "add beta" && + git tag -a -m "added tag beta" beta + ) && + + hg_clone gitrepo hgrepo && + git_clone hgrepo gitrepo2 && + hg_clone gitrepo2 hgrepo2 && + + hg_log hgrepo > expected && + hg_log hgrepo2 > actual && + + test_cmp expected actual +' + +test_expect_success 'hg branch' ' + mkdir -p tmp && cd tmp && + test_when_finished "cd .. && rm -rf tmp" && + + ( + git init -q gitrepo && + cd gitrepo && + + echo alpha > alpha && + git add alpha && + git commit -q -m "add alpha" && + git checkout -q -b not-master + ) && + + ( + hg_clone gitrepo hgrepo && + + cd hgrepo && + hg -q co master && + hg mv alpha beta && + hg -q commit -m "rename alpha to beta" && + hg branch gamma | grep -v "permanent and global" && + hg -q commit -m "started branch gamma" + ) && + + hg_push hgrepo gitrepo && + hg_clone gitrepo hgrepo2 && + + : TODO, avoid "master" bookmark && + (cd hgrepo2 && hg checkout gamma) && + + hg_log hgrepo > expected && + hg_log hgrepo2 > actual && + + test_cmp expected actual +' + +test_expect_success 'hg tags' ' + mkdir -p tmp && cd tmp && + test_when_finished "cd .. && rm -rf tmp" && + + ( + git init -q gitrepo && + cd gitrepo && + + echo alpha > alpha && + git add alpha && + git commit -m "add alpha" && + git checkout -q -b not-master + ) && + + ( + hg_clone gitrepo hgrepo && + + cd hgrepo && + hg co master && + hg tag alpha + ) && + + hg_push hgrepo gitrepo && + hg_clone gitrepo hgrepo2 && + + hg_log hgrepo > expected && + hg_log hgrepo2 > actual && + + test_cmp expected actual +' + +test_done diff --git a/contrib/remote-helpers/test-hg-hg-git.sh b/contrib/remote-helpers/test-hg-hg-git.sh new file mode 100755 index 0000000000..3e76d9fb60 --- /dev/null +++ b/contrib/remote-helpers/test-hg-hg-git.sh @@ -0,0 +1,466 @@ +#!/bin/sh +# +# Copyright (c) 2012 Felipe Contreras +# +# Base commands from hg-git tests: +# https://bitbucket.org/durin42/hg-git/src +# + +test_description='Test remote-hg output compared to hg-git' + +. ./test-lib.sh + +if ! test_have_prereq PYTHON; then + skip_all='skipping remote-hg tests; python not available' + test_done +fi + +if ! "$PYTHON_PATH" -c 'import mercurial'; then + skip_all='skipping remote-hg tests; mercurial not available' + test_done +fi + +if ! "$PYTHON_PATH" -c 'import hggit'; then + skip_all='skipping remote-hg tests; hg-git not available' + test_done +fi + +# clone to a git repo with git +git_clone_git () { + hg -R $1 bookmark -f -r tip master && + git clone -q "hg::$PWD/$1" $2 +} + +# clone to an hg repo with git +hg_clone_git () { + ( + hg init $2 && + cd $1 && + git push -q "hg::$PWD/../$2" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' + ) && + + (cd $2 && hg -q update) +} + +# clone to a git repo with hg +git_clone_hg () { + ( + git init -q $2 && + cd $1 && + hg bookmark -f -r tip master && + hg -q push -r master ../$2 || true + ) +} + +# clone to an hg repo with hg +hg_clone_hg () { + hg -q clone $1 $2 +} + +# push an hg repo with git +hg_push_git () { + ( + cd $2 + old=$(git symbolic-ref --short HEAD) + git checkout -q -b tmp && + git fetch -q "hg::$PWD/../$1" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' && + git checkout -q $old && + git branch -q -D tmp 2> /dev/null || true + ) +} + +# push an hg git repo with hg +hg_push_hg () { + ( + cd $1 && + hg -q push ../$2 || true + ) +} + +hg_log () { + hg -R $1 log --graph --debug | grep -v 'tag: *default/' +} + +git_log () { + git --git-dir=$1/.git fast-export --branches +} + +setup () { + ( + echo "[ui]" + echo "username = A U Thor " + echo "[defaults]" + echo "backout = -d \"0 0\"" + echo "commit = -d \"0 0\"" + echo "debugrawcommit = -d \"0 0\"" + echo "tag = -d \"0 0\"" + echo "[extensions]" + echo "hgext.bookmarks =" + echo "hggit =" + ) >> "$HOME"/.hgrc && + git config --global receive.denycurrentbranch warn + git config --global remote-hg.hg-git-compat true + + export HGEDITOR=/usr/bin/true + + export GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0230" + export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" +} + +setup + +test_expect_success 'merge conflict 1' ' + mkdir -p tmp && cd tmp && + test_when_finished "cd .. && rm -rf tmp" && + + ( + hg init hgrepo1 && + cd hgrepo1 && + echo A > afile && + hg add afile && + hg ci -m "origin" && + + echo B > afile && + hg ci -m "A->B" && + + hg up -r0 && + echo C > afile && + hg ci -m "A->C" && + + hg merge -r1 || true && + echo C > afile && + hg resolve -m afile && + hg ci -m "merge to C" + ) && + + for x in hg git; do + git_clone_$x hgrepo1 gitrepo-$x && + hg_clone_$x gitrepo-$x hgrepo2-$x && + hg_log hgrepo2-$x > hg-log-$x && + git_log gitrepo-$x > git-log-$x + done && + + test_cmp hg-log-hg hg-log-git && + test_cmp git-log-hg git-log-git +' + +test_expect_success 'merge conflict 2' ' + mkdir -p tmp && cd tmp && + test_when_finished "cd .. && rm -rf tmp" && + + ( + hg init hgrepo1 && + cd hgrepo1 && + echo A > afile && + hg add afile && + hg ci -m "origin" && + + echo B > afile && + hg ci -m "A->B" && + + hg up -r0 && + echo C > afile && + hg ci -m "A->C" && + + hg merge -r1 || true && + echo B > afile && + hg resolve -m afile && + hg ci -m "merge to B" + ) && + + for x in hg git; do + git_clone_$x hgrepo1 gitrepo-$x && + hg_clone_$x gitrepo-$x hgrepo2-$x && + hg_log hgrepo2-$x > hg-log-$x && + git_log gitrepo-$x > git-log-$x + done && + + test_cmp hg-log-hg hg-log-git && + test_cmp git-log-hg git-log-git +' + +test_expect_success 'converged merge' ' + mkdir -p tmp && cd tmp && + test_when_finished "cd .. && rm -rf tmp" && + + ( + hg init hgrepo1 && + cd hgrepo1 && + echo A > afile && + hg add afile && + hg ci -m "origin" && + + echo B > afile && + hg ci -m "A->B" && + + echo C > afile && + hg ci -m "B->C" && + + hg up -r0 && + echo C > afile && + hg ci -m "A->C" && + + hg merge -r2 || true && + hg ci -m "merge" + ) && + + for x in hg git; do + git_clone_$x hgrepo1 gitrepo-$x && + hg_clone_$x gitrepo-$x hgrepo2-$x && + hg_log hgrepo2-$x > hg-log-$x && + git_log gitrepo-$x > git-log-$x + done && + + test_cmp hg-log-hg hg-log-git && + test_cmp git-log-hg git-log-git +' + +test_expect_success 'encoding' ' + mkdir -p tmp && cd tmp && + test_when_finished "cd .. && rm -rf tmp" && + + ( + git init -q gitrepo && + cd gitrepo && + + echo alpha > alpha && + git add alpha && + git commit -m "add älphà" && + + export GIT_AUTHOR_NAME="tést èncödîng" && + echo beta > beta && + git add beta && + git commit -m "add beta" && + + echo gamma > gamma && + git add gamma && + git commit -m "add gämmâ" && + + : TODO git config i18n.commitencoding latin-1 && + echo delta > delta && + git add delta && + git commit -m "add déltà" + ) && + + for x in hg git; do + hg_clone_$x gitrepo hgrepo-$x && + git_clone_$x hgrepo-$x gitrepo2-$x && + + HGENCODING=utf-8 hg_log hgrepo-$x > hg-log-$x && + git_log gitrepo2-$x > git-log-$x + done && + + test_cmp hg-log-hg hg-log-git && + test_cmp git-log-hg git-log-git +' + +test_expect_success 'file removal' ' + mkdir -p tmp && cd tmp && + test_when_finished "cd .. && rm -rf tmp" && + + ( + git init -q gitrepo && + cd gitrepo && + echo alpha > alpha && + git add alpha && + git commit -m "add alpha" && + echo beta > beta && + git add beta && + git commit -m "add beta" + mkdir foo && + echo blah > foo/bar && + git add foo && + git commit -m "add foo" && + git rm alpha && + git commit -m "remove alpha" && + git rm foo/bar && + git commit -m "remove foo/bar" + ) && + + for x in hg git; do + ( + hg_clone_$x gitrepo hgrepo-$x && + cd hgrepo-$x && + hg_log . && + hg manifest -r 3 && + hg manifest + ) > output-$x && + + git_clone_$x hgrepo-$x gitrepo2-$x && + git_log gitrepo2-$x > log-$x + done && + + test_cmp output-hg output-git && + test_cmp log-hg log-git +' + +test_expect_success 'git tags' ' + mkdir -p tmp && cd tmp && + test_when_finished "cd .. && rm -rf tmp" && + + ( + git init -q gitrepo && + cd gitrepo && + git config receive.denyCurrentBranch ignore && + echo alpha > alpha && + git add alpha && + git commit -m "add alpha" && + git tag alpha && + + echo beta > beta && + git add beta && + git commit -m "add beta" && + git tag -a -m "added tag beta" beta + ) && + + for x in hg git; do + hg_clone_$x gitrepo hgrepo-$x && + hg_log hgrepo-$x > log-$x + done && + + test_cmp log-hg log-git +' + +test_expect_success 'hg author' ' + mkdir -p tmp && cd tmp && + test_when_finished "cd .. && rm -rf tmp" && + + for x in hg git; do + ( + git init -q gitrepo-$x && + cd gitrepo-$x && + + echo alpha > alpha && + git add alpha && + git commit -m "add alpha" && + git checkout -q -b not-master + ) && + + ( + hg_clone_$x gitrepo-$x hgrepo-$x && + cd hgrepo-$x && + + hg co master && + echo beta > beta && + hg add beta && + hg commit -u "test" -m "add beta" && + + echo gamma >> beta && + hg commit -u "test (comment)" -m "modify beta" && + + echo gamma > gamma && + hg add gamma && + hg commit -u "" -m "add gamma" && + + echo delta > delta && + hg add delta && + hg commit -u "name" -m "add delta" && + + echo epsilon > epsilon && + hg add epsilon && + hg commit -u "name zeta && + hg add zeta && + hg commit -u " test " -m "add zeta" && + + echo eta > eta && + hg add eta && + hg commit -u "test < test@example.com >" -m "add eta" && + + echo theta > theta && + hg add theta && + hg commit -u "test >test@example.com>" -m "add theta" && + + echo iota > iota && + hg add iota && + hg commit -u "test example com>" -m "add iota" + ) && + + hg_push_$x hgrepo-$x gitrepo-$x && + hg_clone_$x gitrepo-$x hgrepo2-$x && + + hg_log hgrepo2-$x > hg-log-$x && + git_log gitrepo-$x > git-log-$x + done && + + test_cmp git-log-hg git-log-git && + + test_cmp hg-log-hg hg-log-git && + test_cmp git-log-hg git-log-git +' + +test_expect_success 'hg branch' ' + mkdir -p tmp && cd tmp && + test_when_finished "cd .. && rm -rf tmp" && + + for x in hg git; do + ( + git init -q gitrepo-$x && + cd gitrepo-$x && + + echo alpha > alpha && + git add alpha && + git commit -q -m "add alpha" && + git checkout -q -b not-master + ) && + + ( + hg_clone_$x gitrepo-$x hgrepo-$x && + + cd hgrepo-$x && + hg -q co master && + hg mv alpha beta && + hg -q commit -m "rename alpha to beta" && + hg branch gamma | grep -v "permanent and global" && + hg -q commit -m "started branch gamma" + ) && + + hg_push_$x hgrepo-$x gitrepo-$x && + hg_clone_$x gitrepo-$x hgrepo2-$x && + + hg_log hgrepo2-$x > hg-log-$x && + git_log gitrepo-$x > git-log-$x + done && + + test_cmp hg-log-hg hg-log-git && + test_cmp git-log-hg git-log-git +' + +test_expect_success 'hg tags' ' + mkdir -p tmp && cd tmp && + test_when_finished "cd .. && rm -rf tmp" && + + for x in hg git; do + ( + git init -q gitrepo-$x && + cd gitrepo-$x && + + echo alpha > alpha && + git add alpha && + git commit -m "add alpha" && + git checkout -q -b not-master + ) && + + ( + hg_clone_$x gitrepo-$x hgrepo-$x && + + cd hgrepo-$x && + hg co master && + hg tag alpha + ) && + + hg_push_$x hgrepo-$x gitrepo-$x && + hg_clone_$x gitrepo-$x hgrepo2-$x && + + ( + git --git-dir=gitrepo-$x/.git tag -l && + hg_log hgrepo2-$x && + cat hgrepo2-$x/.hgtags + ) > output-$x + done && + + test_cmp output-hg output-git +' + +test_done diff --git a/contrib/remote-helpers/test-hg.sh b/contrib/remote-helpers/test-hg.sh new file mode 100755 index 0000000000..5f81dfae6c --- /dev/null +++ b/contrib/remote-helpers/test-hg.sh @@ -0,0 +1,121 @@ +#!/bin/sh +# +# Copyright (c) 2012 Felipe Contreras +# +# Base commands from hg-git tests: +# https://bitbucket.org/durin42/hg-git/src +# + +test_description='Test remote-hg' + +. ./test-lib.sh + +if ! test_have_prereq PYTHON; then + skip_all='skipping remote-hg tests; python not available' + test_done +fi + +if ! "$PYTHON_PATH" -c 'import mercurial'; then + skip_all='skipping remote-hg tests; mercurial not available' + test_done +fi + +check () { + (cd $1 && + git log --format='%s' -1 && + git symbolic-ref HEAD) > actual && + (echo $2 && + echo "refs/heads/$3") > expected && + test_cmp expected actual +} + +setup () { + ( + echo "[ui]" + echo "username = H G Wells " + ) >> "$HOME"/.hgrc +} + +setup + +test_expect_success 'cloning' ' + test_when_finished "rm -rf gitrepo*" && + + ( + hg init hgrepo && + cd hgrepo && + echo zero > content && + hg add content && + hg commit -m zero + ) && + + git clone "hg::$PWD/hgrepo" gitrepo && + check gitrepo zero master +' + +test_expect_success 'cloning with branches' ' + test_when_finished "rm -rf gitrepo*" && + + ( + cd hgrepo && + hg branch next && + echo next > content && + hg commit -m next + ) && + + git clone "hg::$PWD/hgrepo" gitrepo && + check gitrepo next next && + + (cd hgrepo && hg checkout default) && + + git clone "hg::$PWD/hgrepo" gitrepo2 && + check gitrepo2 zero master +' + +test_expect_success 'cloning with bookmarks' ' + test_when_finished "rm -rf gitrepo*" && + + ( + cd hgrepo && + hg bookmark feature-a && + echo feature-a > content && + hg commit -m feature-a + ) && + + git clone "hg::$PWD/hgrepo" gitrepo && + check gitrepo feature-a feature-a +' + +test_expect_success 'cloning with detached head' ' + test_when_finished "rm -rf gitrepo*" && + + ( + cd hgrepo && + hg update -r 0 + ) && + + git clone "hg::$PWD/hgrepo" gitrepo && + check gitrepo zero master +' + +test_expect_success 'update bookmark' ' + test_when_finished "rm -rf gitrepo*" && + + ( + cd hgrepo && + hg bookmark devel + ) && + + ( + git clone "hg::$PWD/hgrepo" gitrepo && + cd gitrepo && + git checkout devel && + echo devel > content && + git commit -a -m devel && + git push + ) && + + hg -R hgrepo bookmarks | grep "devel\s\+3:" +' + +test_done diff --git a/contrib/svn-fe/svn-fe.c b/contrib/svn-fe/svn-fe.c index 35db24f5ea..f363505abb 100644 --- a/contrib/svn-fe/svn-fe.c +++ b/contrib/svn-fe/svn-fe.c @@ -10,7 +10,8 @@ int main(int argc, char **argv) { if (svndump_init(NULL)) return 1; - svndump_read((argc > 1) ? argv[1] : NULL); + svndump_read((argc > 1) ? argv[1] : NULL, "refs/heads/master", + "refs/notes/svn/revs"); svndump_deinit(); svndump_reset(); return 0; diff --git a/contrib/svn-fe/svnrdump_sim.py b/contrib/svn-fe/svnrdump_sim.py new file mode 100755 index 0000000000..1cfac4a6f8 --- /dev/null +++ b/contrib/svn-fe/svnrdump_sim.py @@ -0,0 +1,53 @@ +#!/usr/bin/python +""" +Simulates svnrdump by replaying an existing dump from a file, taking care +of the specified revision range. +To simulate incremental imports the environment variable SVNRMAX can be set +to the highest revision that should be available. +""" +import sys, os + + +def getrevlimit(): + var = 'SVNRMAX' + if os.environ.has_key(var): + return os.environ[var] + return None + +def writedump(url, lower, upper): + if url.startswith('sim://'): + filename = url[6:] + if filename[-1] == '/': filename = filename[:-1] #remove terminating slash + else: + raise ValueError('sim:// url required') + f = open(filename, 'r'); + state = 'header' + wroterev = False + while(True): + l = f.readline() + if l == '': break + if state == 'header' and l.startswith('Revision-number: '): + state = 'prefix' + if state == 'prefix' and l == 'Revision-number: %s\n' % lower: + state = 'selection' + if not upper == 'HEAD' and state == 'selection' and l == 'Revision-number: %s\n' % upper: + break; + + if state == 'header' or state == 'selection': + if state == 'selection': wroterev = True + sys.stdout.write(l) + return wroterev + +if __name__ == "__main__": + if not (len(sys.argv) in (3, 4, 5)): + print "usage: %s dump URL -rLOWER:UPPER" + sys.exit(1) + if not sys.argv[1] == 'dump': raise NotImplementedError('only "dump" is suppported.') + url = sys.argv[2] + r = ('0', 'HEAD') + if len(sys.argv) == 4 and sys.argv[3][0:2] == '-r': + r = sys.argv[3][2:].lstrip().split(':') + if not getrevlimit() is None: r[1] = getrevlimit() + if writedump(url, r[0], r[1]): ret = 0 + else: ret = 1 + sys.exit(ret) diff --git a/diff.c b/diff.c index 35d3f07385..732d4c2275 100644 --- a/diff.c +++ b/diff.c @@ -15,6 +15,7 @@ #include "sigchain.h" #include "submodule.h" #include "ll-merge.h" +#include "string-list.h" #ifdef NO_FAST_WORKING_DIRECTORY #define FAST_WORKING_DIRECTORY 0 @@ -26,6 +27,7 @@ static int diff_detect_rename_default; static int diff_rename_limit_default = 400; static int diff_suppress_blank_empty; static int diff_use_color_default = -1; +static int diff_context_default = 3; static const char *diff_word_regex_cfg; static const char *external_diff_cmd_cfg; int diff_auto_refresh_index = 1; @@ -68,26 +70,30 @@ static int parse_diff_color_slot(const char *var, int ofs) return -1; } -static int parse_dirstat_params(struct diff_options *options, const char *params, +static int parse_dirstat_params(struct diff_options *options, const char *params_string, struct strbuf *errmsg) { - const char *p = params; - int p_len, ret = 0; + char *params_copy = xstrdup(params_string); + struct string_list params = STRING_LIST_INIT_NODUP; + int ret = 0; + int i; - while (*p) { - p_len = strchrnul(p, ',') - p; - if (!memcmp(p, "changes", p_len)) { + if (*params_copy) + string_list_split_in_place(¶ms, params_copy, ',', -1); + for (i = 0; i < params.nr; i++) { + const char *p = params.items[i].string; + if (!strcmp(p, "changes")) { DIFF_OPT_CLR(options, DIRSTAT_BY_LINE); DIFF_OPT_CLR(options, DIRSTAT_BY_FILE); - } else if (!memcmp(p, "lines", p_len)) { + } else if (!strcmp(p, "lines")) { DIFF_OPT_SET(options, DIRSTAT_BY_LINE); DIFF_OPT_CLR(options, DIRSTAT_BY_FILE); - } else if (!memcmp(p, "files", p_len)) { + } else if (!strcmp(p, "files")) { DIFF_OPT_CLR(options, DIRSTAT_BY_LINE); DIFF_OPT_SET(options, DIRSTAT_BY_FILE); - } else if (!memcmp(p, "noncumulative", p_len)) { + } else if (!strcmp(p, "noncumulative")) { DIFF_OPT_CLR(options, DIRSTAT_CUMULATIVE); - } else if (!memcmp(p, "cumulative", p_len)) { + } else if (!strcmp(p, "cumulative")) { DIFF_OPT_SET(options, DIRSTAT_CUMULATIVE); } else if (isdigit(*p)) { char *end; @@ -99,27 +105,35 @@ static int parse_dirstat_params(struct diff_options *options, const char *params while (isdigit(*++end)) ; /* nothing */ } - if (end - p == p_len) + if (!*end) options->dirstat_permille = permille; else { - strbuf_addf(errmsg, _(" Failed to parse dirstat cut-off percentage '%.*s'\n"), - p_len, p); + strbuf_addf(errmsg, _(" Failed to parse dirstat cut-off percentage '%s'\n"), + p); ret++; } } else { - strbuf_addf(errmsg, _(" Unknown dirstat parameter '%.*s'\n"), - p_len, p); + strbuf_addf(errmsg, _(" Unknown dirstat parameter '%s'\n"), p); ret++; } - p += p_len; - - if (*p) - p++; /* more parameters, swallow separator */ } + string_list_clear(¶ms, 0); + free(params_copy); return ret; } +static int parse_submodule_params(struct diff_options *options, const char *value) +{ + if (!strcmp(value, "log")) + DIFF_OPT_SET(options, SUBMODULE_LOG); + else if (!strcmp(value, "short")) + DIFF_OPT_CLR(options, SUBMODULE_LOG); + else + return -1; + return 0; +} + static int git_config_rename(const char *var, const char *value) { if (!value) @@ -141,6 +155,12 @@ int git_diff_ui_config(const char *var, const char *value, void *cb) diff_use_color_default = git_config_colorbool(var, value); return 0; } + if (!strcmp(var, "diff.context")) { + diff_context_default = git_config_int(var, value); + if (diff_context_default < 0) + return -1; + return 0; + } if (!strcmp(var, "diff.renames")) { diff_detect_rename_default = git_config_rename(var, value); return 0; @@ -169,6 +189,13 @@ int git_diff_ui_config(const char *var, const char *value, void *cb) if (!strcmp(var, "diff.ignoresubmodules")) handle_ignore_submodules_arg(&default_diff_options, value); + if (!strcmp(var, "diff.submodule")) { + if (parse_submodule_params(&default_diff_options, value)) + warning(_("Unknown value for 'diff.submodule' config variable: '%s'"), + value); + return 0; + } + if (git_color_config(var, value, cb) < 0) return -1; @@ -1300,6 +1327,7 @@ struct diffstat_t { unsigned is_unmerged:1; unsigned is_binary:1; unsigned is_renamed:1; + unsigned is_interesting:1; uintmax_t added, deleted; } **files; }; @@ -1469,8 +1497,8 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) for (i = 0; (i < count) && (i < data->nr); i++) { struct diffstat_file *file = data->files[i]; uintmax_t change = file->added + file->deleted; - if (!data->files[i]->is_renamed && - (change == 0)) { + + if (!file->is_interesting && (change == 0)) { count++; /* not shown == room for one more */ continue; } @@ -1497,7 +1525,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) if (max_change < change) max_change = change; } - count = i; /* min(count, data->nr) */ + count = i; /* where we can stop scanning in data->files[] */ /* * We have width = stat_width or term_columns() columns total. @@ -1585,16 +1613,15 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) */ for (i = 0; i < count; i++) { const char *prefix = ""; - char *name = data->files[i]->print_name; - uintmax_t added = data->files[i]->added; - uintmax_t deleted = data->files[i]->deleted; + struct diffstat_file *file = data->files[i]; + char *name = file->print_name; + uintmax_t added = file->added; + uintmax_t deleted = file->deleted; int name_len; - if (!data->files[i]->is_renamed && - (added + deleted == 0)) { - total_files--; + if (!file->is_interesting && (added + deleted == 0)) continue; - } + /* * "scale" the filename */ @@ -1610,7 +1637,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) name = slash; } - if (data->files[i]->is_binary) { + if (file->is_binary) { fprintf(options->file, "%s", line_prefix); show_name(options->file, prefix, name, len); fprintf(options->file, " %*s", number_width, "Bin"); @@ -1627,7 +1654,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) fprintf(options->file, "\n"); continue; } - else if (data->files[i]->is_unmerged) { + else if (file->is_unmerged) { fprintf(options->file, "%s", line_prefix); show_name(options->file, prefix, name, len); fprintf(options->file, " Unmerged\n"); @@ -1639,8 +1666,6 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) */ add = added; del = deleted; - adds += add; - dels += del; if (graph_width <= max_change) { int total = add + del; @@ -1666,16 +1691,24 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) show_graph(options->file, '-', del, del_c, reset); fprintf(options->file, "\n"); } - for (i = count; i < data->nr; i++) { - uintmax_t added = data->files[i]->added; - uintmax_t deleted = data->files[i]->deleted; - if (!data->files[i]->is_renamed && - (added + deleted == 0)) { + + for (i = 0; i < data->nr; i++) { + struct diffstat_file *file = data->files[i]; + uintmax_t added = file->added; + uintmax_t deleted = file->deleted; + + if (file->is_unmerged || + (!file->is_interesting && (added + deleted == 0))) { total_files--; continue; } - adds += added; - dels += deleted; + + if (!file->is_binary) { + adds += added; + dels += deleted; + } + if (i < count) + continue; if (!extra_shown) fprintf(options->file, "%s ...\n", line_prefix); extra_shown = 1; @@ -1695,9 +1728,8 @@ static void show_shortstats(struct diffstat_t *data, struct diff_options *option int added = data->files[i]->added; int deleted= data->files[i]->deleted; - if (data->files[i]->is_unmerged) - continue; - if (!data->files[i]->is_renamed && (added + deleted == 0)) { + if (data->files[i]->is_unmerged || + (!data->files[i]->is_interesting && (added + deleted == 0))) { total_files--; } else if (!data->files[i]->is_binary) { /* don't count bytes */ adds += added; @@ -2213,7 +2245,7 @@ static void builtin_diff(const char *name_a, mmfile_t mf1, mf2; const char *lbl[2]; char *a_one, *b_two; - const char *set = diff_get_color_opt(o, DIFF_METAINFO); + const char *meta = diff_get_color_opt(o, DIFF_METAINFO); const char *reset = diff_get_color_opt(o, DIFF_RESET); const char *a_prefix, *b_prefix; struct userdiff_driver *textconv_one = NULL; @@ -2234,7 +2266,7 @@ static void builtin_diff(const char *name_a, const char *add = diff_get_color_opt(o, DIFF_FILE_NEW); show_submodule_summary(o->file, one ? one->path : two->path, one->sha1, two->sha1, two->dirty_submodule, - del, add, reset); + meta, del, add, reset); return; } @@ -2260,24 +2292,24 @@ static void builtin_diff(const char *name_a, b_two = quote_two(b_prefix, name_b + (*name_b == '/')); lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null"; lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null"; - strbuf_addf(&header, "%s%sdiff --git %s %s%s\n", line_prefix, set, a_one, b_two, reset); + strbuf_addf(&header, "%s%sdiff --git %s %s%s\n", line_prefix, meta, a_one, b_two, reset); if (lbl[0][0] == '/') { /* /dev/null */ - strbuf_addf(&header, "%s%snew file mode %06o%s\n", line_prefix, set, two->mode, reset); + strbuf_addf(&header, "%s%snew file mode %06o%s\n", line_prefix, meta, two->mode, reset); if (xfrm_msg) strbuf_addstr(&header, xfrm_msg); must_show_header = 1; } else if (lbl[1][0] == '/') { - strbuf_addf(&header, "%s%sdeleted file mode %06o%s\n", line_prefix, set, one->mode, reset); + strbuf_addf(&header, "%s%sdeleted file mode %06o%s\n", line_prefix, meta, one->mode, reset); if (xfrm_msg) strbuf_addstr(&header, xfrm_msg); must_show_header = 1; } else { if (one->mode != two->mode) { - strbuf_addf(&header, "%s%sold mode %06o%s\n", line_prefix, set, one->mode, reset); - strbuf_addf(&header, "%s%snew mode %06o%s\n", line_prefix, set, two->mode, reset); + strbuf_addf(&header, "%s%sold mode %06o%s\n", line_prefix, meta, one->mode, reset); + strbuf_addf(&header, "%s%snew mode %06o%s\n", line_prefix, meta, two->mode, reset); must_show_header = 1; } if (xfrm_msg) @@ -2397,13 +2429,20 @@ static void builtin_diffstat(const char *name_a, const char *name_b, struct diff_filespec *two, struct diffstat_t *diffstat, struct diff_options *o, - int complete_rewrite) + struct diff_filepair *p) { mmfile_t mf1, mf2; struct diffstat_file *data; int same_contents; + int complete_rewrite = 0; + + if (!DIFF_PAIR_UNMERGED(p)) { + if (p->status == DIFF_STATUS_MODIFIED && p->score) + complete_rewrite = 1; + } data = diffstat_add(diffstat, name_a, name_b); + data->is_interesting = p->status != DIFF_STATUS_UNKNOWN; if (!one || !two) { data->is_unmerged = 1; @@ -3114,11 +3153,10 @@ static void run_diffstat(struct diff_filepair *p, struct diff_options *o, { const char *name; const char *other; - int complete_rewrite = 0; if (DIFF_PAIR_UNMERGED(p)) { /* unmerged */ - builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat, o, 0); + builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat, o, p); return; } @@ -3131,9 +3169,7 @@ static void run_diffstat(struct diff_filepair *p, struct diff_options *o, diff_fill_sha1_info(p->one); diff_fill_sha1_info(p->two); - if (p->status == DIFF_STATUS_MODIFIED && p->score) - complete_rewrite = 1; - builtin_diffstat(name, other, p->one, p->two, diffstat, o, complete_rewrite); + builtin_diffstat(name, other, p->one, p->two, diffstat, o, p); } static void run_checkdiff(struct diff_filepair *p, struct diff_options *o) @@ -3170,7 +3206,7 @@ void diff_setup(struct diff_options *options) options->break_opt = -1; options->rename_limit = -1; options->dirstat_permille = diff_dirstat_permille_default; - options->context = 3; + options->context = diff_context_default; DIFF_OPT_SET(options, RENAME_EMPTY); options->change = diff_change; @@ -3466,6 +3502,14 @@ static int parse_dirstat_opt(struct diff_options *options, const char *params) return 1; } +static int parse_submodule_opt(struct diff_options *options, const char *value) +{ + if (parse_submodule_params(options, value)) + die(_("Failed to parse --submodule option parameter: '%s'"), + value); + return 1; +} + int diff_opt_parse(struct diff_options *options, const char **av, int ac) { const char *arg = av[0]; @@ -3646,10 +3690,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) handle_ignore_submodules_arg(options, arg + 20); } else if (!strcmp(arg, "--submodule")) DIFF_OPT_SET(options, SUBMODULE_LOG); - else if (!prefixcmp(arg, "--submodule=")) { - if (!strcmp(arg + 12, "log")) - DIFF_OPT_SET(options, SUBMODULE_LOG); - } + else if (!prefixcmp(arg, "--submodule=")) + return parse_submodule_opt(options, arg + 12); /* misc options */ else if (!strcmp(arg, "-z")) @@ -4871,3 +4913,19 @@ size_t fill_textconv(struct userdiff_driver *driver, return size; } + +void setup_diff_pager(struct diff_options *opt) +{ + /* + * If the user asked for our exit code, then either they want --quiet + * or --exit-code. We should definitely not bother with a pager in the + * former case, as we will generate no output. Since we still properly + * report our exit code even when a pager is run, we _could_ run a + * pager with --exit-code. But since we have not done so historically, + * and because it is easy to find people oneline advising "git diff + * --exit-code" in hooks and other scripts, we do not do so. + */ + if (!DIFF_OPT_TST(opt, EXIT_WITH_STATUS) && + check_pager_config("diff") != 0) + setup_pager(); +} diff --git a/diff.h b/diff.h index a658f85f2b..a47bae48d5 100644 --- a/diff.h +++ b/diff.h @@ -335,5 +335,6 @@ extern int parse_rename_score(const char **cp_p); extern int print_stat_summary(FILE *fp, int files, int insertions, int deletions); +extern void setup_diff_pager(struct diff_options *); #endif /* DIFF_H */ diff --git a/diffcore-pickaxe.c b/diffcore-pickaxe.c index ed23eb4bdd..b097fa7661 100644 --- a/diffcore-pickaxe.c +++ b/diffcore-pickaxe.c @@ -104,10 +104,10 @@ static int diff_grep(struct diff_filepair *p, struct diff_options *o, if (!mf2.ptr) return 0; /* ignore unmerged */ /* created "two" -- does it have what we are looking for? */ - hit = !regexec(regexp, p->two->data, 1, ®match, 0); + hit = !regexec(regexp, mf2.ptr, 1, ®match, 0); } else if (!mf2.ptr) { /* removed "one" -- did it have what we are looking for? */ - hit = !regexec(regexp, p->one->data, 1, ®match, 0); + hit = !regexec(regexp, mf1.ptr, 1, ®match, 0); } else { /* * We have both sides; need to run textual diff and see if @@ -157,19 +157,15 @@ static void diffcore_pickaxe_grep(struct diff_options *o) return; } -static unsigned int contains(struct diff_filespec *one, struct diff_options *o, +static unsigned int contains(mmfile_t *mf, struct diff_options *o, regex_t *regexp, kwset_t kws) { unsigned int cnt; unsigned long sz; const char *data; - if (!o->pickaxe[0]) - return 0; - if (diff_populate_filespec(one, 0)) - return 0; - sz = one->size; - data = one->data; + sz = mf->size; + data = mf->ptr; cnt = 0; if (regexp) { @@ -199,26 +195,53 @@ static unsigned int contains(struct diff_filespec *one, struct diff_options *o, cnt++; } } - diff_free_filespec_data(one); return cnt; } static int has_changes(struct diff_filepair *p, struct diff_options *o, regex_t *regexp, kwset_t kws) { - if (!DIFF_FILE_VALID(p->one)) { - if (!DIFF_FILE_VALID(p->two)) - return 0; /* ignore unmerged */ + struct userdiff_driver *textconv_one = get_textconv(p->one); + struct userdiff_driver *textconv_two = get_textconv(p->two); + mmfile_t mf1, mf2; + int ret; + + if (!o->pickaxe[0]) + return 0; + + /* + * If we have an unmodified pair, we know that the count will be the + * same and don't even have to load the blobs. Unless textconv is in + * play, _and_ we are using two different textconv filters (e.g., + * because a pair is an exact rename with different textconv attributes + * for each side, which might generate different content). + */ + if (textconv_one == textconv_two && diff_unmodified_pair(p)) + return 0; + + fill_one(p->one, &mf1, &textconv_one); + fill_one(p->two, &mf2, &textconv_two); + + if (!mf1.ptr) { + if (!mf2.ptr) + ret = 0; /* ignore unmerged */ /* created */ - return contains(p->two, o, regexp, kws) != 0; - } - if (!DIFF_FILE_VALID(p->two)) - return contains(p->one, o, regexp, kws) != 0; - if (!diff_unmodified_pair(p)) { - return contains(p->one, o, regexp, kws) != - contains(p->two, o, regexp, kws); + ret = contains(&mf2, o, regexp, kws) != 0; } - return 0; + else if (!mf2.ptr) /* removed */ + ret = contains(&mf1, o, regexp, kws) != 0; + else + ret = contains(&mf1, o, regexp, kws) != + contains(&mf2, o, regexp, kws); + + if (textconv_one) + free(mf1.ptr); + if (textconv_two) + free(mf2.ptr); + diff_free_filespec_data(p->one); + diff_free_filespec_data(p->two); + + return ret; } static void diffcore_pickaxe_count(struct diff_options *o) diff --git a/dir.c b/dir.c index 486833986e..5a83aa7897 100644 --- a/dir.c +++ b/dir.c @@ -308,42 +308,69 @@ static int no_wildcard(const char *string) return string[simple_length(string)] == '\0'; } +void parse_exclude_pattern(const char **pattern, + int *patternlen, + int *flags, + int *nowildcardlen) +{ + const char *p = *pattern; + size_t i, len; + + *flags = 0; + if (*p == '!') { + *flags |= EXC_FLAG_NEGATIVE; + p++; + } + len = strlen(p); + if (len && p[len - 1] == '/') { + len--; + *flags |= EXC_FLAG_MUSTBEDIR; + } + for (i = 0; i < len; i++) { + if (p[i] == '/') + break; + } + if (i == len) + *flags |= EXC_FLAG_NODIR; + *nowildcardlen = simple_length(p); + /* + * we should have excluded the trailing slash from 'p' too, + * but that's one more allocation. Instead just make sure + * nowildcardlen does not exceed real patternlen + */ + if (*nowildcardlen > len) + *nowildcardlen = len; + if (*p == '*' && no_wildcard(p + 1)) + *flags |= EXC_FLAG_ENDSWITH; + *pattern = p; + *patternlen = len; +} + void add_exclude(const char *string, const char *base, int baselen, struct exclude_list *which) { struct exclude *x; - size_t len; - int to_exclude = 1; - int flags = 0; + int patternlen; + int flags; + int nowildcardlen; - if (*string == '!') { - to_exclude = 0; - string++; - } - len = strlen(string); - if (len && string[len - 1] == '/') { + parse_exclude_pattern(&string, &patternlen, &flags, &nowildcardlen); + if (flags & EXC_FLAG_MUSTBEDIR) { char *s; - x = xmalloc(sizeof(*x) + len); + x = xmalloc(sizeof(*x) + patternlen + 1); s = (char *)(x+1); - memcpy(s, string, len - 1); - s[len - 1] = '\0'; - string = s; + memcpy(s, string, patternlen); + s[patternlen] = '\0'; x->pattern = s; - flags = EXC_FLAG_MUSTBEDIR; } else { x = xmalloc(sizeof(*x)); x->pattern = string; } - x->to_exclude = to_exclude; - x->patternlen = strlen(string); + x->patternlen = patternlen; + x->nowildcardlen = nowildcardlen; x->base = base; x->baselen = baselen; x->flags = flags; - if (!strchr(string, '/')) - x->flags |= EXC_FLAG_NODIR; - x->nowildcardlen = simple_length(string); - if (*string == '*' && no_wildcard(string+1)) - x->flags |= EXC_FLAG_ENDSWITH; ALLOC_GROW(which->excludes, which->nr + 1, which->alloc); which->excludes[which->nr++] = x; } @@ -505,6 +532,72 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen) dir->basebuf[baselen] = '\0'; } +int match_basename(const char *basename, int basenamelen, + const char *pattern, int prefix, int patternlen, + int flags) +{ + if (prefix == patternlen) { + if (!strcmp_icase(pattern, basename)) + return 1; + } else if (flags & EXC_FLAG_ENDSWITH) { + if (patternlen - 1 <= basenamelen && + !strcmp_icase(pattern + 1, + basename + basenamelen - patternlen + 1)) + return 1; + } else { + if (fnmatch_icase(pattern, basename, 0) == 0) + return 1; + } + return 0; +} + +int match_pathname(const char *pathname, int pathlen, + const char *base, int baselen, + const char *pattern, int prefix, int patternlen, + int flags) +{ + const char *name; + int namelen; + + /* + * match with FNM_PATHNAME; the pattern has base implicitly + * in front of it. + */ + if (*pattern == '/') { + pattern++; + prefix--; + } + + /* + * baselen does not count the trailing slash. base[] may or + * may not end with a trailing slash though. + */ + if (pathlen < baselen + 1 || + (baselen && pathname[baselen] != '/') || + strncmp_icase(pathname, base, baselen)) + return 0; + + namelen = baselen ? pathlen - baselen - 1 : pathlen; + name = pathname + pathlen - namelen; + + if (prefix) { + /* + * if the non-wildcard part is longer than the + * remaining pathname, surely it cannot match. + */ + if (prefix > namelen) + return 0; + + if (strncmp_icase(pattern, name, prefix)) + return 0; + pattern += prefix; + name += prefix; + namelen -= prefix; + } + + return fnmatch_icase(pattern, name, FNM_PATHNAME) == 0; +} + /* Scan the list and let the last match determine the fate. * Return 1 for exclude, 0 for include and -1 for undecided. */ @@ -519,9 +612,9 @@ int excluded_from_list(const char *pathname, for (i = el->nr - 1; 0 <= i; i--) { struct exclude *x = el->excludes[i]; - const char *name, *exclude = x->pattern; - int to_exclude = x->to_exclude; - int namelen, prefix = x->nowildcardlen; + const char *exclude = x->pattern; + int to_exclude = x->flags & EXC_FLAG_NEGATIVE ? 0 : 1; + int prefix = x->nowildcardlen; if (x->flags & EXC_FLAG_MUSTBEDIR) { if (*dtype == DT_UNKNOWN) @@ -531,51 +624,18 @@ int excluded_from_list(const char *pathname, } if (x->flags & EXC_FLAG_NODIR) { - /* match basename */ - if (prefix == x->patternlen) { - if (!strcmp_icase(exclude, basename)) - return to_exclude; - } else if (x->flags & EXC_FLAG_ENDSWITH) { - if (x->patternlen - 1 <= pathlen && - !strcmp_icase(exclude + 1, pathname + pathlen - x->patternlen + 1)) - return to_exclude; - } else { - if (fnmatch_icase(exclude, basename, 0) == 0) - return to_exclude; - } - continue; - } - - /* match with FNM_PATHNAME: - * exclude has base (baselen long) implicitly in front of it. - */ - if (*exclude == '/') { - exclude++; - prefix--; - } - - if (pathlen < x->baselen || - (x->baselen && pathname[x->baselen-1] != '/') || - strncmp_icase(pathname, x->base, x->baselen)) + if (match_basename(basename, + pathlen - (basename - pathname), + exclude, prefix, x->patternlen, + x->flags)) + return to_exclude; continue; - - namelen = x->baselen ? pathlen - x->baselen : pathlen; - name = pathname + pathlen - namelen; - - /* if the non-wildcard part is longer than the - remaining pathname, surely it cannot match */ - if (prefix > namelen) - continue; - - if (prefix) { - if (strncmp_icase(exclude, name, prefix)) - continue; - exclude += prefix; - name += prefix; - namelen -= prefix; } - if (!namelen || !fnmatch_icase(exclude, name, FNM_PATHNAME)) + assert(x->baselen == 0 || x->base[x->baselen - 1] == '/'); + if (match_pathname(pathname, pathlen, + x->base, x->baselen ? x->baselen - 1 : 0, + exclude, prefix, x->patternlen, x->flags)) return to_exclude; } return -1; /* undecided */ diff --git a/dir.h b/dir.h index 893465a1e8..f5c89e3b80 100644 --- a/dir.h +++ b/dir.h @@ -11,6 +11,7 @@ struct dir_entry { #define EXC_FLAG_NODIR 1 #define EXC_FLAG_ENDSWITH 4 #define EXC_FLAG_MUSTBEDIR 8 +#define EXC_FLAG_NEGATIVE 16 struct exclude_list { int nr; @@ -21,7 +22,6 @@ struct exclude_list { int nowildcardlen; const char *base; int baselen; - int to_exclude; int flags; } **excludes; }; @@ -80,6 +80,16 @@ extern int excluded_from_list(const char *pathname, int pathlen, const char *bas int *dtype, struct exclude_list *el); struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len); +/* + * these implement the matching logic for dir.c:excluded_from_list and + * attr.c:path_matches() + */ +extern int match_basename(const char *, int, + const char *, int, int, int); +extern int match_pathname(const char *, int, + const char *, int, + const char *, int, int, int); + /* * The excluded() API is meant for callers that check each level of leading * directory hierarchies with excluded() to avoid recursing into excluded @@ -97,6 +107,7 @@ extern int path_excluded(struct path_exclude_check *, const char *, int namelen, extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen, char **buf_p, struct exclude_list *which, int check_index); extern void add_excludes_from_file(struct dir_struct *, const char *fname); +extern void parse_exclude_pattern(const char **string, int *patternlen, int *flags, int *nowildcardlen); extern void add_exclude(const char *string, const char *base, int baselen, struct exclude_list *which); extern void free_excludes(struct exclude_list *el); diff --git a/fetch-pack.c b/fetch-pack.c new file mode 100644 index 0000000000..099ff4ddff --- /dev/null +++ b/fetch-pack.c @@ -0,0 +1,951 @@ +#include "cache.h" +#include "refs.h" +#include "pkt-line.h" +#include "commit.h" +#include "tag.h" +#include "exec_cmd.h" +#include "pack.h" +#include "sideband.h" +#include "fetch-pack.h" +#include "remote.h" +#include "run-command.h" +#include "transport.h" +#include "version.h" + +static int transfer_unpack_limit = -1; +static int fetch_unpack_limit = -1; +static int unpack_limit = 100; +static int prefer_ofs_delta = 1; +static int no_done; +static int fetch_fsck_objects = -1; +static int transfer_fsck_objects = -1; +static int agent_supported; + +#define COMPLETE (1U << 0) +#define COMMON (1U << 1) +#define COMMON_REF (1U << 2) +#define SEEN (1U << 3) +#define POPPED (1U << 4) + +static int marked; + +/* + * After sending this many "have"s if we do not get any new ACK , we + * give up traversing our history. + */ +#define MAX_IN_VAIN 256 + +static struct commit_list *rev_list; +static int non_common_revs, multi_ack, use_sideband; + +static void rev_list_push(struct commit *commit, int mark) +{ + if (!(commit->object.flags & mark)) { + commit->object.flags |= mark; + + if (!(commit->object.parsed)) + if (parse_commit(commit)) + return; + + commit_list_insert_by_date(commit, &rev_list); + + if (!(commit->object.flags & COMMON)) + non_common_revs++; + } +} + +static int rev_list_insert_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) +{ + struct object *o = deref_tag(parse_object(sha1), refname, 0); + + if (o && o->type == OBJ_COMMIT) + rev_list_push((struct commit *)o, SEEN); + + return 0; +} + +static int clear_marks(const char *refname, const unsigned char *sha1, int flag, void *cb_data) +{ + struct object *o = deref_tag(parse_object(sha1), refname, 0); + + if (o && o->type == OBJ_COMMIT) + clear_commit_marks((struct commit *)o, + COMMON | COMMON_REF | SEEN | POPPED); + return 0; +} + +/* + This function marks a rev and its ancestors as common. + In some cases, it is desirable to mark only the ancestors (for example + when only the server does not yet know that they are common). +*/ + +static void mark_common(struct commit *commit, + int ancestors_only, int dont_parse) +{ + if (commit != NULL && !(commit->object.flags & COMMON)) { + struct object *o = (struct object *)commit; + + if (!ancestors_only) + o->flags |= COMMON; + + if (!(o->flags & SEEN)) + rev_list_push(commit, SEEN); + else { + struct commit_list *parents; + + if (!ancestors_only && !(o->flags & POPPED)) + non_common_revs--; + if (!o->parsed && !dont_parse) + if (parse_commit(commit)) + return; + + for (parents = commit->parents; + parents; + parents = parents->next) + mark_common(parents->item, 0, dont_parse); + } + } +} + +/* + Get the next rev to send, ignoring the common. +*/ + +static const unsigned char *get_rev(void) +{ + struct commit *commit = NULL; + + while (commit == NULL) { + unsigned int mark; + struct commit_list *parents; + + if (rev_list == NULL || non_common_revs == 0) + return NULL; + + commit = rev_list->item; + if (!commit->object.parsed) + parse_commit(commit); + parents = commit->parents; + + commit->object.flags |= POPPED; + if (!(commit->object.flags & COMMON)) + non_common_revs--; + + if (commit->object.flags & COMMON) { + /* do not send "have", and ignore ancestors */ + commit = NULL; + mark = COMMON | SEEN; + } else if (commit->object.flags & COMMON_REF) + /* send "have", and ignore ancestors */ + mark = COMMON | SEEN; + else + /* send "have", also for its ancestors */ + mark = SEEN; + + while (parents) { + if (!(parents->item->object.flags & SEEN)) + rev_list_push(parents->item, mark); + if (mark & COMMON) + mark_common(parents->item, 1, 0); + parents = parents->next; + } + + rev_list = rev_list->next; + } + + return commit->object.sha1; +} + +enum ack_type { + NAK = 0, + ACK, + ACK_continue, + ACK_common, + ACK_ready +}; + +static void consume_shallow_list(struct fetch_pack_args *args, int fd) +{ + if (args->stateless_rpc && args->depth > 0) { + /* If we sent a depth we will get back "duplicate" + * shallow and unshallow commands every time there + * is a block of have lines exchanged. + */ + char line[1000]; + while (packet_read_line(fd, line, sizeof(line))) { + if (!prefixcmp(line, "shallow ")) + continue; + if (!prefixcmp(line, "unshallow ")) + continue; + die("git fetch-pack: expected shallow list"); + } + } +} + +struct write_shallow_data { + struct strbuf *out; + int use_pack_protocol; + int count; +}; + +static int write_one_shallow(const struct commit_graft *graft, void *cb_data) +{ + struct write_shallow_data *data = cb_data; + const char *hex = sha1_to_hex(graft->sha1); + data->count++; + if (data->use_pack_protocol) + packet_buf_write(data->out, "shallow %s", hex); + else { + strbuf_addstr(data->out, hex); + strbuf_addch(data->out, '\n'); + } + return 0; +} + +static int write_shallow_commits(struct strbuf *out, int use_pack_protocol) +{ + struct write_shallow_data data; + data.out = out; + data.use_pack_protocol = use_pack_protocol; + data.count = 0; + for_each_commit_graft(write_one_shallow, &data); + return data.count; +} + +static enum ack_type get_ack(int fd, unsigned char *result_sha1) +{ + static char line[1000]; + int len = packet_read_line(fd, line, sizeof(line)); + + if (!len) + die("git fetch-pack: expected ACK/NAK, got EOF"); + if (line[len-1] == '\n') + line[--len] = 0; + if (!strcmp(line, "NAK")) + return NAK; + if (!prefixcmp(line, "ACK ")) { + if (!get_sha1_hex(line+4, result_sha1)) { + if (strstr(line+45, "continue")) + return ACK_continue; + if (strstr(line+45, "common")) + return ACK_common; + if (strstr(line+45, "ready")) + return ACK_ready; + return ACK; + } + } + die("git fetch_pack: expected ACK/NAK, got '%s'", line); +} + +static void send_request(struct fetch_pack_args *args, + int fd, struct strbuf *buf) +{ + if (args->stateless_rpc) { + send_sideband(fd, -1, buf->buf, buf->len, LARGE_PACKET_MAX); + packet_flush(fd); + } else + safe_write(fd, buf->buf, buf->len); +} + +static void insert_one_alternate_ref(const struct ref *ref, void *unused) +{ + rev_list_insert_ref(NULL, ref->old_sha1, 0, NULL); +} + +#define INITIAL_FLUSH 16 +#define PIPESAFE_FLUSH 32 +#define LARGE_FLUSH 1024 + +static int next_flush(struct fetch_pack_args *args, int count) +{ + int flush_limit = args->stateless_rpc ? LARGE_FLUSH : PIPESAFE_FLUSH; + + if (count < flush_limit) + count <<= 1; + else + count += flush_limit; + return count; +} + +static int find_common(struct fetch_pack_args *args, + int fd[2], unsigned char *result_sha1, + struct ref *refs) +{ + int fetching; + int count = 0, flushes = 0, flush_at = INITIAL_FLUSH, retval; + const unsigned char *sha1; + unsigned in_vain = 0; + int got_continue = 0; + int got_ready = 0; + struct strbuf req_buf = STRBUF_INIT; + size_t state_len = 0; + + if (args->stateless_rpc && multi_ack == 1) + die("--stateless-rpc requires multi_ack_detailed"); + if (marked) + for_each_ref(clear_marks, NULL); + marked = 1; + + for_each_ref(rev_list_insert_ref, NULL); + for_each_alternate_ref(insert_one_alternate_ref, NULL); + + fetching = 0; + for ( ; refs ; refs = refs->next) { + unsigned char *remote = refs->old_sha1; + const char *remote_hex; + struct object *o; + + /* + * If that object is complete (i.e. it is an ancestor of a + * local ref), we tell them we have it but do not have to + * tell them about its ancestors, which they already know + * about. + * + * We use lookup_object here because we are only + * interested in the case we *know* the object is + * reachable and we have already scanned it. + */ + if (((o = lookup_object(remote)) != NULL) && + (o->flags & COMPLETE)) { + continue; + } + + remote_hex = sha1_to_hex(remote); + if (!fetching) { + struct strbuf c = STRBUF_INIT; + if (multi_ack == 2) strbuf_addstr(&c, " multi_ack_detailed"); + if (multi_ack == 1) strbuf_addstr(&c, " multi_ack"); + if (no_done) strbuf_addstr(&c, " no-done"); + if (use_sideband == 2) strbuf_addstr(&c, " side-band-64k"); + if (use_sideband == 1) strbuf_addstr(&c, " side-band"); + if (args->use_thin_pack) strbuf_addstr(&c, " thin-pack"); + if (args->no_progress) strbuf_addstr(&c, " no-progress"); + if (args->include_tag) strbuf_addstr(&c, " include-tag"); + if (prefer_ofs_delta) strbuf_addstr(&c, " ofs-delta"); + if (agent_supported) strbuf_addf(&c, " agent=%s", + git_user_agent_sanitized()); + packet_buf_write(&req_buf, "want %s%s\n", remote_hex, c.buf); + strbuf_release(&c); + } else + packet_buf_write(&req_buf, "want %s\n", remote_hex); + fetching++; + } + + if (!fetching) { + strbuf_release(&req_buf); + packet_flush(fd[1]); + return 1; + } + + if (is_repository_shallow()) + write_shallow_commits(&req_buf, 1); + if (args->depth > 0) + packet_buf_write(&req_buf, "deepen %d", args->depth); + packet_buf_flush(&req_buf); + state_len = req_buf.len; + + if (args->depth > 0) { + char line[1024]; + unsigned char sha1[20]; + + send_request(args, fd[1], &req_buf); + while (packet_read_line(fd[0], line, sizeof(line))) { + if (!prefixcmp(line, "shallow ")) { + if (get_sha1_hex(line + 8, sha1)) + die("invalid shallow line: %s", line); + register_shallow(sha1); + continue; + } + if (!prefixcmp(line, "unshallow ")) { + if (get_sha1_hex(line + 10, sha1)) + die("invalid unshallow line: %s", line); + if (!lookup_object(sha1)) + die("object not found: %s", line); + /* make sure that it is parsed as shallow */ + if (!parse_object(sha1)) + die("error in object: %s", line); + if (unregister_shallow(sha1)) + die("no shallow found: %s", line); + continue; + } + die("expected shallow/unshallow, got %s", line); + } + } else if (!args->stateless_rpc) + send_request(args, fd[1], &req_buf); + + if (!args->stateless_rpc) { + /* If we aren't using the stateless-rpc interface + * we don't need to retain the headers. + */ + strbuf_setlen(&req_buf, 0); + state_len = 0; + } + + flushes = 0; + retval = -1; + while ((sha1 = get_rev())) { + packet_buf_write(&req_buf, "have %s\n", sha1_to_hex(sha1)); + if (args->verbose) + fprintf(stderr, "have %s\n", sha1_to_hex(sha1)); + in_vain++; + if (flush_at <= ++count) { + int ack; + + packet_buf_flush(&req_buf); + send_request(args, fd[1], &req_buf); + strbuf_setlen(&req_buf, state_len); + flushes++; + flush_at = next_flush(args, count); + + /* + * We keep one window "ahead" of the other side, and + * will wait for an ACK only on the next one + */ + if (!args->stateless_rpc && count == INITIAL_FLUSH) + continue; + + consume_shallow_list(args, fd[0]); + do { + ack = get_ack(fd[0], result_sha1); + if (args->verbose && ack) + fprintf(stderr, "got ack %d %s\n", ack, + sha1_to_hex(result_sha1)); + switch (ack) { + case ACK: + flushes = 0; + multi_ack = 0; + retval = 0; + goto done; + case ACK_common: + case ACK_ready: + case ACK_continue: { + struct commit *commit = + lookup_commit(result_sha1); + if (!commit) + die("invalid commit %s", sha1_to_hex(result_sha1)); + if (args->stateless_rpc + && ack == ACK_common + && !(commit->object.flags & COMMON)) { + /* We need to replay the have for this object + * on the next RPC request so the peer knows + * it is in common with us. + */ + const char *hex = sha1_to_hex(result_sha1); + packet_buf_write(&req_buf, "have %s\n", hex); + state_len = req_buf.len; + } + mark_common(commit, 0, 1); + retval = 0; + in_vain = 0; + got_continue = 1; + if (ack == ACK_ready) { + rev_list = NULL; + got_ready = 1; + } + break; + } + } + } while (ack); + flushes--; + if (got_continue && MAX_IN_VAIN < in_vain) { + if (args->verbose) + fprintf(stderr, "giving up\n"); + break; /* give up */ + } + } + } +done: + if (!got_ready || !no_done) { + packet_buf_write(&req_buf, "done\n"); + send_request(args, fd[1], &req_buf); + } + if (args->verbose) + fprintf(stderr, "done\n"); + if (retval != 0) { + multi_ack = 0; + flushes++; + } + strbuf_release(&req_buf); + + consume_shallow_list(args, fd[0]); + while (flushes || multi_ack) { + int ack = get_ack(fd[0], result_sha1); + if (ack) { + if (args->verbose) + fprintf(stderr, "got ack (%d) %s\n", ack, + sha1_to_hex(result_sha1)); + if (ack == ACK) + return 0; + multi_ack = 1; + continue; + } + flushes--; + } + /* it is no error to fetch into a completely empty repo */ + return count ? retval : 0; +} + +static struct commit_list *complete; + +static int mark_complete(const char *refname, const unsigned char *sha1, int flag, void *cb_data) +{ + struct object *o = parse_object(sha1); + + while (o && o->type == OBJ_TAG) { + struct tag *t = (struct tag *) o; + if (!t->tagged) + break; /* broken repository */ + o->flags |= COMPLETE; + o = parse_object(t->tagged->sha1); + } + if (o && o->type == OBJ_COMMIT) { + struct commit *commit = (struct commit *)o; + if (!(commit->object.flags & COMPLETE)) { + commit->object.flags |= COMPLETE; + commit_list_insert_by_date(commit, &complete); + } + } + return 0; +} + +static void mark_recent_complete_commits(struct fetch_pack_args *args, + unsigned long cutoff) +{ + while (complete && cutoff <= complete->item->date) { + if (args->verbose) + fprintf(stderr, "Marking %s as complete\n", + sha1_to_hex(complete->item->object.sha1)); + pop_most_recent_commit(&complete, COMPLETE); + } +} + +static int non_matching_ref(struct string_list_item *item, void *unused) +{ + if (item->util) { + item->util = NULL; + return 0; + } + else + return 1; +} + +static void filter_refs(struct fetch_pack_args *args, + struct ref **refs, struct string_list *sought) +{ + struct ref *newlist = NULL; + struct ref **newtail = &newlist; + struct ref *ref, *next; + int sought_pos; + + sought_pos = 0; + for (ref = *refs; ref; ref = next) { + int keep = 0; + next = ref->next; + if (!memcmp(ref->name, "refs/", 5) && + check_refname_format(ref->name + 5, 0)) + ; /* trash */ + else { + while (sought_pos < sought->nr) { + int cmp = strcmp(ref->name, sought->items[sought_pos].string); + if (cmp < 0) + break; /* definitely do not have it */ + else if (cmp == 0) { + keep = 1; /* definitely have it */ + sought->items[sought_pos++].util = "matched"; + break; + } + else + sought_pos++; /* might have it; keep looking */ + } + } + + if (! keep && args->fetch_all && + (!args->depth || prefixcmp(ref->name, "refs/tags/"))) + keep = 1; + + if (keep) { + *newtail = ref; + ref->next = NULL; + newtail = &ref->next; + } else { + free(ref); + } + } + + filter_string_list(sought, 0, non_matching_ref, NULL); + *refs = newlist; +} + +static void mark_alternate_complete(const struct ref *ref, void *unused) +{ + mark_complete(NULL, ref->old_sha1, 0, NULL); +} + +static int everything_local(struct fetch_pack_args *args, + struct ref **refs, struct string_list *sought) +{ + struct ref *ref; + int retval; + unsigned long cutoff = 0; + + save_commit_buffer = 0; + + for (ref = *refs; ref; ref = ref->next) { + struct object *o; + + o = parse_object(ref->old_sha1); + if (!o) + continue; + + /* We already have it -- which may mean that we were + * in sync with the other side at some time after + * that (it is OK if we guess wrong here). + */ + if (o->type == OBJ_COMMIT) { + struct commit *commit = (struct commit *)o; + if (!cutoff || cutoff < commit->date) + cutoff = commit->date; + } + } + + if (!args->depth) { + for_each_ref(mark_complete, NULL); + for_each_alternate_ref(mark_alternate_complete, NULL); + if (cutoff) + mark_recent_complete_commits(args, cutoff); + } + + /* + * Mark all complete remote refs as common refs. + * Don't mark them common yet; the server has to be told so first. + */ + for (ref = *refs; ref; ref = ref->next) { + struct object *o = deref_tag(lookup_object(ref->old_sha1), + NULL, 0); + + if (!o || o->type != OBJ_COMMIT || !(o->flags & COMPLETE)) + continue; + + if (!(o->flags & SEEN)) { + rev_list_push((struct commit *)o, COMMON_REF | SEEN); + + mark_common((struct commit *)o, 1, 1); + } + } + + filter_refs(args, refs, sought); + + for (retval = 1, ref = *refs; ref ; ref = ref->next) { + const unsigned char *remote = ref->old_sha1; + unsigned char local[20]; + struct object *o; + + o = lookup_object(remote); + if (!o || !(o->flags & COMPLETE)) { + retval = 0; + if (!args->verbose) + continue; + fprintf(stderr, + "want %s (%s)\n", sha1_to_hex(remote), + ref->name); + continue; + } + + hashcpy(ref->new_sha1, local); + if (!args->verbose) + continue; + fprintf(stderr, + "already have %s (%s)\n", sha1_to_hex(remote), + ref->name); + } + return retval; +} + +static int sideband_demux(int in, int out, void *data) +{ + int *xd = data; + + int ret = recv_sideband("fetch-pack", xd[0], out); + close(out); + return ret; +} + +static int get_pack(struct fetch_pack_args *args, + int xd[2], char **pack_lockfile) +{ + struct async demux; + const char *argv[20]; + char keep_arg[256]; + char hdr_arg[256]; + const char **av; + int do_keep = args->keep_pack; + struct child_process cmd; + + memset(&demux, 0, sizeof(demux)); + if (use_sideband) { + /* xd[] is talking with upload-pack; subprocess reads from + * xd[0], spits out band#2 to stderr, and feeds us band#1 + * through demux->out. + */ + demux.proc = sideband_demux; + demux.data = xd; + demux.out = -1; + if (start_async(&demux)) + die("fetch-pack: unable to fork off sideband" + " demultiplexer"); + } + else + demux.out = xd[0]; + + memset(&cmd, 0, sizeof(cmd)); + cmd.argv = argv; + av = argv; + *hdr_arg = 0; + if (!args->keep_pack && unpack_limit) { + struct pack_header header; + + if (read_pack_header(demux.out, &header)) + die("protocol error: bad pack header"); + snprintf(hdr_arg, sizeof(hdr_arg), + "--pack_header=%"PRIu32",%"PRIu32, + ntohl(header.hdr_version), ntohl(header.hdr_entries)); + if (ntohl(header.hdr_entries) < unpack_limit) + do_keep = 0; + else + do_keep = 1; + } + + if (do_keep) { + if (pack_lockfile) + cmd.out = -1; + *av++ = "index-pack"; + *av++ = "--stdin"; + if (!args->quiet && !args->no_progress) + *av++ = "-v"; + if (args->use_thin_pack) + *av++ = "--fix-thin"; + if (args->lock_pack || unpack_limit) { + int s = sprintf(keep_arg, + "--keep=fetch-pack %"PRIuMAX " on ", (uintmax_t) getpid()); + if (gethostname(keep_arg + s, sizeof(keep_arg) - s)) + strcpy(keep_arg + s, "localhost"); + *av++ = keep_arg; + } + } + else { + *av++ = "unpack-objects"; + if (args->quiet || args->no_progress) + *av++ = "-q"; + } + if (*hdr_arg) + *av++ = hdr_arg; + if (fetch_fsck_objects >= 0 + ? fetch_fsck_objects + : transfer_fsck_objects >= 0 + ? transfer_fsck_objects + : 0) + *av++ = "--strict"; + *av++ = NULL; + + cmd.in = demux.out; + cmd.git_cmd = 1; + if (start_command(&cmd)) + die("fetch-pack: unable to fork off %s", argv[0]); + if (do_keep && pack_lockfile) { + *pack_lockfile = index_pack_lockfile(cmd.out); + close(cmd.out); + } + + if (finish_command(&cmd)) + die("%s failed", argv[0]); + if (use_sideband && finish_async(&demux)) + die("error in sideband demultiplexer"); + return 0; +} + +static struct ref *do_fetch_pack(struct fetch_pack_args *args, + int fd[2], + const struct ref *orig_ref, + struct string_list *sought, + char **pack_lockfile) +{ + struct ref *ref = copy_ref_list(orig_ref); + unsigned char sha1[20]; + const char *agent_feature; + int agent_len; + + sort_ref_list(&ref, ref_compare_name); + + if (is_repository_shallow() && !server_supports("shallow")) + die("Server does not support shallow clients"); + if (server_supports("multi_ack_detailed")) { + if (args->verbose) + fprintf(stderr, "Server supports multi_ack_detailed\n"); + multi_ack = 2; + if (server_supports("no-done")) { + if (args->verbose) + fprintf(stderr, "Server supports no-done\n"); + if (args->stateless_rpc) + no_done = 1; + } + } + else if (server_supports("multi_ack")) { + if (args->verbose) + fprintf(stderr, "Server supports multi_ack\n"); + multi_ack = 1; + } + if (server_supports("side-band-64k")) { + if (args->verbose) + fprintf(stderr, "Server supports side-band-64k\n"); + use_sideband = 2; + } + else if (server_supports("side-band")) { + if (args->verbose) + fprintf(stderr, "Server supports side-band\n"); + use_sideband = 1; + } + if (!server_supports("thin-pack")) + args->use_thin_pack = 0; + if (!server_supports("no-progress")) + args->no_progress = 0; + if (!server_supports("include-tag")) + args->include_tag = 0; + if (server_supports("ofs-delta")) { + if (args->verbose) + fprintf(stderr, "Server supports ofs-delta\n"); + } else + prefer_ofs_delta = 0; + + if ((agent_feature = server_feature_value("agent", &agent_len))) { + agent_supported = 1; + if (args->verbose && agent_len) + fprintf(stderr, "Server version is %.*s\n", + agent_len, agent_feature); + } + + if (everything_local(args, &ref, sought)) { + packet_flush(fd[1]); + goto all_done; + } + if (find_common(args, fd, sha1, ref) < 0) + if (!args->keep_pack) + /* When cloning, it is not unusual to have + * no common commit. + */ + warning("no common commits"); + + if (args->stateless_rpc) + packet_flush(fd[1]); + if (get_pack(args, fd, pack_lockfile)) + die("git fetch-pack: fetch failed."); + + all_done: + return ref; +} + +static int fetch_pack_config(const char *var, const char *value, void *cb) +{ + if (strcmp(var, "fetch.unpacklimit") == 0) { + fetch_unpack_limit = git_config_int(var, value); + return 0; + } + + if (strcmp(var, "transfer.unpacklimit") == 0) { + transfer_unpack_limit = git_config_int(var, value); + return 0; + } + + if (strcmp(var, "repack.usedeltabaseoffset") == 0) { + prefer_ofs_delta = git_config_bool(var, value); + return 0; + } + + if (!strcmp(var, "fetch.fsckobjects")) { + fetch_fsck_objects = git_config_bool(var, value); + return 0; + } + + if (!strcmp(var, "transfer.fsckobjects")) { + transfer_fsck_objects = git_config_bool(var, value); + return 0; + } + + return git_default_config(var, value, cb); +} + +static struct lock_file lock; + +static void fetch_pack_setup(void) +{ + static int did_setup; + if (did_setup) + return; + git_config(fetch_pack_config, NULL); + if (0 <= transfer_unpack_limit) + unpack_limit = transfer_unpack_limit; + else if (0 <= fetch_unpack_limit) + unpack_limit = fetch_unpack_limit; + did_setup = 1; +} + +struct ref *fetch_pack(struct fetch_pack_args *args, + int fd[], struct child_process *conn, + const struct ref *ref, + const char *dest, + struct string_list *sought, + char **pack_lockfile) +{ + struct stat st; + struct ref *ref_cpy; + + fetch_pack_setup(); + if (args->depth > 0) { + if (stat(git_path("shallow"), &st)) + st.st_mtime = 0; + } + + if (sought->nr) { + sort_string_list(sought); + string_list_remove_duplicates(sought, 0); + } + + if (!ref) { + packet_flush(fd[1]); + die("no matching remote head"); + } + ref_cpy = do_fetch_pack(args, fd, ref, sought, pack_lockfile); + + if (args->depth > 0) { + struct cache_time mtime; + struct strbuf sb = STRBUF_INIT; + char *shallow = git_path("shallow"); + int fd; + + mtime.sec = st.st_mtime; + mtime.nsec = ST_MTIME_NSEC(st); + if (stat(shallow, &st)) { + if (mtime.sec) + die("shallow file was removed during fetch"); + } else if (st.st_mtime != mtime.sec +#ifdef USE_NSEC + || ST_MTIME_NSEC(st) != mtime.nsec +#endif + ) + die("shallow file was changed during fetch"); + + fd = hold_lock_file_for_update(&lock, shallow, + LOCK_DIE_ON_ERROR); + if (!write_shallow_commits(&sb, 0) + || write_in_full(fd, sb.buf, sb.len) != sb.len) { + unlink_or_warn(shallow); + rollback_lock_file(&lock); + } else { + commit_lock_file(&lock); + } + strbuf_release(&sb); + } + + reprepare_packed_git(); + return ref_cpy; +} diff --git a/git-compat-util.h b/git-compat-util.h index 2fbf1fd8b1..2e79b8a2f3 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -506,6 +506,7 @@ extern const char tolower_trans_tbl[256]; #undef isdigit #undef isalpha #undef isalnum +#undef isprint #undef islower #undef isupper #undef tolower @@ -523,6 +524,7 @@ extern unsigned char sane_ctype[256]; #define isdigit(x) sane_istest(x,GIT_DIGIT) #define isalpha(x) sane_istest(x,GIT_ALPHA) #define isalnum(x) sane_istest(x,GIT_ALPHA | GIT_DIGIT) +#define isprint(x) ((x) >= 0x20 && (x) <= 0x7e) #define islower(x) sane_iscase(x, 1) #define isupper(x) sane_iscase(x, 0) #define is_glob_special(x) sane_istest(x,GIT_GLOB_SPECIAL) diff --git a/git-cvsimport.perl b/git-cvsimport.perl index 8032f23202..0a31ebd820 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -24,14 +24,14 @@ use Time::Local; use IO::Socket; use IO::Pipe; -use POSIX qw(strftime dup2 ENOENT); +use POSIX qw(strftime tzset dup2 ENOENT); use IPC::Open2; $SIG{'PIPE'}="IGNORE"; -$ENV{'TZ'}="UTC"; +set_timezone('UTC'); our ($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,@opt_M,$opt_A,$opt_S,$opt_L, $opt_a, $opt_r, $opt_R); -my (%conv_author_name, %conv_author_email); +my (%conv_author_name, %conv_author_email, %conv_author_tz); sub usage(;$) { my $msg = shift; @@ -59,6 +59,14 @@ ($) $conv_author_name{$user} = $2; $conv_author_email{$user} = $3; } + # or with an optional timezone: + # spawn=Simon Pawn America/Chicago + elsif (m/^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*(\S+?)\s*$/) { + $user = $1; + $conv_author_name{$user} = $2; + $conv_author_email{$user} = $3; + $conv_author_tz{$user} = $4; + } # However, we also read from CVSROOT/users format # to ease migration. elsif (/^(\w+):(['"]?)(.+?)\2\s*$/) { @@ -84,11 +92,22 @@ ($) die("Failed to open $file for writing: $!"); foreach (keys %conv_author_name) { - print $f "$_=$conv_author_name{$_} <$conv_author_email{$_}>\n"; + print $f "$_=$conv_author_name{$_} <$conv_author_email{$_}>"; + print $f " $conv_author_tz{$_}" if ($conv_author_tz{$_}); + print $f "\n"; } close ($f); } +# Versions of perl before 5.10.0 may not automatically check $TZ each +# time localtime is run (most platforms will do so only the first time). +# We can work around this by using tzset() to update the internal +# variable whenever we change the environment. +sub set_timezone { + $ENV{TZ} = shift; + tzset(); +} + # convert getopts specs for use by git config my %longmap = ( 'A:' => 'authors-file', @@ -795,7 +814,7 @@ () return $tree; } -my ($patchset,$date,$author_name,$author_email,$branch,$ancestor,$tag,$logmsg); +my ($patchset,$date,$author_name,$author_email,$author_tz,$branch,$ancestor,$tag,$logmsg); my (@old,@new,@skipped,%ignorebranch,@commit_revisions); # commits that cvsps cannot place anywhere... @@ -844,7 +863,9 @@ sub commit { } } - my $commit_date = strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)); + set_timezone($author_tz); + my $commit_date = strftime("%s %z", localtime($date)); + set_timezone('UTC'); $ENV{GIT_AUTHOR_NAME} = $author_name; $ENV{GIT_AUTHOR_EMAIL} = $author_email; $ENV{GIT_AUTHOR_DATE} = $commit_date; @@ -945,12 +966,14 @@ sub commit { } $state=3; } elsif ($state == 3 and s/^Author:\s+//) { + $author_tz = "UTC"; s/\s+$//; if (/^(.*?)\s+<(.*)>/) { ($author_name, $author_email) = ($1, $2); } elsif ($conv_author_name{$_}) { $author_name = $conv_author_name{$_}; $author_email = $conv_author_email{$_}; + $author_tz = $conv_author_tz{$_} if ($conv_author_tz{$_}); } else { $author_name = $author_email = $_; } diff --git a/git-cvsserver.perl b/git-cvsserver.perl index b8eddabc94..c5ebfa0636 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -51,6 +51,10 @@ #### Definition and mappings of functions #### +# NOTE: Despite the existence of req_CATCHALL and req_EMPTY unimplemented +# requests, this list is incomplete. It is missing many rarer/optional +# requests. Perhaps some clients require a claim of support for +# these specific requests for main functionality to work? my $methods = { 'Root' => \&req_Root, 'Valid-responses' => \&req_Validresponses, @@ -80,7 +84,6 @@ 'noop' => \&req_EMPTY, 'annotate' => \&req_annotate, 'Global_option' => \&req_Globaloption, - #'annotate' => \&req_CATCHALL, }; ############################################## @@ -499,7 +502,7 @@ sub req_Entry #$log->debug("req_Entry : $data"); - my @data = split(/\//, $data); + my @data = split(/\//, $data, -1); $state->{entries}{$state->{directory}.$data[1]} = { revision => $data[2], @@ -540,8 +543,6 @@ sub req_add my $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log); $updater->update(); - argsfromdir($updater); - my $addcount = 0; foreach my $filename ( @{$state->{args}} ) @@ -551,10 +552,10 @@ sub req_add my $meta = $updater->getmeta($filename); my $wrev = revparse($filename); - if ($wrev && $meta && ($wrev < 0)) + if ($wrev && $meta && ($wrev=~/^-/)) { # previously removed file, add back - $log->info("added file $filename was previously removed, send 1.$meta->{revision}"); + $log->info("added file $filename was previously removed, send $meta->{revision}"); print "MT +updated\n"; print "MT text U \n"; @@ -571,8 +572,8 @@ sub req_add # this is an "entries" line my $kopts = kopts_from_path($filename,"sha1",$meta->{filehash}); - $log->debug("/$filepart/1.$meta->{revision}//$kopts/"); - print "/$filepart/1.$meta->{revision}//$kopts/\n"; + $log->debug("/$filepart/$meta->{revision}//$kopts/"); + print "/$filepart/$meta->{revision}//$kopts/\n"; # permissions $log->debug("SEND : u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}"); print "u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}\n"; @@ -680,13 +681,13 @@ sub req_remove next; } - if ( defined($wrev) and $wrev < 0 ) + if ( defined($wrev) and ($wrev=~/^-/) ) { print "E cvs remove: file `$filename' already scheduled for removal\n"; next; } - unless ( $wrev == $meta->{revision} ) + unless ( $wrev eq $meta->{revision} ) { # TODO : not sure if the format of this message is quite correct. print "E cvs remove: Up to date check failed for `$filename'\n"; @@ -701,7 +702,7 @@ sub req_remove print "Checked-in $dirpart\n"; print "$filename\n"; my $kopts = kopts_from_path($filename,"sha1",$meta->{filehash}); - print "/$filepart/-1.$wrev//$kopts/\n"; + print "/$filepart/-$wrev//$kopts/\n"; $rmcount++; } @@ -992,7 +993,7 @@ sub req_co # this is an "entries" line my $kopts = kopts_from_path($fullName,"sha1",$git->{filehash}); - print "/$git->{name}/1.$git->{revision}//$kopts/\n"; + print "/$git->{name}/$git->{revision}//$kopts/\n"; # permissions print "u=$git->{mode},g=$git->{mode},o=$git->{mode}\n"; @@ -1080,7 +1081,7 @@ sub req_update } my $meta; - if ( defined($state->{opt}{r}) and $state->{opt}{r} =~ /^1\.(\d+)/ ) + if ( defined($state->{opt}{r}) and $state->{opt}{r} =~ /^(1\.\d+)$/ ) { $meta = $updater->getmeta($filename, $1); } else { @@ -1102,7 +1103,7 @@ sub req_update { $meta = { name => $filename, - revision => 0, + revision => '0', filehash => 'added' }; } @@ -1112,7 +1113,7 @@ sub req_update my $wrev = revparse($filename); # If the working copy is an old revision, lets get that version too for comparison. - if ( defined($wrev) and $wrev != $meta->{revision} ) + if ( defined($wrev) and $wrev ne $meta->{revision} ) { $oldmeta = $updater->getmeta($filename, $wrev); } @@ -1123,7 +1124,7 @@ sub req_update # and the working copy is unmodified _and_ the user hasn't specified -C next if ( defined ( $wrev ) and defined($meta->{revision}) - and $wrev == $meta->{revision} + and $wrev eq $meta->{revision} and $state->{entries}{$filename}{unchanged} and not exists ( $state->{opt}{C} ) ); @@ -1131,7 +1132,7 @@ sub req_update # but the working copy is modified, tell the client it's modified if ( defined ( $wrev ) and defined($meta->{revision}) - and $wrev == $meta->{revision} + and $wrev eq $meta->{revision} and defined($state->{entries}{$filename}{modified_hash}) and not exists ( $state->{opt}{C} ) ) { @@ -1144,6 +1145,10 @@ sub req_update if ( $meta->{filehash} eq "deleted" ) { + # TODO: If it has been modified in the sandbox, error out + # with the appropriate message, rather than deleting a modified + # file. + my ( $filepart, $dirpart ) = filenamesplit($filename,1); $log->info("Removing '$filename' from working copy (no longer in the repo)"); @@ -1161,7 +1166,7 @@ sub req_update { # normal update, just send the new revision (either U=Update, # or A=Add, or R=Remove) - if ( defined($wrev) && $wrev < 0 ) + if ( defined($wrev) && ($wrev=~/^-/) ) { $log->info("Tell the client the file is scheduled for removal"); print "MT text R \n"; @@ -1169,7 +1174,8 @@ sub req_update print "MT newline\n"; next; } - elsif ( (!defined($wrev) || $wrev == 0) && (!defined($meta->{revision}) || $meta->{revision} == 0) ) + elsif ( (!defined($wrev) || $wrev eq '0') && + (!defined($meta->{revision}) || $meta->{revision} eq '0') ) { $log->info("Tell the client the file is scheduled for addition"); print "MT text A \n"; @@ -1179,7 +1185,7 @@ sub req_update } else { - $log->info("Updating '$filename' to ".$meta->{revision}); + $log->info("UpdatingX3 '$filename' to ".$meta->{revision}); print "MT +updated\n"; print "MT text U \n"; print "MT fname $filename\n"; @@ -1211,8 +1217,8 @@ sub req_update # this is an "entries" line my $kopts = kopts_from_path($filename,"sha1",$meta->{filehash}); - $log->debug("/$filepart/1.$meta->{revision}//$kopts/"); - print "/$filepart/1.$meta->{revision}//$kopts/\n"; + $log->debug("/$filepart/$meta->{revision}//$kopts/"); + print "/$filepart/$meta->{revision}//$kopts/\n"; # permissions $log->debug("SEND : u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}"); @@ -1222,7 +1228,6 @@ sub req_update transmitfile($meta->{filehash}); } } else { - $log->info("Updating '$filename'"); my ( $filepart, $dirpart ) = filenamesplit($meta->{name},1); my $mergeDir = setupTmpDir(); @@ -1237,7 +1242,7 @@ sub req_update # we need to merge with the local changes ( M=successful merge, C=conflict merge ) $log->info("Merging $file_local, $file_old, $file_new"); - print "M Merging differences between 1.$oldmeta->{revision} and 1.$meta->{revision} into $filename\n"; + print "M Merging differences between $oldmeta->{revision} and $meta->{revision} into $filename\n"; $log->debug("Temporary directory for merge is $mergeDir"); @@ -1260,8 +1265,8 @@ sub req_update print $state->{CVSROOT} . "/$state->{module}/$filename\n"; my $kopts = kopts_from_path("$dirpart/$filepart", "file",$mergedFile); - $log->debug("/$filepart/1.$meta->{revision}//$kopts/"); - print "/$filepart/1.$meta->{revision}//$kopts/\n"; + $log->debug("/$filepart/$meta->{revision}//$kopts/"); + print "/$filepart/$meta->{revision}//$kopts/\n"; } } elsif ( $return == 1 ) @@ -1277,7 +1282,7 @@ sub req_update print $state->{CVSROOT} . "/$state->{module}/$filename\n"; my $kopts = kopts_from_path("$dirpart/$filepart", "file",$mergedFile); - print "/$filepart/1.$meta->{revision}/+/$kopts/\n"; + print "/$filepart/$meta->{revision}/+/$kopts/\n"; } } else @@ -1380,11 +1385,12 @@ sub req_ci my $addflag = 0; my $rmflag = 0; - $rmflag = 1 if ( defined($wrev) and $wrev < 0 ); + $rmflag = 1 if ( defined($wrev) and ($wrev=~/^-/) ); $addflag = 1 unless ( -e $filename ); # Do up to date checking - unless ( $addflag or $wrev == $meta->{revision} or ( $rmflag and -$wrev == $meta->{revision} ) ) + unless ( $addflag or $wrev eq $meta->{revision} or + ( $rmflag and $wrev eq "-$meta->{revision}" ) ) { # fail everything if an up to date check fails print "error 1 Up to date check failed for $filename\n"; @@ -1421,7 +1427,7 @@ sub req_ci $log->info("Adding file '$filename'"); system("git", "update-index", "--add", $filename); } else { - $log->info("Updating file '$filename'"); + $log->info("UpdatingX2 file '$filename'"); system("git", "update-index", $filename); } } @@ -1512,7 +1518,7 @@ sub req_ci my $meta = $updater->getmeta($filename); unless (defined $meta->{revision}) { - $meta->{revision} = 1; + $meta->{revision} = "1.1"; } my ( $filepart, $dirpart ) = filenamesplit($filename, 1); @@ -1522,19 +1528,19 @@ sub req_ci print "M $state->{CVSROOT}/$state->{module}/$filename,v <-- $dirpart$filepart\n"; if ( defined $meta->{filehash} && $meta->{filehash} eq "deleted" ) { - print "M new revision: delete; previous revision: 1.$oldmeta{$filename}{revision}\n"; + print "M new revision: delete; previous revision: $oldmeta{$filename}{revision}\n"; print "Remove-entry $dirpart\n"; print "$filename\n"; } else { - if ($meta->{revision} == 1) { + if ($meta->{revision} eq "1.1") { print "M initial revision: 1.1\n"; } else { - print "M new revision: 1.$meta->{revision}; previous revision: 1.$oldmeta{$filename}{revision}\n"; + print "M new revision: $meta->{revision}; previous revision: $oldmeta{$filename}{revision}\n"; } print "Checked-in $dirpart\n"; print "$filename\n"; my $kopts = kopts_from_path($filename,"sha1",$meta->{filehash}); - print "/$filepart/1.$meta->{revision}//$kopts/\n"; + print "/$filepart/$meta->{revision}//$kopts/\n"; } } @@ -1552,10 +1558,12 @@ sub req_status #$log->debug("status state : " . Dumper($state)); # Grab a handle to the SQLite db and do any necessary updates - my $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log); + my $updater; + $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log); $updater->update(); - # if no files were specified, we need to work out what files we should be providing status on ... + # if no files were specified, we need to work out what files we should + # be providing status on ... argsfromdir($updater); # foreach file specified on the command line ... @@ -1563,67 +1571,135 @@ sub req_status { $filename = filecleanup($filename); - next if exists($state->{opt}{l}) && index($filename, '/', length($state->{prependdir})) >= 0; + if ( exists($state->{opt}{l}) && + index($filename, '/', length($state->{prependdir})) >= 0 ) + { + next; + } my $meta = $updater->getmeta($filename); my $oldmeta = $meta; my $wrev = revparse($filename); - # If the working copy is an old revision, lets get that version too for comparison. - if ( defined($wrev) and $wrev != $meta->{revision} ) + # If the working copy is an old revision, lets get that + # version too for comparison. + if ( defined($wrev) and $wrev ne $meta->{revision} ) { $oldmeta = $updater->getmeta($filename, $wrev); } # TODO : All possible statuses aren't yet implemented my $status; - # Files are up to date if the working copy and repo copy have the same revision, and the working copy is unmodified - $status = "Up-to-date" if ( defined ( $wrev ) and defined($meta->{revision}) and $wrev == $meta->{revision} - and - ( ( $state->{entries}{$filename}{unchanged} and ( not defined ( $state->{entries}{$filename}{conflict} ) or $state->{entries}{$filename}{conflict} !~ /^\+=/ ) ) - or ( defined($state->{entries}{$filename}{modified_hash}) and $state->{entries}{$filename}{modified_hash} eq $meta->{filehash} ) ) - ); - - # Need checkout if the working copy has an older revision than the repo copy, and the working copy is unmodified - $status ||= "Needs Checkout" if ( defined ( $wrev ) and defined ( $meta->{revision} ) and $meta->{revision} > $wrev - and - ( $state->{entries}{$filename}{unchanged} - or ( defined($state->{entries}{$filename}{modified_hash}) and $state->{entries}{$filename}{modified_hash} eq $oldmeta->{filehash} ) ) - ); - - # Need checkout if it exists in the repo but doesn't have a working copy - $status ||= "Needs Checkout" if ( not defined ( $wrev ) and defined ( $meta->{revision} ) ); - - # Locally modified if working copy and repo copy have the same revision but there are local changes - $status ||= "Locally Modified" if ( defined ( $wrev ) and defined($meta->{revision}) and $wrev == $meta->{revision} and $state->{entries}{$filename}{modified_filename} ); - - # Needs Merge if working copy revision is less than repo copy and there are local changes - $status ||= "Needs Merge" if ( defined ( $wrev ) and defined ( $meta->{revision} ) and $meta->{revision} > $wrev and $state->{entries}{$filename}{modified_filename} ); - - $status ||= "Locally Added" if ( defined ( $state->{entries}{$filename}{revision} ) and not defined ( $meta->{revision} ) ); - $status ||= "Locally Removed" if ( defined ( $wrev ) and defined ( $meta->{revision} ) and -$wrev == $meta->{revision} ); - $status ||= "Unresolved Conflict" if ( defined ( $state->{entries}{$filename}{conflict} ) and $state->{entries}{$filename}{conflict} =~ /^\+=/ ); - $status ||= "File had conflicts on merge" if ( 0 ); + # Files are up to date if the working copy and repo copy have + # the same revision, and the working copy is unmodified + if ( defined ( $wrev ) and defined($meta->{revision}) and + $wrev eq $meta->{revision} and + ( ( $state->{entries}{$filename}{unchanged} and + ( not defined ( $state->{entries}{$filename}{conflict} ) or + $state->{entries}{$filename}{conflict} !~ /^\+=/ ) ) or + ( defined($state->{entries}{$filename}{modified_hash}) and + $state->{entries}{$filename}{modified_hash} eq + $meta->{filehash} ) ) ) + { + $status = "Up-to-date" + } + + # Need checkout if the working copy has a different (usually + # older) revision than the repo copy, and the working copy is + # unmodified + if ( defined ( $wrev ) and defined ( $meta->{revision} ) and + $meta->{revision} ne $wrev and + ( $state->{entries}{$filename}{unchanged} or + ( defined($state->{entries}{$filename}{modified_hash}) and + $state->{entries}{$filename}{modified_hash} eq + $oldmeta->{filehash} ) ) ) + { + $status ||= "Needs Checkout"; + } + + # Need checkout if it exists in the repo but doesn't have a working + # copy + if ( not defined ( $wrev ) and defined ( $meta->{revision} ) ) + { + $status ||= "Needs Checkout"; + } + + # Locally modified if working copy and repo copy have the + # same revision but there are local changes + if ( defined ( $wrev ) and defined($meta->{revision}) and + $wrev eq $meta->{revision} and + $state->{entries}{$filename}{modified_filename} ) + { + $status ||= "Locally Modified"; + } + + # Needs Merge if working copy revision is different + # (usually older) than repo copy and there are local changes + if ( defined ( $wrev ) and defined ( $meta->{revision} ) and + $meta->{revision} ne $wrev and + $state->{entries}{$filename}{modified_filename} ) + { + $status ||= "Needs Merge"; + } + + if ( defined ( $state->{entries}{$filename}{revision} ) and + not defined ( $meta->{revision} ) ) + { + $status ||= "Locally Added"; + } + if ( defined ( $wrev ) and defined ( $meta->{revision} ) and + $wrev eq "-$meta->{revision}" ) + { + $status ||= "Locally Removed"; + } + if ( defined ( $state->{entries}{$filename}{conflict} ) and + $state->{entries}{$filename}{conflict} =~ /^\+=/ ) + { + $status ||= "Unresolved Conflict"; + } + if ( 0 ) + { + $status ||= "File had conflicts on merge"; + } $status ||= "Unknown"; my ($filepart) = filenamesplit($filename); - print "M ===================================================================\n"; + print "M =======" . ( "=" x 60 ) . "\n"; print "M File: $filepart\tStatus: $status\n"; if ( defined($state->{entries}{$filename}{revision}) ) { - print "M Working revision:\t" . $state->{entries}{$filename}{revision} . "\n"; + print "M Working revision:\t" . + $state->{entries}{$filename}{revision} . "\n"; } else { print "M Working revision:\tNo entry for $filename\n"; } if ( defined($meta->{revision}) ) { - print "M Repository revision:\t1." . $meta->{revision} . "\t$state->{CVSROOT}/$state->{module}/$filename,v\n"; - print "M Sticky Tag:\t\t(none)\n"; - print "M Sticky Date:\t\t(none)\n"; - print "M Sticky Options:\t\t(none)\n"; + print "M Repository revision:\t" . + $meta->{revision} . + "\t$state->{CVSROOT}/$state->{module}/$filename,v\n"; + my($tagOrDate)=$state->{entries}{$filename}{tag_or_date}; + my($tag)=($tagOrDate=~m/^T(.+)$/); + if( !defined($tag) ) + { + $tag="(none)"; + } + print "M Sticky Tag:\t\t$tag\n"; + my($date)=($tagOrDate=~m/^D(.+)$/); + if( !defined($date) ) + { + $date="(none)"; + } + print "M Sticky Date:\t\t$date\n"; + my($options)=$state->{entries}{$filename}{options}; + if( $options eq "" ) + { + $options="(none)"; + } + print "M Sticky Options:\t\t$options\n"; } else { print "M Repository revision:\tNo revision control file\n"; } @@ -1651,16 +1727,17 @@ sub req_diff $revision1 = $state->{opt}{r}; } - $revision1 =~ s/^1\.// if ( defined ( $revision1 ) ); - $revision2 =~ s/^1\.// if ( defined ( $revision2 ) ); - - $log->debug("Diffing revisions " . ( defined($revision1) ? $revision1 : "[NULL]" ) . " and " . ( defined($revision2) ? $revision2 : "[NULL]" ) ); + $log->debug("Diffing revisions " . + ( defined($revision1) ? $revision1 : "[NULL]" ) . + " and " . ( defined($revision2) ? $revision2 : "[NULL]" ) ); # Grab a handle to the SQLite db and do any necessary updates - my $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log); + my $updater; + $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log); $updater->update(); - # if no files were specified, we need to work out what files we should be providing status on ... + # if no files were specified, we need to work out what files we should + # be providing status on ... argsfromdir($updater); # foreach file specified on the command line ... @@ -1682,7 +1759,7 @@ sub req_diff $meta1 = $updater->getmeta($filename, $revision1); unless ( defined ( $meta1 ) and $meta1->{filehash} ne "deleted" ) { - print "E File $filename at revision 1.$revision1 doesn't exist\n"; + print "E File $filename at revision $revision1 doesn't exist\n"; next; } transmitfile($meta1->{filehash}, { targetfile => $file1 }); @@ -1703,7 +1780,7 @@ sub req_diff unless ( defined ( $meta2 ) and $meta2->{filehash} ne "deleted" ) { - print "E File $filename at revision 1.$revision2 doesn't exist\n"; + print "E File $filename at revision $revision2 doesn't exist\n"; next; } @@ -1715,7 +1792,8 @@ sub req_diff $file2 = $state->{entries}{$filename}{modified_filename}; } - # if we have been given -r, and we don't have a $file2 yet, lets get one + # if we have been given -r, and we don't have a $file2 yet, lets + # get one if ( defined ( $revision1 ) and not defined ( $file2 ) ) { ( undef, $file2 ) = tempfile( DIR => $TEMP_DIR, OPEN => 0 ); @@ -1726,21 +1804,37 @@ sub req_diff # We need to have retrieved something useful next unless ( defined ( $meta1 ) ); - # Files to date if the working copy and repo copy have the same revision, and the working copy is unmodified - next if ( not defined ( $meta2 ) and $wrev == $meta1->{revision} - and - ( ( $state->{entries}{$filename}{unchanged} and ( not defined ( $state->{entries}{$filename}{conflict} ) or $state->{entries}{$filename}{conflict} !~ /^\+=/ ) ) - or ( defined($state->{entries}{$filename}{modified_hash}) and $state->{entries}{$filename}{modified_hash} eq $meta1->{filehash} ) ) - ); + # Files to date if the working copy and repo copy have the same + # revision, and the working copy is unmodified + if ( not defined ( $meta2 ) and $wrev eq $meta1->{revision} and + ( ( $state->{entries}{$filename}{unchanged} and + ( not defined ( $state->{entries}{$filename}{conflict} ) or + $state->{entries}{$filename}{conflict} !~ /^\+=/ ) ) or + ( defined($state->{entries}{$filename}{modified_hash}) and + $state->{entries}{$filename}{modified_hash} eq + $meta1->{filehash} ) ) ) + { + next; + } # Apparently we only show diffs for locally modified files - next unless ( defined($meta2) or defined ( $state->{entries}{$filename}{modified_filename} ) ); + unless ( defined($meta2) or + defined ( $state->{entries}{$filename}{modified_filename} ) ) + { + next; + } print "M Index: $filename\n"; - print "M ===================================================================\n"; + print "M =======" . ( "=" x 60 ) . "\n"; print "M RCS file: $state->{CVSROOT}/$state->{module}/$filename,v\n"; - print "M retrieving revision 1.$meta1->{revision}\n" if ( defined ( $meta1 ) ); - print "M retrieving revision 1.$meta2->{revision}\n" if ( defined ( $meta2 ) ); + if ( defined ( $meta1 ) ) + { + print "M retrieving revision $meta1->{revision}\n" + } + if ( defined ( $meta2 ) ) + { + print "M retrieving revision $meta2->{revision}\n" + } print "M diff "; foreach my $opt ( keys %{$state->{opt}} ) { @@ -1752,18 +1846,27 @@ sub req_diff } } else { print "-$opt "; - print "$state->{opt}{$opt} " if ( defined ( $state->{opt}{$opt} ) ); + if ( defined ( $state->{opt}{$opt} ) ) + { + print "$state->{opt}{$opt} " + } } } print "$filename\n"; - $log->info("Diffing $filename -r $meta1->{revision} -r " . ( $meta2->{revision} or "workingcopy" )); + $log->info("Diffing $filename -r $meta1->{revision} -r " . + ( $meta2->{revision} or "workingcopy" )); ( $fh, $filediff ) = tempfile ( DIR => $TEMP_DIR ); if ( exists $state->{opt}{u} ) { - system("diff -u -L '$filename revision 1.$meta1->{revision}' -L '$filename " . ( defined($meta2->{revision}) ? "revision 1.$meta2->{revision}" : "working copy" ) . "' $file1 $file2 > $filediff"); + system("diff -u -L '$filename revision $meta1->{revision}'" . + " -L '$filename " . + ( defined($meta2->{revision}) ? + "revision $meta2->{revision}" : + "working copy" ) . + "' $file1 $file2 > $filediff" ); } else { system("diff $file1 $file2 > $filediff"); } @@ -1787,22 +1890,19 @@ sub req_log $log->debug("req_log : " . ( defined($data) ? $data : "[NULL]" )); #$log->debug("log state : " . Dumper($state)); - my ( $minrev, $maxrev ); - if ( defined ( $state->{opt}{r} ) and $state->{opt}{r} =~ /([\d.]+)?(::?)([\d.]+)?/ ) + my ( $revFilter ); + if ( defined ( $state->{opt}{r} ) ) { - my $control = $2; - $minrev = $1; - $maxrev = $3; - $minrev =~ s/^1\.// if ( defined ( $minrev ) ); - $maxrev =~ s/^1\.// if ( defined ( $maxrev ) ); - $minrev++ if ( defined($minrev) and $control eq "::" ); + $revFilter = $state->{opt}{r}; } # Grab a handle to the SQLite db and do any necessary updates - my $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log); + my $updater; + $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log); $updater->update(); - # if no files were specified, we need to work out what files we should be providing status on ... + # if no files were specified, we need to work out what files we + # should be providing status on ... argsfromdir($updater); # foreach file specified on the command line ... @@ -1812,53 +1912,46 @@ sub req_log my $headmeta = $updater->getmeta($filename); - my $revisions = $updater->getlog($filename); - my $totalrevisions = scalar(@$revisions); - - if ( defined ( $minrev ) ) - { - $log->debug("Removing revisions less than $minrev"); - while ( scalar(@$revisions) > 0 and $revisions->[-1]{revision} < $minrev ) - { - pop @$revisions; - } - } - if ( defined ( $maxrev ) ) - { - $log->debug("Removing revisions greater than $maxrev"); - while ( scalar(@$revisions) > 0 and $revisions->[0]{revision} > $maxrev ) - { - shift @$revisions; - } - } + my ($revisions,$totalrevisions) = $updater->getlog($filename, + $revFilter); next unless ( scalar(@$revisions) ); print "M \n"; print "M RCS file: $state->{CVSROOT}/$state->{module}/$filename,v\n"; print "M Working file: $filename\n"; - print "M head: 1.$headmeta->{revision}\n"; + print "M head: $headmeta->{revision}\n"; print "M branch:\n"; print "M locks: strict\n"; print "M access list:\n"; print "M symbolic names:\n"; print "M keyword substitution: kv\n"; - print "M total revisions: $totalrevisions;\tselected revisions: " . scalar(@$revisions) . "\n"; + print "M total revisions: $totalrevisions;\tselected revisions: " . + scalar(@$revisions) . "\n"; print "M description:\n"; foreach my $revision ( @$revisions ) { print "M ----------------------------\n"; - print "M revision 1.$revision->{revision}\n"; + print "M revision $revision->{revision}\n"; # reformat the date for log output - $revision->{modified} = sprintf('%04d/%02d/%02d %s', $3, $DATE_LIST->{$2}, $1, $4 ) if ( $revision->{modified} =~ /(\d+)\s+(\w+)\s+(\d+)\s+(\S+)/ and defined($DATE_LIST->{$2}) ); + if ( $revision->{modified} =~ /(\d+)\s+(\w+)\s+(\d+)\s+(\S+)/ and + defined($DATE_LIST->{$2}) ) + { + $revision->{modified} = sprintf('%04d/%02d/%02d %s', + $3, $DATE_LIST->{$2}, $1, $4 ); + } $revision->{author} = cvs_author($revision->{author}); - print "M date: $revision->{modified}; author: $revision->{author}; state: " . ( $revision->{filehash} eq "deleted" ? "dead" : "Exp" ) . "; lines: +2 -3\n"; - my $commitmessage = $updater->commitmessage($revision->{commithash}); + print "M date: $revision->{modified};" . + " author: $revision->{author}; state: " . + ( $revision->{filehash} eq "deleted" ? "dead" : "Exp" ) . + "; lines: +2 -3\n"; + my $commitmessage; + $commitmessage = $updater->commitmessage($revision->{commithash}); $commitmessage =~ s/^/M /mg; print $commitmessage . "\n"; } - print "M =============================================================================\n"; + print "M =======" . ( "=" x 70 ) . "\n"; } print "ok\n"; @@ -1964,7 +2057,7 @@ sub req_annotate $metadata->{$commithash}{author} = cvs_author($metadata->{$commithash}{author}); $metadata->{$commithash}{modified} = sprintf("%02d-%s-%02d", $1, $2, $3) if ( $metadata->{$commithash}{modified} =~ /^(\d+)\s(\w+)\s\d\d(\d\d)/ ); } - printf("M 1.%-5d (%-8s %10s): %s\n", + printf("M %-7s (%-8s %10s): %s\n", $metadata->{$commithash}{revision}, $metadata->{$commithash}{author}, $metadata->{$commithash}{modified}, @@ -2085,7 +2178,7 @@ sub argsfromdir # push added files foreach my $file (keys %{$state->{entries}}) { if ( exists $state->{entries}{$file}{revision} && - $state->{entries}{$file}{revision} == 0 ) + $state->{entries}{$file}{revision} eq '0' ) { push @gethead, { name => $file, filehash => 'added' }; } @@ -2129,16 +2222,15 @@ sub statecleanup $state->{entries} = {}; } +# Return working directory CVS revision "1.X" out +# of the the working directory "entries" state, for the given filename. +# This is prefixed with a dash if the file is scheduled for removal +# when it is committed. sub revparse { my $filename = shift; - return undef unless ( defined ( $state->{entries}{$filename}{revision} ) ); - - return $1 if ( $state->{entries}{$filename}{revision} =~ /^1\.(\d+)/ ); - return -$1 if ( $state->{entries}{$filename}{revision} =~ /^-1\.(\d+)/ ); - - return undef; + return $state->{entries}{$filename}{revision}; } # This method takes a file hash and does a CVS "file transfer". Its @@ -2444,42 +2536,14 @@ sub kopts_from_path } elsif( ($cfg->{gitcvs}{allbinary} =~ /^\s*guess\s*$/i) ) { - if( $srcType eq "sha1Or-k" && - !defined($name) ) + if( is_binary($srcType,$name) ) { - my ($ret)=$state->{entries}{$path}{options}; - if( !defined($ret) ) - { - $ret=$state->{opt}{k}; - if(defined($ret)) - { - $ret="-k$ret"; - } - else - { - $ret=""; - } - } - if( ! ($ret=~/^(|-kb|-kkv|-kkvl|-kk|-ko|-kv)$/) ) - { - print "E Bad -k option\n"; - $log->warn("Bad -k option: $ret"); - die "Error: Bad -k option: $ret\n"; - } - - return $ret; + $log->debug("... as binary"); + return "-kb"; } else { - if( is_binary($srcType,$name) ) - { - $log->debug("... as binary"); - return "-kb"; - } - else - { - $log->debug("... as text"); - } + $log->debug("... as text"); } } } @@ -2586,7 +2650,7 @@ sub open_blob_or_die die "Unable to open file $name: $!\n"; } } - elsif( $srcType eq "sha1" || $srcType eq "sha1Or-k" ) + elsif( $srcType eq "sha1" ) { unless ( defined ( $name ) and $name =~ /^[a-zA-Z0-9]{40}$/ ) { @@ -2929,6 +2993,16 @@ sub new } # Construct the revision table if required + # The revision table stores an entry for each file, each time that file + # changes. + # numberOfRecords = O( numCommits * averageNumChangedFilesPerCommit ) + # This is not sufficient to support "-r {commithash}" for any + # files except files that were modified by that commit (also, + # some places in the code ignore/effectively strip out -r in + # some cases, before it gets passed to getmeta()). + # The "filehash" field typically has a git blob hash, but can also + # be set to "dead" to indicate that the given version of the file + # should not exist in the sandbox. unless ( $self->{tables}{$self->tablename("revision")} ) { my $tablename = $self->tablename("revision"); @@ -2956,6 +3030,15 @@ sub new } # Construct the head table if required + # The head table (along with the "last_commit" entry in the property + # table) is the persisted working state of the "sub update" subroutine. + # All of it's data is read entirely first, and completely recreated + # last, every time "sub update" runs. + # This is also used by "sub getmeta" when it is asked for the latest + # version of a file (as opposed to some specific version). + # Another way of thinking about it is as a single slice out of + # "revisions", giving just the most recent revision information for + # each file. unless ( $self->{tables}{$self->tablename("head")} ) { my $tablename = $self->tablename("head"); @@ -2978,6 +3061,7 @@ sub new } # Construct the properties table if required + # - "last_commit" - Used by "sub update". unless ( $self->{tables}{$self->tablename("properties")} ) { my $tablename = $self->tablename("properties"); @@ -2990,6 +3074,11 @@ sub new } # Construct the commitmsgs table if required + # The commitmsgs table is only used for merge commits, since + # "sub update" will only keep one branch of parents. Shortlogs + # for ignored commits (i.e. not on the chosen branch) will be used + # to construct a replacement "collapsed" merge commit message, + # which will be stored in this table. See also "sub commitmessage". unless ( $self->{tables}{$self->tablename("commitmsgs")} ) { my $tablename = $self->tablename("commitmsgs"); @@ -3021,6 +3110,14 @@ sub tablename =head2 update +Bring the database up to date with the latest changes from +the git repository. + +Internal working state is read out of the "head" table and the +"last_commit" property, then it updates "revisions" based on that, and +finally it writes the new internal state back to the "head" table +so it can be used as a starting point the next time update is called. + =cut sub update { @@ -3116,7 +3213,7 @@ sub update my $commitcount = 0; # Load the head table into $head (for cached lookups during the update process) - foreach my $file ( @{$self->gethead()} ) + foreach my $file ( @{$self->gethead(1)} ) { $head->{$file->{name}} = $file; } @@ -3134,17 +3231,18 @@ sub update next; } elsif (@{$commit->{parents}} > 1) { # it is a merge commit, for each parent that is - # not $lastpicked, see if we can get a log + # not $lastpicked (not given a CVS revision number), + # see if we can get a log # from the merge-base to that parent to put it # in the message as a merge summary. my @parents = @{$commit->{parents}}; foreach my $parent (@parents) { - # git-merge-base can potentially (but rarely) throw - # several candidate merge bases. let's assume - # that the first one is the best one. if ($parent eq $lastpicked) { next; } + # git-merge-base can potentially (but rarely) throw + # several candidate merge bases. let's assume + # that the first one is the best one. my $base = eval { safe_pipe_capture('git', 'merge-base', $lastpicked, $parent); @@ -3426,19 +3524,6 @@ sub insert_head $insert_head->execute($name, $revision, $filehash, $commithash, $modified, $author, $mode); } -sub _headrev -{ - my $self = shift; - my $filename = shift; - my $tablename = $self->tablename("head"); - - my $db_query = $self->{dbh}->prepare_cached("SELECT filehash, revision, mode FROM $tablename WHERE name=?",{},1); - $db_query->execute($filename); - my ( $hash, $revision, $mode ) = $db_query->fetchrow_array; - - return ( $hash, $revision, $mode ); -} - sub _get_prop { my $self = shift; @@ -3478,6 +3563,7 @@ =head2 gethead sub gethead { my $self = shift; + my $intRev = shift; my $tablename = $self->tablename("head"); return $self->{gethead_cache} if ( defined ( $self->{gethead_cache} ) ); @@ -3488,6 +3574,10 @@ sub gethead my $tree = []; while ( my $file = $db_query->fetchrow_hashref ) { + if(!$intRev) + { + $file->{revision} = "1.$file->{revision}" + } push @$tree, $file; } @@ -3498,24 +3588,57 @@ sub gethead =head2 getlog +See also gethistorydense(). + =cut sub getlog { my $self = shift; my $filename = shift; + my $revFilter = shift; + my $tablename = $self->tablename("revision"); + # Filters: + # TODO: date, state, or by specific logins filters? + # TODO: Handle comma-separated list of revFilter items, each item + # can be a range [only case currently handled] or individual + # rev or branch or "branch.". + # TODO: Adjust $db_query WHERE clause based on revFilter, instead of + # manually filtering the results of the query? + my ( $minrev, $maxrev ); + if( defined($revFilter) and + $state->{opt}{r} =~ /^(1.(\d+))?(::?)(1.(\d.+))?$/ ) + { + my $control = $3; + $minrev = $2; + $maxrev = $5; + $minrev++ if ( defined($minrev) and $control eq "::" ); + } + my $db_query = $self->{dbh}->prepare_cached("SELECT name, filehash, author, mode, revision, modified, commithash FROM $tablename WHERE name=? ORDER BY revision DESC",{},1); $db_query->execute($filename); + my $totalRevs=0; my $tree = []; while ( my $file = $db_query->fetchrow_hashref ) { + $totalRevs++; + if( defined($minrev) and $file->{revision} < $minrev ) + { + next; + } + if( defined($maxrev) and $file->{revision} > $maxrev ) + { + next; + } + + $file->{revision} = "1." . $file->{revision}; push @$tree, $file; } - return $tree; + return ($tree,$totalRevs); } =head2 getmeta @@ -3534,10 +3657,11 @@ sub getmeta my $tablename_head = $self->tablename("head"); my $db_query; - if ( defined($revision) and $revision =~ /^\d+$/ ) + if ( defined($revision) and $revision =~ /^1\.(\d+)$/ ) { + my ($intRev) = $1; $db_query = $self->{dbh}->prepare_cached("SELECT * FROM $tablename_rev WHERE name=? AND revision=?",{},1); - $db_query->execute($filename, $revision); + $db_query->execute($filename, $intRev); } elsif ( defined($revision) and $revision =~ /^[a-zA-Z0-9]{40}$/ ) { @@ -3548,7 +3672,12 @@ sub getmeta $db_query->execute($filename); } - return $db_query->fetchrow_hashref; + my $meta = $db_query->fetchrow_hashref; + if($meta) + { + $meta->{revision} = "1.$meta->{revision}"; + } + return $meta; } =head2 commitmessage @@ -3583,25 +3712,6 @@ sub commitmessage return $message; } -=head2 gethistory - -This function takes a filename (with path) argument and returns an arrayofarrays -containing revision,filehash,commithash ordered by revision descending - -=cut -sub gethistory -{ - my $self = shift; - my $filename = shift; - my $tablename = $self->tablename("revision"); - - my $db_query; - $db_query = $self->{dbh}->prepare_cached("SELECT revision, filehash, commithash FROM $tablename WHERE name=? ORDER BY revision DESC",{},1); - $db_query->execute($filename); - - return $db_query->fetchall_arrayref; -} - =head2 gethistorydense This function takes a filename (with path) argument and returns an arrayofarrays @@ -3611,6 +3721,8 @@ =head2 gethistorydense The 'dense' part is a reference to a '--dense' option available for git-rev-list and other git tools that depend on it. +See also getlog(). + =cut sub gethistorydense { @@ -3622,7 +3734,15 @@ sub gethistorydense $db_query = $self->{dbh}->prepare_cached("SELECT revision, filehash, commithash FROM $tablename WHERE name=? AND filehash!='deleted' ORDER BY revision DESC",{},1); $db_query->execute($filename); - return $db_query->fetchall_arrayref; + my $result = $db_query->fetchall_arrayref; + + my $i; + for($i=0 ; $i[$i][0]="1." . $result->[$i][0]; + } + + return $result; } =head2 in_array() diff --git a/git-filter-branch.sh b/git-filter-branch.sh index 178e45305d..53142492af 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -64,37 +64,19 @@ EOF eval "$functions" -# When piped a commit, output a script to set the ident of either -# "author" or "committer +finish_ident() { + # Ensure non-empty id name. + echo "case \"\$GIT_$1_NAME\" in \"\") GIT_$1_NAME=\"\${GIT_$1_EMAIL%%@*}\" && export GIT_$1_NAME;; esac" + # And make sure everything is exported. + echo "export GIT_$1_NAME" + echo "export GIT_$1_EMAIL" + echo "export GIT_$1_DATE" +} set_ident () { - lid="$(echo "$1" | tr "[A-Z]" "[a-z]")" - uid="$(echo "$1" | tr "[a-z]" "[A-Z]")" - pick_id_script=' - /^'$lid' /{ - s/'\''/'\''\\'\'\''/g - h - s/^'$lid' \([^<]*\) <[^>]*> .*$/\1/ - s/'\''/'\''\'\'\''/g - s/.*/GIT_'$uid'_NAME='\''&'\''; export GIT_'$uid'_NAME/p - - g - s/^'$lid' [^<]* <\([^>]*\)> .*$/\1/ - s/'\''/'\''\'\'\''/g - s/.*/GIT_'$uid'_EMAIL='\''&'\''; export GIT_'$uid'_EMAIL/p - - g - s/^'$lid' [^<]* <[^>]*> \(.*\)$/@\1/ - s/'\''/'\''\'\'\''/g - s/.*/GIT_'$uid'_DATE='\''&'\''; export GIT_'$uid'_DATE/p - - q - } - ' - - LANG=C LC_ALL=C sed -ne "$pick_id_script" - # Ensure non-empty id name. - echo "case \"\$GIT_${uid}_NAME\" in \"\") GIT_${uid}_NAME=\"\${GIT_${uid}_EMAIL%%@*}\" && export GIT_${uid}_NAME;; esac" + parse_ident_from_commit author AUTHOR committer COMMITTER + finish_ident AUTHOR + finish_ident COMMITTER } USAGE="[--env-filter ] [--tree-filter ] @@ -320,10 +302,8 @@ while read commit parents; do git cat-file commit "$commit" >../commit || die "Cannot read commit $commit" - eval "$(set_ident AUTHOR <../commit)" || - die "setting author failed for commit $commit" - eval "$(set_ident COMMITTER <../commit)" || - die "setting committer failed for commit $commit" + eval "$(set_ident <../commit)" || + die "setting author/committer failed for commit $commit" eval "$filter_env" < /dev/null || die "env filter failed: $filter_env" diff --git a/git-p4.py b/git-p4.py index 882b1bbab5..551aec9417 100755 --- a/git-p4.py +++ b/git-p4.py @@ -129,6 +129,25 @@ def p4_has_command(cmd): p.communicate() return p.returncode == 0 +def p4_has_move_command(): + """See if the move command exists, that it supports -k, and that + it has not been administratively disabled. The arguments + must be correct, but the filenames do not have to exist. Use + ones with wildcards so even if they exist, it will fail.""" + + if not p4_has_command("move"): + return False + cmd = p4_build_cmd(["move", "-k", "@from", "@to"]) + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (out, err) = p.communicate() + # return code will be 1 in either case + if err.find("Invalid option") >= 0: + return False + if err.find("disabled") >= 0: + return False + # assume it failed because @... was invalid changelist + return True + def system(cmd): expand = isinstance(cmd,basestring) if verbose: @@ -169,6 +188,29 @@ def p4_reopen(type, f): def p4_move(src, dest): p4_system(["move", "-k", wildcard_encode(src), wildcard_encode(dest)]) +def p4_describe(change): + """Make sure it returns a valid result by checking for + the presence of field "time". Return a dict of the + results.""" + + ds = p4CmdList(["describe", "-s", str(change)]) + if len(ds) != 1: + die("p4 describe -s %d did not return 1 result: %s" % (change, str(ds))) + + d = ds[0] + + if "p4ExitCode" in d: + die("p4 describe -s %d exited with %d: %s" % (change, d["p4ExitCode"], + str(d))) + if "code" in d: + if d["code"] == "error": + die("p4 describe -s %d returned error code: %s" % (change, str(d))) + + if "time" not in d: + die("p4 describe -s %d returned no \"time\": %s" % (change, str(d))) + + return d + # # Canonicalize the p4 type and return a tuple of the # base type, plus any modifiers. See "p4 help filetypes" @@ -227,7 +269,7 @@ def p4_keywords_regexp_for_type(base, type_mods): pattern = r""" \$ # Starts with a dollar, followed by... (%s) # one of the keywords, followed by... - (:[^$]+)? # possibly an old expansion, followed by... + (:[^$\n]+)? # possibly an old expansion, followed by... \$ # another dollar """ % kwords return pattern @@ -871,7 +913,7 @@ def __init__(self): self.conflict_behavior = None self.isWindows = (platform.system() == "Windows") self.exportLabels = False - self.p4HasMoveCommand = p4_has_command("move") + self.p4HasMoveCommand = p4_has_move_command() def check(self): if len(p4CmdList("opened ...")) > 0: @@ -2097,6 +2139,29 @@ def streamOneP4Deletion(self, file): # handle another chunk of streaming data def streamP4FilesCb(self, marshalled): + # catch p4 errors and complain + err = None + if "code" in marshalled: + if marshalled["code"] == "error": + if "data" in marshalled: + err = marshalled["data"].rstrip() + if err: + f = None + if self.stream_have_file_info: + if "depotFile" in self.stream_file: + f = self.stream_file["depotFile"] + # force a failure in fast-import, else an empty + # commit will be made + self.gitStream.write("\n") + self.gitStream.write("die-now\n") + self.gitStream.close() + # ignore errors, but make sure it exits first + self.importProcess.wait() + if f: + die("Error from p4 print for %s: %s" % (f, err)) + else: + die("Error from p4 print: %s" % err) + if marshalled.has_key('depotFile') and self.stream_have_file_info: # start of a new file - output the old one first self.streamOneP4File(self.stream_file, self.stream_contents) @@ -2341,7 +2406,7 @@ def importP4Labels(self, stream, p4Labels): try: tmwhen = time.strptime(labelDetails['Update'], "%Y/%m/%d %H:%M:%S") except ValueError: - print "Could not convert label time %s" % labelDetail['Update'] + print "Could not convert label time %s" % labelDetails['Update'] tmwhen = 1 when = int(time.mktime(tmwhen)) @@ -2543,7 +2608,7 @@ def searchParent(self, parent, branch, target): def importChanges(self, changes): cnt = 1 for change in changes: - description = p4Cmd(["describe", str(change)]) + description = p4_describe(change) self.updateOptionDict(description) if not self.silent: @@ -2667,14 +2732,8 @@ def importHeadRevision(self, revision): # Use time from top-most change so that all git p4 clones of # the same p4 repo have the same commit SHA1s. - res = p4CmdList("describe -s %d" % newestRevision) - newestTime = None - for r in res: - if r.has_key('time'): - newestTime = int(r['time']) - if newestTime is None: - die("\"describe -s\" on newest change %d did not give a time") - details["time"] = newestTime + res = p4_describe(newestRevision) + details["time"] = res["time"] self.updateOptionDict(details) try: @@ -2864,12 +2923,13 @@ def run(self, args): self.tz = "%+03d%02d" % (- time.timezone / 3600, ((- time.timezone % 3600) / 60)) - importProcess = subprocess.Popen(["git", "fast-import"], - stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=subprocess.PIPE); - self.gitOutput = importProcess.stdout - self.gitStream = importProcess.stdin - self.gitError = importProcess.stderr + self.importProcess = subprocess.Popen(["git", "fast-import"], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE); + self.gitOutput = self.importProcess.stdout + self.gitStream = self.importProcess.stdin + self.gitError = self.importProcess.stderr if revision: self.importHeadRevision(revision) @@ -2929,7 +2989,7 @@ def run(self, args): self.importP4Labels(self.gitStream, missingP4Labels) self.gitStream.close() - if importProcess.wait() != 0: + if self.importProcess.wait() != 0: die("fast-import failed: %s" % self.gitError.read()) self.gitOutput.close() self.gitError.close() @@ -3128,7 +3188,6 @@ def main(): printUsage(commands.keys()) sys.exit(2) - cmd = "" cmdName = sys.argv[1] try: klass = commands[cmdName] diff --git a/git-parse-remote.sh b/git-parse-remote.sh index 484b2e61cc..0e87e0915e 100644 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -80,7 +80,7 @@ See git-${cmd}(1) for details If you wish to set tracking information for this branch you can do so with: - git branch --set-upstream ${branch_name#refs/heads/} $remote/ + git branch --set-upstream-to=$remote/ ${branch_name#refs/heads/} " fi exit 1 diff --git a/git-pull.sh b/git-pull.sh index 2a10047eb7..266e682f6c 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -200,6 +200,7 @@ test true = "$rebase" && { require_clean_work_tree "pull with rebase" "Please commit or stash them." fi oldremoteref= && + test -n "$curr_branch" && . git-parse-remote && remoteref="$(get_remote_merge_branch "$@" 2>/dev/null)" && oldremoteref="$(git rev-parse -q --verify "$remoteref")" && diff --git a/git-send-email.perl b/git-send-email.perl index aea66a0d47..94c7f76a15 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -56,6 +56,7 @@ sub usage { --in-reply-to * Email "In-Reply-To:" --annotate * Review each patch that will be sent in an editor. --compose * Open an editor for introduction. + --compose-encoding * Encoding to assume for introduction. --8bit-encoding * Encoding to assume 8bit mails if undeclared Sending: @@ -198,6 +199,7 @@ sub do_edit { my ($validate, $confirm); my (@suppress_cc); my ($auto_8bit_encoding); +my ($compose_encoding); my ($debug_net_smtp) = 0; # Net::SMTP, see send_message() @@ -231,6 +233,7 @@ sub do_edit { "confirm" => \$confirm, "from" => \$sender, "assume8bitencoding" => \$auto_8bit_encoding, + "composeencoding" => \$compose_encoding, ); my %config_path_settings = ( @@ -315,6 +318,7 @@ sub signal_handler { "validate!" => \$validate, "format-patch!" => \$format_patch, "8bit-encoding=s" => \$auto_8bit_encoding, + "compose-encoding=s" => \$compose_encoding, "force" => \$force, ); @@ -632,6 +636,9 @@ sub get_patch_subject { my $need_8bit_cte = file_has_nonascii($compose_filename); my $in_body = 0; my $summary_empty = 1; + if (!defined $compose_encoding) { + $compose_encoding = "UTF-8"; + } while(<$c>) { next if m/^GIT:/; if ($in_body) { @@ -641,7 +648,7 @@ sub get_patch_subject { if ($need_8bit_cte) { print $c2 "MIME-Version: 1.0\n", "Content-Type: text/plain; ", - "charset=UTF-8\n", + "charset=$compose_encoding\n", "Content-Transfer-Encoding: 8bit\n"; } } elsif (/^MIME-Version:/i) { @@ -650,9 +657,7 @@ sub get_patch_subject { $initial_subject = $1; my $subject = $initial_subject; $_ = "Subject: " . - ($subject =~ /[^[:ascii:]]/ ? - quote_rfc2047($subject) : - $subject) . + quote_subject($subject, $compose_encoding) . "\n"; } elsif (/^In-Reply-To:\s*(.+)\s*$/i) { $initial_reply_to = $1; @@ -748,16 +753,11 @@ sub file_declares_8bit_cte { } } -my $prompting = 0; if (!defined $sender) { $sender = $repoauthor || $repocommitter || ''; - $sender = ask("Who should the emails appear to be from? [$sender] ", - default => $sender, - valid_re => qr/\@.*\./, confirm_only => 1); - print "Emails will be sent from: ", $sender, "\n"; - $prompting++; } +my $prompting = 0; if (!@initial_to && !defined $to_cmd) { my $to = ask("Who should the emails be sent to (if any)? ", default => "", @@ -781,9 +781,11 @@ sub expand_one_alias { } @initial_to = expand_aliases(@initial_to); -@initial_to = (map { sanitize_address($_) } @initial_to); +@initial_to = validate_address_list(sanitize_address_list(@initial_to)); @initial_cc = expand_aliases(@initial_cc); +@initial_cc = validate_address_list(sanitize_address_list(@initial_cc)); @bcclist = expand_aliases(@bcclist); +@bcclist = validate_address_list(sanitize_address_list(@bcclist)); if ($thread && !defined $initial_reply_to && $prompting) { $initial_reply_to = ask( @@ -826,12 +828,45 @@ sub extract_valid_address { $address =~ s/^\s*<(.*)>\s*$/$1/; if ($have_email_valid) { return scalar Email::Valid->address($address); - } else { - # less robust/correct than the monster regexp in Email::Valid, - # but still does a 99% job, and one less dependency - $address =~ /($local_part_regexp\@$domain_regexp)/; - return $1; } + + # less robust/correct than the monster regexp in Email::Valid, + # but still does a 99% job, and one less dependency + return $1 if $address =~ /($local_part_regexp\@$domain_regexp)/; + return undef; +} + +sub extract_valid_address_or_die { + my $address = shift; + $address = extract_valid_address($address); + die "error: unable to extract a valid address from: $address\n" + if !$address; + return $address; +} + +sub validate_address { + my $address = shift; + while (!extract_valid_address($address)) { + print STDERR "error: unable to extract a valid address from: $address\n"; + $_ = ask("What to do with this address? ([q]uit|[d]rop|[e]dit): ", + valid_re => qr/^(?:quit|q|drop|d|edit|e)/i, + default => 'q'); + if (/^d/i) { + return undef; + } elsif (/^q/i) { + cleanup_compose_files(); + exit(0); + } + $address = ask("Who should the email be sent to (if any)? ", + default => "", + valid_re => qr/\@.*\./, confirm_only => 1); + } + return $address; +} + +sub validate_address_list { + return (grep { defined $_ } + map { validate_address($_) } @_); } # Usually don't need to change anything below here. @@ -900,9 +935,29 @@ sub is_rfc2047_quoted { $s =~ m/^(?:"[[:ascii:]]*"|=\?$token\?$token\?$encoded_text\?=)$/o; } +sub subject_needs_rfc2047_quoting { + my $s = shift; + + return ($s =~ /[^[:ascii:]]/) || ($s =~ /=\?/); +} + +sub quote_subject { + local $subject = shift; + my $encoding = shift || 'UTF-8'; + + if (subject_needs_rfc2047_quoting($subject)) { + return quote_rfc2047($subject, $encoding); + } + return $subject; +} + # use the simplest quoting being able to handle the recipient sub sanitize_address { my ($recipient) = @_; + + # remove garbage after email address + $recipient =~ s/(.*>).*$/$1/; + my ($recipient_name, $recipient_addr) = ($recipient =~ /^(.*?)\s*(<.*)/); if (not $recipient_name) { @@ -930,6 +985,10 @@ sub sanitize_address { } +sub sanitize_address_list { + return (map { sanitize_address($_) } @_); +} + # Returns the local Fully Qualified Domain Name (FQDN) if available. # # Tightly configured MTAa require that a caller sends a real DNS @@ -992,14 +1051,13 @@ sub maildomain { sub send_message { my @recipients = unique_email_list(@to); - @cc = (grep { my $cc = extract_valid_address($_); + @cc = (grep { my $cc = extract_valid_address_or_die($_); not grep { $cc eq $_ || $_ =~ /<\Q${cc}\E>$/ } @recipients } - map { sanitize_address($_) } @cc); my $to = join (",\n\t", @recipients); @recipients = unique_email_list(@recipients,@cc,@bcclist); - @recipients = (map { extract_valid_address($_) } @recipients); + @recipients = (map { extract_valid_address_or_die($_) } @recipients); my $date = format_2822_time($time++); my $gitversion = '@@GIT_VERSION@@'; if ($gitversion =~ m/..GIT_VERSION../) { @@ -1242,7 +1300,7 @@ sub send_message { foreach my $addr (parse_address_line($1)) { printf("(mbox) Adding to: %s from line '%s'\n", $addr, $_) unless $quiet; - push @to, sanitize_address($addr); + push @to, $addr; } } elsif (/^Cc:\s+(.*)$/) { @@ -1321,7 +1379,7 @@ sub send_message { } if ($broken_encoding{$t} && !is_rfc2047_quoted($subject)) { - $subject = quote_rfc2047($subject, $auto_8bit_encoding); + $subject = quote_subject($subject, $auto_8bit_encoding); } if (defined $author and $author ne $sender) { @@ -1351,6 +1409,9 @@ sub send_message { ($confirm =~ /^(?:auto|compose)$/ && $compose && $message_num == 1)); $needs_confirm = "inform" if ($needs_confirm && $confirm_unconfigured && @cc); + @to = validate_address_list(sanitize_address_list(@to)); + @cc = validate_address_list(sanitize_address_list(@cc)); + @to = (@initial_to, @to); @cc = (@initial_cc, @cc); @@ -1406,14 +1467,10 @@ sub unique_email_list { my @emails; foreach my $entry (@_) { - if (my $clean = extract_valid_address($entry)) { - $seen{$clean} ||= 0; - next if $seen{$clean}++; - push @emails, $entry; - } else { - print STDERR "W: unable to extract a valid address", - " from: $entry\n"; - } + my $clean = extract_valid_address_or_die($entry); + $seen{$clean} ||= 0; + next if $seen{$clean}++; + push @emails, $entry; } return @emails; } diff --git a/git-sh-setup.sh b/git-sh-setup.sh index ee0e0bc045..22f0aed6db 100644 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -191,28 +191,52 @@ require_clean_work_tree () { fi } +# Generate a sed script to parse identities from a commit. +# +# Reads the commit from stdin, which should be in raw format (e.g., from +# cat-file or "--pretty=raw"). +# +# The first argument specifies the ident line to parse (e.g., "author"), and +# the second specifies the environment variable to put it in (e.g., "AUTHOR" +# for "GIT_AUTHOR_*"). Multiple pairs can be given to parse author and +# committer. +pick_ident_script () { + while test $# -gt 0 + do + lid=$1; shift + uid=$1; shift + printf '%s' " + /^$lid /{ + s/'/'\\\\''/g + h + s/^$lid "'\([^<]*\) <[^>]*> .*$/\1/'" + s/.*/GIT_${uid}_NAME='&'/p + + g + s/^$lid "'[^<]* <\([^>]*\)> .*$/\1/'" + s/.*/GIT_${uid}_EMAIL='&'/p + + g + s/^$lid "'[^<]* <[^>]*> \(.*\)$/@\1/'" + s/.*/GIT_${uid}_DATE='&'/p + } + " + done + echo '/^$/q' +} + +# Create a pick-script as above and feed it to sed. Stdout is suitable for +# feeding to eval. +parse_ident_from_commit () { + LANG=C LC_ALL=C sed -ne "$(pick_ident_script "$@")" +} + +# Parse the author from a commit given as an argument. Stdout is suitable for +# feeding to eval to set the usual GIT_* ident variables. get_author_ident_from_commit () { - pick_author_script=' - /^author /{ - s/'\''/'\''\\'\'\''/g - h - s/^author \([^<]*\) <[^>]*> .*$/\1/ - s/.*/GIT_AUTHOR_NAME='\''&'\''/p - - g - s/^author [^<]* <\([^>]*\)> .*$/\1/ - s/.*/GIT_AUTHOR_EMAIL='\''&'\''/p - - g - s/^author [^<]* <[^>]*> \(.*\)$/@\1/ - s/.*/GIT_AUTHOR_DATE='\''&'\''/p - - q - } - ' encoding=$(git config i18n.commitencoding || echo UTF-8) git show -s --pretty=raw --encoding="$encoding" "$1" -- | - LANG=C LC_ALL=C sed -ne "$pick_author_script" + parse_ident_from_commit author AUTHOR } # Clear repo-local GIT_* environment variables. Useful when switching to diff --git a/git-submodule.sh b/git-submodule.sh index ab6b1107b6..2365149d0b 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -5,13 +5,13 @@ # Copyright (c) 2007 Lars Hjemli dashless=$(basename "$0" | sed -e 's/-/ /') -USAGE="[--quiet] add [-b branch] [-f|--force] [--reference ] [--] [] +USAGE="[--quiet] add [-b ] [-f|--force] [--name ] [--reference ] [--] [] or: $dashless [--quiet] status [--cached] [--recursive] [--] [...] or: $dashless [--quiet] init [--] [...] or: $dashless [--quiet] update [--init] [-N|--no-fetch] [-f|--force] [--rebase] [--reference ] [--merge] [--recursive] [--] [...] or: $dashless [--quiet] summary [--cached|--files] [--summary-limit ] [commit] [--] [...] or: $dashless [--quiet] foreach [--recursive] - or: $dashless [--quiet] sync [--] [...]" + or: $dashless [--quiet] sync [--recursive] [--] [...]" OPTIONS_SPEC= . git-sh-setup . git-sh-i18n @@ -29,6 +29,7 @@ files= nofetch= update= prefix= +custom_name= # The function takes at most 2 arguments. The first argument is the # URL that navigates to the submodule origin repo. When relative, this URL @@ -179,8 +180,9 @@ module_name() module_clone() { sm_path=$1 - url=$2 - reference="$3" + name=$2 + url=$3 + reference="$4" quiet= if test -n "$GIT_QUIET" then @@ -189,8 +191,6 @@ module_clone() gitdir= gitdir_base= - name=$(module_name "$sm_path" 2>/dev/null) - test -n "$name" || name="$sm_path" base_name=$(dirname "$name") gitdir=$(git rev-parse --git-dir) @@ -270,6 +270,10 @@ cmd_add() ;; --reference=*) reference="$1" + ;; + --name) + case "$2" in '') usage ;; esac + custom_name=$2 shift ;; --) @@ -336,6 +340,13 @@ Use -f if you really want to add it." >&2 exit 1 fi + if test -n "$custom_name" + then + sm_name="$custom_name" + else + sm_name="$sm_path" + fi + # perhaps the path exists and is already a git repo, else clone it if test -e "$sm_path" then @@ -347,8 +358,21 @@ Use -f if you really want to add it." >&2 fi else - - module_clone "$sm_path" "$realrepo" "$reference" || exit + if test -d ".git/modules/$sm_name" + then + if test -z "$force" + then + echo >&2 "$(eval_gettext "A git directory for '\$sm_name' is found locally with remote(s):")" + GIT_DIR=".git/modules/$sm_name" GIT_WORK_TREE=. git remote -v | grep '(fetch)' | sed -e s,^," ", -e s,' (fetch)',, >&2 + echo >&2 "$(eval_gettext "If you want to reuse this local git directory instead of cloning again from")" + echo >&2 " $realrepo" + echo >&2 "$(eval_gettext "use the '--force' option. If the local git directory is not the correct repo")" + die "$(eval_gettext "or you are unsure what this means choose another name with the '--name' option.")" + else + echo "$(eval_gettext "Reactivating local git directory for submodule '\$sm_name'.")" + fi + fi + module_clone "$sm_path" "$sm_name" "$realrepo" "$reference" || exit ( clear_local_git_env cd "$sm_path" && @@ -359,13 +383,13 @@ Use -f if you really want to add it." >&2 esac ) || die "$(eval_gettext "Unable to checkout submodule '\$sm_path'")" fi - git config submodule."$sm_path".url "$realrepo" + git config submodule."$sm_name".url "$realrepo" git add $force "$sm_path" || die "$(eval_gettext "Failed to add submodule '\$sm_path'")" - git config -f .gitmodules submodule."$sm_path".path "$sm_path" && - git config -f .gitmodules submodule."$sm_path".url "$repo" && + git config -f .gitmodules submodule."$sm_name".path "$sm_path" && + git config -f .gitmodules submodule."$sm_name".url "$repo" && git add --force .gitmodules || die "$(eval_gettext "Failed to register submodule '\$sm_path'")" } @@ -594,7 +618,7 @@ Maybe you want to use 'update --init'?")" if ! test -d "$sm_path"/.git -o -f "$sm_path"/.git then - module_clone "$sm_path" "$url" "$reference"|| exit + module_clone "$sm_path" "$name" "$url" "$reference" || exit cloned_modules="$cloned_modules;$name" subsha1= else @@ -926,7 +950,6 @@ cmd_summary() { cmd_status() { # parse $args after "submodule ... status". - orig_flags= while test $# -ne 0 do case "$1" in @@ -950,7 +973,6 @@ cmd_status() break ;; esac - orig_flags="$orig_flags $(git rev-parse --sq-quote "$1")" shift done @@ -990,7 +1012,7 @@ cmd_status() prefix="$displaypath/" clear_local_git_env cd "$sm_path" && - eval cmd_status "$orig_args" + eval cmd_status ) || die "$(eval_gettext "Failed to recurse into submodule path '\$sm_path'")" fi @@ -1010,6 +1032,10 @@ cmd_sync() GIT_QUIET=1 shift ;; + --recursive) + recursive=1 + shift + ;; --) shift break @@ -1051,7 +1077,7 @@ cmd_sync() if git config "submodule.$name.url" >/dev/null 2>/dev/null then - say "$(eval_gettext "Synchronizing submodule url for '\$name'")" + say "$(eval_gettext "Synchronizing submodule url for '\$prefix\$sm_path'")" git config submodule."$name".url "$super_config_url" if test -e "$sm_path"/.git @@ -1061,6 +1087,12 @@ cmd_sync() cd "$sm_path" remote=$(get_default_remote) git config remote."$remote".url "$sub_origin_url" + + if test -n "$recursive" + then + prefix="$prefix$sm_path/" + eval cmd_sync + fi ) fi fi diff --git a/git.c b/git.c index 8788b32ccd..d33f9b32a2 100644 --- a/git.c +++ b/git.c @@ -17,39 +17,6 @@ const char git_more_info_string[] = static struct startup_info git_startup_info; static int use_pager = -1; -struct pager_config { - const char *cmd; - int want; - char *value; -}; - -static int pager_command_config(const char *var, const char *value, void *data) -{ - struct pager_config *c = data; - if (!prefixcmp(var, "pager.") && !strcmp(var + 6, c->cmd)) { - int b = git_config_maybe_bool(var, value); - if (b >= 0) - c->want = b; - else { - c->want = 1; - c->value = xstrdup(value); - } - } - return 0; -} - -/* returns 0 for "no pager", 1 for "use pager", and -1 for "not specified" */ -int check_pager_config(const char *cmd) -{ - struct pager_config c; - c.cmd = cmd; - c.want = -1; - c.value = NULL; - git_config(pager_command_config, &c); - if (c.value) - pager_program = c.value; - return c.want; -} static void commit_pager_choice(void) { switch (use_pager) { diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 10ed9e51a5..0f207f2e20 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -270,16 +270,15 @@ sub evaluate_uri { our %highlight_ext = ( # main extensions, defining name of syntax; # see files in /usr/share/highlight/langDefs/ directory - map { $_ => $_ } - qw(py c cpp rb java css php sh pl js tex bib xml awk bat ini spec tcl sql make), + (map { $_ => $_ } qw(py rb java css js tex bib xml awk bat ini spec tcl sql)), # alternate extensions, see /etc/highlight/filetypes.conf - 'h' => 'c', - map { $_ => 'sh' } qw(bash zsh ksh), - map { $_ => 'cpp' } qw(cxx c++ cc), - map { $_ => 'php' } qw(php3 php4 php5 phps), - map { $_ => 'pl' } qw(perl pm), # perhaps also 'cgi' - map { $_ => 'make'} qw(mak mk), - map { $_ => 'xml' } qw(xhtml html htm), + (map { $_ => 'c' } qw(c h)), + (map { $_ => 'sh' } qw(sh bash zsh ksh)), + (map { $_ => 'cpp' } qw(cpp cxx c++ cc)), + (map { $_ => 'php' } qw(php php3 php4 php5 phps)), + (map { $_ => 'pl' } qw(pl perl pm)), # perhaps also 'cgi' + (map { $_ => 'make'} qw(make mak mk)), + (map { $_ => 'xml' } qw(xml xhtml html htm)), ); # You define site-wide feature defaults here; override them with @@ -541,7 +540,7 @@ sub evaluate_uri { # $feature{'remote_heads'}{'default'} = [1]; # To have project specific config enable override in $GITWEB_CONFIG # $feature{'remote_heads'}{'override'} = 1; - # and in project config gitweb.remote_heads = 0|1; + # and in project config gitweb.remoteheads = 0|1; 'remote_heads' => { 'sub' => sub { feature_bool('remote_heads', @_) }, 'override' => 0, @@ -2697,12 +2696,15 @@ sub git_get_project_config { # only subsection, if exists, is case sensitive, # and not lowercased by 'git config -z -l' if (my ($hi, $mi, $lo) = ($key =~ /^([^.]*)\.(.*)\.([^.]*)$/)) { + $lo =~ s/_//g; $key = join(".", lc($hi), $mi, lc($lo)); + return if ($lo =~ /\W/ || $hi =~ /\W/); } else { $key = lc($key); + $key =~ s/_//g; + return if ($key =~ /\W/); } $key =~ s/^gitweb\.//; - return if ($key =~ m/\W/); # type sanity check if (defined $type) { @@ -8055,6 +8057,7 @@ sub git_feed { $feed_type = 'history'; } $title .= " $feed_type"; + $title = esc_html($title); my $descr = git_get_project_description($project); if (defined $descr) { $descr = esc_html($descr); diff --git a/grep.c b/grep.c index edc7776677..4bd1b8b1dd 100644 --- a/grep.c +++ b/grep.c @@ -6,6 +6,183 @@ static int grep_source_load(struct grep_source *gs); static int grep_source_is_binary(struct grep_source *gs); +static struct grep_opt grep_defaults; + +/* + * Initialize the grep_defaults template with hardcoded defaults. + * We could let the compiler do this, but without C99 initializers + * the code gets unwieldy and unreadable, so... + */ +void init_grep_defaults(void) +{ + struct grep_opt *opt = &grep_defaults; + static int run_once; + + if (run_once) + return; + run_once++; + + memset(opt, 0, sizeof(*opt)); + opt->relative = 1; + opt->pathname = 1; + opt->regflags = REG_NEWLINE; + opt->max_depth = -1; + opt->pattern_type_option = GREP_PATTERN_TYPE_UNSPECIFIED; + opt->extended_regexp_option = 0; + strcpy(opt->color_context, ""); + strcpy(opt->color_filename, ""); + strcpy(opt->color_function, ""); + strcpy(opt->color_lineno, ""); + strcpy(opt->color_match, GIT_COLOR_BOLD_RED); + strcpy(opt->color_selected, ""); + strcpy(opt->color_sep, GIT_COLOR_CYAN); + opt->color = -1; +} + +static int parse_pattern_type_arg(const char *opt, const char *arg) +{ + if (!strcmp(arg, "default")) + return GREP_PATTERN_TYPE_UNSPECIFIED; + else if (!strcmp(arg, "basic")) + return GREP_PATTERN_TYPE_BRE; + else if (!strcmp(arg, "extended")) + return GREP_PATTERN_TYPE_ERE; + else if (!strcmp(arg, "fixed")) + return GREP_PATTERN_TYPE_FIXED; + else if (!strcmp(arg, "perl")) + return GREP_PATTERN_TYPE_PCRE; + die("bad %s argument: %s", opt, arg); +} + +/* + * Read the configuration file once and store it in + * the grep_defaults template. + */ +int grep_config(const char *var, const char *value, void *cb) +{ + struct grep_opt *opt = &grep_defaults; + char *color = NULL; + + if (userdiff_config(var, value) < 0) + return -1; + + if (!strcmp(var, "grep.extendedregexp")) { + if (git_config_bool(var, value)) + opt->extended_regexp_option = 1; + else + opt->extended_regexp_option = 0; + return 0; + } + + if (!strcmp(var, "grep.patterntype")) { + opt->pattern_type_option = parse_pattern_type_arg(var, value); + return 0; + } + + if (!strcmp(var, "grep.linenumber")) { + opt->linenum = git_config_bool(var, value); + return 0; + } + + if (!strcmp(var, "color.grep")) + opt->color = git_config_colorbool(var, value); + else if (!strcmp(var, "color.grep.context")) + color = opt->color_context; + else if (!strcmp(var, "color.grep.filename")) + color = opt->color_filename; + else if (!strcmp(var, "color.grep.function")) + color = opt->color_function; + else if (!strcmp(var, "color.grep.linenumber")) + color = opt->color_lineno; + else if (!strcmp(var, "color.grep.match")) + color = opt->color_match; + else if (!strcmp(var, "color.grep.selected")) + color = opt->color_selected; + else if (!strcmp(var, "color.grep.separator")) + color = opt->color_sep; + + if (color) { + if (!value) + return config_error_nonbool(var); + color_parse(value, var, color); + } + return 0; +} + +/* + * Initialize one instance of grep_opt and copy the + * default values from the template we read the configuration + * information in an earlier call to git_config(grep_config). + */ +void grep_init(struct grep_opt *opt, const char *prefix) +{ + struct grep_opt *def = &grep_defaults; + + memset(opt, 0, sizeof(*opt)); + opt->prefix = prefix; + opt->prefix_length = (prefix && *prefix) ? strlen(prefix) : 0; + opt->pattern_tail = &opt->pattern_list; + opt->header_tail = &opt->header_list; + + opt->color = def->color; + opt->extended_regexp_option = def->extended_regexp_option; + opt->pattern_type_option = def->pattern_type_option; + opt->linenum = def->linenum; + opt->max_depth = def->max_depth; + opt->pathname = def->pathname; + opt->regflags = def->regflags; + opt->relative = def->relative; + + strcpy(opt->color_context, def->color_context); + strcpy(opt->color_filename, def->color_filename); + strcpy(opt->color_function, def->color_function); + strcpy(opt->color_lineno, def->color_lineno); + strcpy(opt->color_match, def->color_match); + strcpy(opt->color_selected, def->color_selected); + strcpy(opt->color_sep, def->color_sep); +} + +void grep_commit_pattern_type(enum grep_pattern_type pattern_type, struct grep_opt *opt) +{ + if (pattern_type != GREP_PATTERN_TYPE_UNSPECIFIED) + grep_set_pattern_type_option(pattern_type, opt); + else if (opt->pattern_type_option != GREP_PATTERN_TYPE_UNSPECIFIED) + grep_set_pattern_type_option(opt->pattern_type_option, opt); + else if (opt->extended_regexp_option) + grep_set_pattern_type_option(GREP_PATTERN_TYPE_ERE, opt); +} + +void grep_set_pattern_type_option(enum grep_pattern_type pattern_type, struct grep_opt *opt) +{ + switch (pattern_type) { + case GREP_PATTERN_TYPE_UNSPECIFIED: + /* fall through */ + + case GREP_PATTERN_TYPE_BRE: + opt->fixed = 0; + opt->pcre = 0; + opt->regflags &= ~REG_EXTENDED; + break; + + case GREP_PATTERN_TYPE_ERE: + opt->fixed = 0; + opt->pcre = 0; + opt->regflags |= REG_EXTENDED; + break; + + case GREP_PATTERN_TYPE_FIXED: + opt->fixed = 1; + opt->pcre = 0; + opt->regflags &= ~REG_EXTENDED; + break; + + case GREP_PATTERN_TYPE_PCRE: + opt->fixed = 0; + opt->pcre = 1; + opt->regflags &= ~REG_EXTENDED; + break; + } +} static struct grep_pat *create_grep_pat(const char *pat, size_t patlen, const char *origin, int no, @@ -1373,7 +1550,7 @@ int grep_buffer(struct grep_opt *opt, char *buf, unsigned long size) struct grep_source gs; int r; - grep_source_init(&gs, GREP_SOURCE_BUF, NULL, NULL); + grep_source_init(&gs, GREP_SOURCE_BUF, NULL, NULL, NULL); gs.buf = buf; gs.size = size; @@ -1384,10 +1561,12 @@ int grep_buffer(struct grep_opt *opt, char *buf, unsigned long size) } void grep_source_init(struct grep_source *gs, enum grep_source_type type, - const char *name, const void *identifier) + const char *name, const char *path, + const void *identifier) { gs->type = type; gs->name = name ? xstrdup(name) : NULL; + gs->path = path ? xstrdup(path) : NULL; gs->buf = NULL; gs->size = 0; gs->driver = NULL; @@ -1409,6 +1588,8 @@ void grep_source_clear(struct grep_source *gs) { free(gs->name); gs->name = NULL; + free(gs->path); + gs->path = NULL; free(gs->identifier); gs->identifier = NULL; grep_source_clear_data(gs); @@ -1501,7 +1682,8 @@ void grep_source_load_driver(struct grep_source *gs) return; grep_attr_lock(); - gs->driver = userdiff_find_by_path(gs->name); + if (gs->path) + gs->driver = userdiff_find_by_path(gs->path); if (!gs->driver) gs->driver = userdiff_find_by_name("default"); grep_attr_unlock(); diff --git a/grep.h b/grep.h index c256ac6e15..8fc854f400 100644 --- a/grep.h +++ b/grep.h @@ -138,6 +138,12 @@ struct grep_opt { void *output_priv; }; +extern void init_grep_defaults(void); +extern int grep_config(const char *var, const char *value, void *); +extern void grep_init(struct grep_opt *, const char *prefix); +void grep_set_pattern_type_option(enum grep_pattern_type, struct grep_opt *opt); +void grep_commit_pattern_type(enum grep_pattern_type, struct grep_opt *opt); + extern void append_grep_pat(struct grep_opt *opt, const char *pat, size_t patlen, const char *origin, int no, enum grep_pat_token t); extern void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *origin, int no, enum grep_pat_token t); extern void append_header_grep_pattern(struct grep_opt *, enum grep_header_field, const char *); @@ -158,11 +164,13 @@ struct grep_source { char *buf; unsigned long size; + char *path; /* for attribute lookups */ struct userdiff_driver *driver; }; void grep_source_init(struct grep_source *gs, enum grep_source_type type, - const char *name, const void *identifier); + const char *name, const char *path, + const void *identifier); void grep_source_clear_data(struct grep_source *gs); void grep_source_clear(struct grep_source *gs); void grep_source_load_driver(struct grep_source *gs); diff --git a/http.c b/http.c index df9bb71086..0a8abf3be3 100644 --- a/http.c +++ b/http.c @@ -631,6 +631,18 @@ void run_active_slot(struct active_request_slot *slot) FD_ZERO(&excfds); curl_multi_fdset(curlm, &readfds, &writefds, &excfds, &max_fd); + /* + * It can happen that curl_multi_timeout returns a pathologically + * long timeout when curl_multi_fdset returns no file descriptors + * to read. See commit message for more details. + */ + if (max_fd < 0 && + (select_timeout.tv_sec > 0 || + select_timeout.tv_usec > 50000)) { + select_timeout.tv_sec = 0; + select_timeout.tv_usec = 50000; + } + select(max_fd+1, &readfds, &writefds, &excfds, &select_timeout); } } @@ -745,8 +757,7 @@ char *get_remote_object_url(const char *url, const char *hex, return strbuf_detach(&buf, NULL); } -int handle_curl_result(struct active_request_slot *slot, - struct slot_results *results) +int handle_curl_result(struct slot_results *results) { if (results->curl_result == CURLE_OK) { credential_approve(&http_auth); @@ -759,7 +770,6 @@ int handle_curl_result(struct active_request_slot *slot, return HTTP_NOAUTH; } else { credential_fill(&http_auth); - init_curl_http_auth(slot->curl); return HTTP_REAUTH; } } else { @@ -821,7 +831,7 @@ static int http_request(const char *url, void *result, int target, int options) if (start_active_slot(slot)) { run_active_slot(slot); - ret = handle_curl_result(slot, &results); + ret = handle_curl_result(&results); } else { error("Unable to start HTTP request for %s", url); ret = HTTP_START_FAILED; diff --git a/http.h b/http.h index 0bd1e849e1..0a80d303e3 100644 --- a/http.h +++ b/http.h @@ -78,8 +78,7 @@ 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 active_request_slot *slot, - struct slot_results *results); +extern int handle_curl_result(struct slot_results *results); #ifdef USE_CURL_MULTI extern void fill_active_slots(void); diff --git a/ident.c b/ident.c index a4bf206e2f..ac9672f607 100644 --- a/ident.c +++ b/ident.c @@ -10,7 +10,12 @@ static struct strbuf git_default_name = STRBUF_INIT; static struct strbuf git_default_email = STRBUF_INIT; static char git_default_date[50]; -int user_ident_explicitly_given; + +#define IDENT_NAME_GIVEN 01 +#define IDENT_MAIL_GIVEN 02 +#define IDENT_ALL_GIVEN (IDENT_NAME_GIVEN|IDENT_MAIL_GIVEN) +static int committer_ident_explicitly_given; +static int author_ident_explicitly_given; #ifdef NO_GECOS_IN_PWENT #define get_gecos(ignored) "&" @@ -109,7 +114,8 @@ const char *ident_default_email(void) if (email && email[0]) { strbuf_addstr(&git_default_email, email); - user_ident_explicitly_given |= IDENT_MAIL_GIVEN; + committer_ident_explicitly_given |= IDENT_MAIL_GIVEN; + author_ident_explicitly_given |= IDENT_MAIL_GIVEN; } else copy_email(xgetpwuid_self(), &git_default_email); strbuf_trim(&git_default_email); @@ -327,6 +333,10 @@ const char *fmt_name(const char *name, const char *email) const char *git_author_info(int flag) { + if (getenv("GIT_AUTHOR_NAME")) + author_ident_explicitly_given |= IDENT_NAME_GIVEN; + if (getenv("GIT_AUTHOR_EMAIL")) + author_ident_explicitly_given |= IDENT_MAIL_GIVEN; return fmt_ident(getenv("GIT_AUTHOR_NAME"), getenv("GIT_AUTHOR_EMAIL"), getenv("GIT_AUTHOR_DATE"), @@ -336,16 +346,16 @@ const char *git_author_info(int flag) const char *git_committer_info(int flag) { if (getenv("GIT_COMMITTER_NAME")) - user_ident_explicitly_given |= IDENT_NAME_GIVEN; + committer_ident_explicitly_given |= IDENT_NAME_GIVEN; if (getenv("GIT_COMMITTER_EMAIL")) - user_ident_explicitly_given |= IDENT_MAIL_GIVEN; + committer_ident_explicitly_given |= IDENT_MAIL_GIVEN; return fmt_ident(getenv("GIT_COMMITTER_NAME"), getenv("GIT_COMMITTER_EMAIL"), getenv("GIT_COMMITTER_DATE"), flag); } -int user_ident_sufficiently_given(void) +static int ident_is_sufficient(int user_ident_explicitly_given) { #ifndef WINDOWS return (user_ident_explicitly_given & IDENT_MAIL_GIVEN); @@ -354,6 +364,16 @@ int user_ident_sufficiently_given(void) #endif } +int committer_ident_sufficiently_given(void) +{ + return ident_is_sufficient(committer_ident_explicitly_given); +} + +int author_ident_sufficiently_given(void) +{ + return ident_is_sufficient(author_ident_explicitly_given); +} + int git_ident_config(const char *var, const char *value, void *data) { if (!strcmp(var, "user.name")) { @@ -361,7 +381,8 @@ int git_ident_config(const char *var, const char *value, void *data) return config_error_nonbool(var); strbuf_reset(&git_default_name); strbuf_addstr(&git_default_name, value); - user_ident_explicitly_given |= IDENT_NAME_GIVEN; + committer_ident_explicitly_given |= IDENT_NAME_GIVEN; + author_ident_explicitly_given |= IDENT_NAME_GIVEN; return 0; } @@ -370,7 +391,8 @@ int git_ident_config(const char *var, const char *value, void *data) return config_error_nonbool(var); strbuf_reset(&git_default_email); strbuf_addstr(&git_default_email, value); - user_ident_explicitly_given |= IDENT_MAIL_GIVEN; + committer_ident_explicitly_given |= IDENT_MAIL_GIVEN; + author_ident_explicitly_given |= IDENT_MAIL_GIVEN; return 0; } diff --git a/log-tree.c b/log-tree.c index c894930c18..4f86defe32 100644 --- a/log-tree.c +++ b/log-tree.c @@ -540,7 +540,6 @@ void show_log(struct rev_info *opt) struct pretty_print_context ctx = {0}; opt->loginfo = NULL; - ctx.show_notes = opt->show_notes; if (!opt->verbose_header) { graph_show_commit(opt->graph); @@ -648,6 +647,18 @@ void show_log(struct rev_info *opt) if (!commit->buffer) return; + if (opt->show_notes) { + int raw; + struct strbuf notebuf = STRBUF_INIT; + + raw = (opt->commit_format == CMIT_FMT_USERFORMAT); + format_display_notes(commit->object.sha1, ¬ebuf, + get_log_output_encoding(), raw); + ctx.notes_message = notebuf.len + ? strbuf_detach(¬ebuf, NULL) + : xcalloc(1, 1); + } + /* * And then the pretty-printed message itself */ @@ -664,6 +675,16 @@ void show_log(struct rev_info *opt) if (opt->add_signoff) append_signoff(&msgbuf, opt->add_signoff); + + if ((ctx.fmt != CMIT_FMT_USERFORMAT) && + ctx.notes_message && *ctx.notes_message) { + if (ctx.fmt == CMIT_FMT_EMAIL) { + strbuf_addstr(&msgbuf, "---\n"); + opt->shown_dashes = 1; + } + strbuf_addstr(&msgbuf, ctx.notes_message); + } + if (opt->show_log_size) { printf("log size %i\n", (int)msgbuf.len); graph_show_oneline(opt->graph); @@ -689,10 +710,12 @@ void show_log(struct rev_info *opt) } strbuf_release(&msgbuf); + free(ctx.notes_message); } int log_tree_diff_flush(struct rev_info *opt) { + opt->shown_dashes = 0; diffcore_std(&opt->diffopt); if (diff_queue_is_empty()) { @@ -704,15 +727,16 @@ int log_tree_diff_flush(struct rev_info *opt) } if (opt->loginfo && !opt->no_commit_id) { - /* When showing a verbose header (i.e. log message), - * and not in --pretty=oneline format, we would want - * an extra newline between the end of log and the - * output for readability. - */ show_log(opt); if ((opt->diffopt.output_format & ~DIFF_FORMAT_NO_OUTPUT) && opt->verbose_header && opt->commit_format != CMIT_FMT_ONELINE) { + /* + * When showing a verbose header (i.e. log message), + * and not in --pretty=oneline format, we would want + * an extra newline between the end of log and the + * diff/diffstat output for readability. + */ int pch = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH; if (opt->diffopt.output_prefix) { struct strbuf *msg = NULL; @@ -720,9 +744,20 @@ int log_tree_diff_flush(struct rev_info *opt) opt->diffopt.output_prefix_data); fwrite(msg->buf, msg->len, 1, stdout); } - if ((pch & opt->diffopt.output_format) == pch) { + + /* + * We may have shown three-dashes line early + * between notes and the log message, in which + * case we only want a blank line after the + * notes without (an extra) three-dashes line. + * Otherwise, we show the three-dashes line if + * we are showing the patch with diffstat, but + * in that case, there is no extra blank line + * after the three-dashes line. + */ + if (!opt->shown_dashes && + (pch & opt->diffopt.output_format) == pch) printf("---"); - } putchar('\n'); } } diff --git a/mailmap.c b/mailmap.c index 47aa419245..ea4b471ede 100644 --- a/mailmap.c +++ b/mailmap.c @@ -118,7 +118,7 @@ static char *parse_name_and_email(char *buffer, char **name, while (isspace(*nstart) && nstart < left) ++nstart; nend = left-1; - while (isspace(*nend) && nend > nstart) + while (nend > nstart && isspace(*nend)) --nend; *name = (nstart < nend ? nstart : NULL); diff --git a/merge-recursive.h b/merge-recursive.h index 58f3435e9e..9e090a3470 100644 --- a/merge-recursive.h +++ b/merge-recursive.h @@ -59,9 +59,4 @@ struct tree *write_tree_from_memory(struct merge_options *o); int parse_merge_opt(struct merge_options *out, const char *s); -/* builtin/merge.c */ -int try_merge_command(const char *strategy, size_t xopts_nr, - const char **xopts, struct commit_list *common, - const char *head_arg, struct commit_list *remotes); - #endif diff --git a/merge.c b/merge.c new file mode 100644 index 0000000000..70f1000fcb --- /dev/null +++ b/merge.c @@ -0,0 +1,112 @@ +#include "cache.h" +#include "commit.h" +#include "run-command.h" +#include "resolve-undo.h" +#include "tree-walk.h" +#include "unpack-trees.h" +#include "dir.h" + +static const char *merge_argument(struct commit *commit) +{ + if (commit) + return sha1_to_hex(commit->object.sha1); + else + return EMPTY_TREE_SHA1_HEX; +} + +int try_merge_command(const char *strategy, size_t xopts_nr, + const char **xopts, struct commit_list *common, + const char *head_arg, struct commit_list *remotes) +{ + const char **args; + int i = 0, x = 0, ret; + struct commit_list *j; + struct strbuf buf = STRBUF_INIT; + + args = xmalloc((4 + xopts_nr + commit_list_count(common) + + commit_list_count(remotes)) * sizeof(char *)); + strbuf_addf(&buf, "merge-%s", strategy); + args[i++] = buf.buf; + for (x = 0; x < xopts_nr; x++) { + char *s = xmalloc(strlen(xopts[x])+2+1); + strcpy(s, "--"); + strcpy(s+2, xopts[x]); + args[i++] = s; + } + for (j = common; j; j = j->next) + args[i++] = xstrdup(merge_argument(j->item)); + args[i++] = "--"; + args[i++] = head_arg; + for (j = remotes; j; j = j->next) + args[i++] = xstrdup(merge_argument(j->item)); + args[i] = NULL; + ret = run_command_v_opt(args, RUN_GIT_CMD); + strbuf_release(&buf); + i = 1; + for (x = 0; x < xopts_nr; x++) + free((void *)args[i++]); + for (j = common; j; j = j->next) + free((void *)args[i++]); + i += 2; + for (j = remotes; j; j = j->next) + free((void *)args[i++]); + free(args); + discard_cache(); + if (read_cache() < 0) + die(_("failed to read the cache")); + resolve_undo_clear(); + + return ret; +} + +int checkout_fast_forward(const unsigned char *head, + const unsigned char *remote, + int overwrite_ignore) +{ + struct tree *trees[MAX_UNPACK_TREES]; + struct unpack_trees_options opts; + struct tree_desc t[MAX_UNPACK_TREES]; + int i, fd, nr_trees = 0; + struct dir_struct dir; + struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); + + refresh_cache(REFRESH_QUIET); + + fd = hold_locked_index(lock_file, 1); + + memset(&trees, 0, sizeof(trees)); + memset(&opts, 0, sizeof(opts)); + memset(&t, 0, sizeof(t)); + if (overwrite_ignore) { + memset(&dir, 0, sizeof(dir)); + dir.flags |= DIR_SHOW_IGNORED; + setup_standard_excludes(&dir); + opts.dir = &dir; + } + + opts.head_idx = 1; + opts.src_index = &the_index; + opts.dst_index = &the_index; + opts.update = 1; + opts.verbose_update = 1; + opts.merge = 1; + opts.fn = twoway_merge; + setup_unpack_trees_porcelain(&opts, "merge"); + + trees[nr_trees] = parse_tree_indirect(head); + if (!trees[nr_trees++]) + return -1; + trees[nr_trees] = parse_tree_indirect(remote); + if (!trees[nr_trees++]) + return -1; + for (i = 0; i < nr_trees; i++) { + parse_tree(trees[i]); + init_tree_desc(t+i, trees[i]->buffer, trees[i]->size); + } + if (unpack_trees(nr_trees, t, &opts)) + return -1; + if (write_cache(fd, active_cache, active_nr) || + commit_locked_index(lock_file)) + die(_("unable to write new index file")); + return 0; +} diff --git a/mergetools/p4merge b/mergetools/p4merge index 1a45c1b0c5..295361a8aa 100644 --- a/mergetools/p4merge +++ b/mergetools/p4merge @@ -1,5 +1,30 @@ diff_cmd () { + # p4merge does not like /dev/null + rm_local= + rm_remote= + if test "/dev/null" = "$LOCAL" + then + LOCAL="./p4merge-dev-null.LOCAL.$$" + >"$LOCAL" + rm_local=true + fi + if test "/dev/null" = "$REMOTE" + then + REMOTE="./p4merge-dev-null.REMOTE.$$" + >"$REMOTE" + rm_remote=true + fi + "$merge_tool_path" "$LOCAL" "$REMOTE" + + if test -n "$rm_local" + then + rm -f "$LOCAL" + fi + if test -n "$rm_remote" + then + rm -f "$REMOTE" + fi } merge_cmd () { diff --git a/notes.c b/notes.c index bc454e1eab..f63fd572d6 100644 --- a/notes.c +++ b/notes.c @@ -848,15 +848,16 @@ int combine_notes_ignore(unsigned char *cur_sha1, return 0; } -static int string_list_add_note_lines(struct string_list *sort_uniq_list, +/* + * Add the lines from the named object to list, with trailing + * newlines removed. + */ +static int string_list_add_note_lines(struct string_list *list, const unsigned char *sha1) { char *data; unsigned long len; enum object_type t; - struct strbuf buf = STRBUF_INIT; - struct strbuf **lines = NULL; - int i, list_index; if (is_null_sha1(sha1)) return 0; @@ -868,24 +869,14 @@ static int string_list_add_note_lines(struct string_list *sort_uniq_list, return t != OBJ_BLOB || !data; } - strbuf_attach(&buf, data, len, len + 1); - lines = strbuf_split(&buf, '\n'); - - for (i = 0; lines[i]; i++) { - if (lines[i]->buf[lines[i]->len - 1] == '\n') - strbuf_setlen(lines[i], lines[i]->len - 1); - if (!lines[i]->len) - continue; /* skip empty lines */ - list_index = string_list_find_insert_index(sort_uniq_list, - lines[i]->buf, 0); - if (list_index < 0) - continue; /* skip duplicate lines */ - string_list_insert_at_index(sort_uniq_list, list_index, - lines[i]->buf); - } - - strbuf_list_free(lines); - strbuf_release(&buf); + /* + * If the last line of the file is EOL-terminated, this will + * add an empty string to the list. But it will be removed + * later, along with any empty strings that came from empty + * lines within the file. + */ + string_list_split(list, data, '\n', -1); + free(data); return 0; } @@ -901,7 +892,7 @@ static int string_list_join_lines_helper(struct string_list_item *item, int combine_notes_cat_sort_uniq(unsigned char *cur_sha1, const unsigned char *new_sha1) { - struct string_list sort_uniq_list = { NULL, 0, 0, 1 }; + struct string_list sort_uniq_list = STRING_LIST_INIT_DUP; struct strbuf buf = STRBUF_INIT; int ret = 1; @@ -910,6 +901,9 @@ int combine_notes_cat_sort_uniq(unsigned char *cur_sha1, goto out; if (string_list_add_note_lines(&sort_uniq_list, new_sha1)) goto out; + string_list_remove_empty_items(&sort_uniq_list, 0); + sort_string_list(&sort_uniq_list); + string_list_remove_duplicates(&sort_uniq_list, 0); /* create a new blob object from sort_uniq_list */ if (for_each_string_list(&sort_uniq_list, @@ -949,23 +943,18 @@ void string_list_add_refs_by_glob(struct string_list *list, const char *glob) void string_list_add_refs_from_colon_sep(struct string_list *list, const char *globs) { - struct strbuf globbuf = STRBUF_INIT; - struct strbuf **split; + struct string_list split = STRING_LIST_INIT_NODUP; + char *globs_copy = xstrdup(globs); int i; - strbuf_addstr(&globbuf, globs); - split = strbuf_split(&globbuf, ':'); + string_list_split_in_place(&split, globs_copy, ':', -1); + string_list_remove_empty_items(&split, 0); - for (i = 0; split[i]; i++) { - if (!split[i]->len) - continue; - if (split[i]->buf[split[i]->len-1] == ':') - strbuf_setlen(split[i], split[i]->len-1); - string_list_add_refs_by_glob(list, split[i]->buf); - } + for (i = 0; i < split.nr; i++) + string_list_add_refs_by_glob(list, split.items[i].string); - strbuf_list_free(split); - strbuf_release(&globbuf); + string_list_clear(&split, 0); + free(globs_copy); } static int notes_display_config(const char *k, const char *v, void *cb) @@ -1204,10 +1193,11 @@ void free_notes(struct notes_tree *t) * If the given notes_tree is NULL, the internal/default notes_tree will be * used instead. * - * 'flags' is a bitwise combination of the flags for format_display_notes. + * (raw != 0) gives the %N userformat; otherwise, the note message is given + * for human consumption. */ static void format_note(struct notes_tree *t, const unsigned char *object_sha1, - struct strbuf *sb, const char *output_encoding, int flags) + struct strbuf *sb, const char *output_encoding, int raw) { static const char utf8[] = "utf-8"; const unsigned char *sha1; @@ -1231,7 +1221,7 @@ static void format_note(struct notes_tree *t, const unsigned char *object_sha1, } if (output_encoding && *output_encoding && - strcmp(utf8, output_encoding)) { + !is_encoding_utf8(output_encoding)) { char *reencoded = reencode_string(msg, output_encoding, utf8); if (reencoded) { free(msg); @@ -1244,7 +1234,7 @@ static void format_note(struct notes_tree *t, const unsigned char *object_sha1, if (msglen && msg[msglen - 1] == '\n') msglen--; - if (flags & NOTES_SHOW_HEADER) { + if (!raw) { const char *ref = t->ref; if (!ref || !strcmp(ref, GIT_NOTES_DEFAULT_REF)) { strbuf_addstr(sb, "\nNotes:\n"); @@ -1260,7 +1250,7 @@ static void format_note(struct notes_tree *t, const unsigned char *object_sha1, for (msg_p = msg; msg_p < msg + msglen; msg_p += linelen + 1) { linelen = strchrnul(msg_p, '\n') - msg_p; - if (flags & NOTES_INDENT) + if (!raw) strbuf_addstr(sb, " "); strbuf_add(sb, msg_p, linelen); strbuf_addch(sb, '\n'); @@ -1270,13 +1260,13 @@ static void format_note(struct notes_tree *t, const unsigned char *object_sha1, } void format_display_notes(const unsigned char *object_sha1, - struct strbuf *sb, const char *output_encoding, int flags) + struct strbuf *sb, const char *output_encoding, int raw) { int i; assert(display_notes_trees); for (i = 0; display_notes_trees[i]; i++) format_note(display_notes_trees[i], object_sha1, sb, - output_encoding, flags); + output_encoding, raw); } int copy_note(struct notes_tree *t, diff --git a/notes.h b/notes.h index 3592b19d2d..3324c486a1 100644 --- a/notes.h +++ b/notes.h @@ -237,10 +237,6 @@ void prune_notes(struct notes_tree *t, int flags); */ void free_notes(struct notes_tree *t); -/* Flags controlling how notes are formatted */ -#define NOTES_SHOW_HEADER 1 -#define NOTES_INDENT 2 - struct string_list; struct display_notes_opt { @@ -274,7 +270,7 @@ void init_display_notes(struct display_notes_opt *opt); * You *must* call init_display_notes() before using this function. */ void format_display_notes(const unsigned char *object_sha1, - struct strbuf *sb, const char *output_encoding, int flags); + struct strbuf *sb, const char *output_encoding, int raw); /* * Load the notes tree from each ref listed in 'refs'. The output is diff --git a/pager.c b/pager.c index 4dcb08d967..c1ecf657fd 100644 --- a/pager.c +++ b/pager.c @@ -6,26 +6,17 @@ #define DEFAULT_PAGER "less" #endif +struct pager_config { + const char *cmd; + int want; + char *value; +}; + /* * This is split up from the rest of git so that we can do * something different on Windows. */ -#ifndef WIN32 -static void pager_preexec(void) -{ - /* - * Work around bug in "less" by not starting it until we - * have real input - */ - fd_set in; - - FD_ZERO(&in); - FD_SET(0, &in); - select(1, &in, NULL, &in, NULL); -} -#endif - static const char *pager_argv[] = { NULL, NULL }; static struct child_process pager_process; @@ -93,9 +84,6 @@ void setup_pager(void) static const char *env[] = { "LESS=FRSX", NULL }; pager_process.env = env; } -#ifndef WIN32 - pager_process.preexec_cb = pager_preexec; -#endif if (start_command(&pager_process)) return; @@ -159,3 +147,31 @@ int decimal_width(int number) i *= 10; return width; } + +static int pager_command_config(const char *var, const char *value, void *data) +{ + struct pager_config *c = data; + if (!prefixcmp(var, "pager.") && !strcmp(var + 6, c->cmd)) { + int b = git_config_maybe_bool(var, value); + if (b >= 0) + c->want = b; + else { + c->want = 1; + c->value = xstrdup(value); + } + } + return 0; +} + +/* returns 0 for "no pager", 1 for "use pager", and -1 for "not specified" */ +int check_pager_config(const char *cmd) +{ + struct pager_config c; + c.cmd = cmd; + c.want = -1; + c.value = NULL; + git_config(pager_command_config, &c); + if (c.value) + pager_program = c.value; + return c.want; +} diff --git a/po/git.pot b/po/git.pot index e5611581e6..009eacba27 100644 --- a/po/git.pot +++ b/po/git.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: Git Mailing List \n" -"POT-Creation-Date: 2012-10-16 08:38+0800\n" +"POT-Creation-Date: 2012-11-23 14:27+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -60,7 +60,7 @@ msgstr "" msgid "archive format" msgstr "" -#: archive.c:323 builtin/log.c:1079 +#: archive.c:323 builtin/log.c:1084 msgid "prefix" msgstr "" @@ -68,9 +68,9 @@ msgstr "" msgid "prepend prefix to each pathname in the archive" msgstr "" -#: archive.c:325 builtin/archive.c:91 builtin/blame.c:2389 -#: builtin/blame.c:2390 builtin/config.c:56 builtin/fast-export.c:642 -#: builtin/fast-export.c:644 builtin/grep.c:801 builtin/hash-object.c:77 +#: archive.c:325 builtin/archive.c:91 builtin/blame.c:2390 +#: builtin/blame.c:2391 builtin/config.c:55 builtin/fast-export.c:642 +#: builtin/fast-export.c:644 builtin/grep.c:715 builtin/hash-object.c:77 #: builtin/ls-files.c:494 builtin/ls-files.c:497 builtin/notes.c:540 #: builtin/notes.c:697 builtin/read-tree.c:107 parse-options.h:149 msgid "file" @@ -120,6 +120,12 @@ msgstr "" msgid "path to the remote git-upload-archive command" msgstr "" +#: attr.c:259 +msgid "" +"Negative patterns are forbidden in git attributes\n" +"Use '\\!' for literal leading exclamation." +msgstr "" + #: bundle.c:36 #, c-format msgid "'%s' does not look like a v2 bundle file" @@ -130,7 +136,7 @@ msgstr "" msgid "unrecognized header: %s%s (%d)" msgstr "" -#: bundle.c:89 builtin/commit.c:672 +#: bundle.c:89 builtin/commit.c:674 #, c-format msgid "could not open '%s'" msgstr "" @@ -140,7 +146,7 @@ msgid "Repository lacks these prerequisite commits:" msgstr "" #: bundle.c:164 sequencer.c:562 sequencer.c:994 builtin/log.c:290 -#: builtin/log.c:727 builtin/log.c:1313 builtin/log.c:1529 builtin/merge.c:347 +#: builtin/log.c:732 builtin/log.c:1319 builtin/log.c:1535 builtin/merge.c:347 #: builtin/shortlog.c:181 msgid "revision walk setup failed" msgstr "" @@ -167,7 +173,7 @@ msgstr[1] "" msgid "rev-list died" msgstr "" -#: bundle.c:300 builtin/log.c:1209 builtin/shortlog.c:284 +#: bundle.c:300 builtin/log.c:1215 builtin/shortlog.c:284 #, c-format msgid "unrecognized argument: %s" msgstr "" @@ -293,24 +299,24 @@ msgid_plural "%lu years ago" msgstr[0] "" msgstr[1] "" -#: diff.c:105 +#: diff.c:111 #, c-format -msgid " Failed to parse dirstat cut-off percentage '%.*s'\n" +msgid " Failed to parse dirstat cut-off percentage '%s'\n" msgstr "" -#: diff.c:110 +#: diff.c:116 #, c-format -msgid " Unknown dirstat parameter '%.*s'\n" +msgid " Unknown dirstat parameter '%s'\n" msgstr "" -#: diff.c:210 +#: diff.c:219 #, c-format msgid "" "Found errors in 'diff.dirstat' config variable:\n" "%s" msgstr "" -#: diff.c:3458 +#: diff.c:3467 #, c-format msgid "" "Failed to parse --dirstat/-X option parameter:\n" @@ -329,17 +335,17 @@ msgstr "" msgid "gpg failed to sign the data" msgstr "" -#: grep.c:1441 +#: grep.c:1622 #, c-format msgid "'%s': unable to read %s" msgstr "" -#: grep.c:1458 +#: grep.c:1639 #, c-format msgid "'%s': %s" msgstr "" -#: grep.c:1469 +#: grep.c:1650 #, c-format msgid "'%s': short read %s" msgstr "" @@ -391,6 +397,15 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" +#: merge.c:56 +msgid "failed to read the cache" +msgstr "" + +#: merge.c:110 builtin/checkout.c:333 builtin/checkout.c:534 +#: builtin/clone.c:586 +msgid "unable to write new index file" +msgstr "" + #: merge-recursive.c:190 #, c-format msgid "(bad commit)\n" @@ -563,7 +578,7 @@ msgstr "" msgid "Auto-merging %s" msgstr "" -#: merge-recursive.c:1633 git-submodule.sh:869 +#: merge-recursive.c:1633 git-submodule.sh:893 msgid "submodule" msgstr "" @@ -633,7 +648,7 @@ msgstr "" msgid "Could not parse object '%s'" msgstr "" -#: merge-recursive.c:2009 builtin/merge.c:696 +#: merge-recursive.c:2009 builtin/merge.c:643 msgid "Unable to write index." msgstr "" @@ -684,14 +699,14 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: sequencer.c:123 builtin/merge.c:864 builtin/merge.c:977 -#: builtin/merge.c:1087 builtin/merge.c:1097 +#: sequencer.c:123 builtin/merge.c:761 builtin/merge.c:874 builtin/merge.c:984 +#: builtin/merge.c:994 #, c-format msgid "Could not open '%s' for writing" msgstr "" -#: sequencer.c:125 builtin/merge.c:333 builtin/merge.c:867 -#: builtin/merge.c:1089 builtin/merge.c:1102 +#: sequencer.c:125 builtin/merge.c:333 builtin/merge.c:764 builtin/merge.c:986 +#: builtin/merge.c:999 #, c-format msgid "Could not write to '%s'" msgstr "" @@ -1299,7 +1314,7 @@ msgstr "" msgid "unexpected diff status %c" msgstr "" -#: builtin/add.c:67 builtin/commit.c:230 +#: builtin/add.c:67 builtin/commit.c:231 msgid "updating files failed" msgstr "" @@ -1317,7 +1332,7 @@ msgstr "" msgid "Unstaged changes after refreshing the index:" msgstr "" -#: builtin/add.c:195 builtin/add.c:460 builtin/rm.c:186 +#: builtin/add.c:195 builtin/add.c:460 builtin/rm.c:260 #, c-format msgid "pathspec '%s' did not match any files" msgstr "" @@ -1360,12 +1375,12 @@ msgstr "" #: builtin/add.c:319 builtin/clean.c:52 builtin/fetch.c:78 builtin/mv.c:63 #: builtin/prune-packed.c:76 builtin/push.c:388 builtin/remote.c:1253 -#: builtin/rm.c:133 +#: builtin/rm.c:206 msgid "dry run" msgstr "" -#: builtin/add.c:320 builtin/apply.c:4354 builtin/commit.c:1154 -#: builtin/count-objects.c:82 builtin/fsck.c:613 builtin/log.c:1477 +#: builtin/add.c:320 builtin/apply.c:4354 builtin/commit.c:1160 +#: builtin/count-objects.c:82 builtin/fsck.c:613 builtin/log.c:1483 #: builtin/mv.c:62 builtin/read-tree.c:112 msgid "be verbose" msgstr "" @@ -1374,7 +1389,7 @@ msgstr "" msgid "interactive picking" msgstr "" -#: builtin/add.c:323 builtin/checkout.c:1028 builtin/reset.c:248 +#: builtin/add.c:323 builtin/checkout.c:1031 builtin/reset.c:248 msgid "select hunks interactively" msgstr "" @@ -1441,12 +1456,12 @@ msgstr "" msgid "Maybe you wanted to say 'git add .'?\n" msgstr "" -#: builtin/add.c:421 builtin/clean.c:95 builtin/commit.c:290 builtin/mv.c:82 -#: builtin/rm.c:162 +#: builtin/add.c:421 builtin/clean.c:95 builtin/commit.c:291 builtin/mv.c:82 +#: builtin/rm.c:235 msgid "index file corrupt" msgstr "" -#: builtin/add.c:481 builtin/apply.c:4450 builtin/mv.c:229 builtin/rm.c:260 +#: builtin/add.c:481 builtin/apply.c:4450 builtin/mv.c:229 builtin/rm.c:355 msgid "Unable to write new index file" msgstr "" @@ -1975,95 +1990,95 @@ msgstr "" msgid "[rev-opts] are documented in git-rev-list(1)" msgstr "" -#: builtin/blame.c:2373 +#: builtin/blame.c:2374 msgid "Show blame entries as we find them, incrementally" msgstr "" -#: builtin/blame.c:2374 +#: builtin/blame.c:2375 msgid "Show blank SHA-1 for boundary commits (Default: off)" msgstr "" -#: builtin/blame.c:2375 +#: builtin/blame.c:2376 msgid "Do not treat root commits as boundaries (Default: off)" msgstr "" -#: builtin/blame.c:2376 +#: builtin/blame.c:2377 msgid "Show work cost statistics" msgstr "" -#: builtin/blame.c:2377 +#: builtin/blame.c:2378 msgid "Show output score for blame entries" msgstr "" -#: builtin/blame.c:2378 +#: builtin/blame.c:2379 msgid "Show original filename (Default: auto)" msgstr "" -#: builtin/blame.c:2379 +#: builtin/blame.c:2380 msgid "Show original linenumber (Default: off)" msgstr "" -#: builtin/blame.c:2380 +#: builtin/blame.c:2381 msgid "Show in a format designed for machine consumption" msgstr "" -#: builtin/blame.c:2381 +#: builtin/blame.c:2382 msgid "Show porcelain format with per-line commit information" msgstr "" -#: builtin/blame.c:2382 +#: builtin/blame.c:2383 msgid "Use the same output mode as git-annotate (Default: off)" msgstr "" -#: builtin/blame.c:2383 +#: builtin/blame.c:2384 msgid "Show raw timestamp (Default: off)" msgstr "" -#: builtin/blame.c:2384 +#: builtin/blame.c:2385 msgid "Show long commit SHA1 (Default: off)" msgstr "" -#: builtin/blame.c:2385 +#: builtin/blame.c:2386 msgid "Suppress author name and timestamp (Default: off)" msgstr "" -#: builtin/blame.c:2386 +#: builtin/blame.c:2387 msgid "Show author email instead of name (Default: off)" msgstr "" -#: builtin/blame.c:2387 +#: builtin/blame.c:2388 msgid "Ignore whitespace differences" msgstr "" -#: builtin/blame.c:2388 +#: builtin/blame.c:2389 msgid "Spend extra cycles to find better match" msgstr "" -#: builtin/blame.c:2389 +#: builtin/blame.c:2390 msgid "Use revisions from instead of calling git-rev-list" msgstr "" -#: builtin/blame.c:2390 +#: builtin/blame.c:2391 msgid "Use 's contents as the final image" msgstr "" -#: builtin/blame.c:2391 builtin/blame.c:2392 +#: builtin/blame.c:2392 builtin/blame.c:2393 msgid "score" msgstr "" -#: builtin/blame.c:2391 +#: builtin/blame.c:2392 msgid "Find line copies within and across files" msgstr "" -#: builtin/blame.c:2392 +#: builtin/blame.c:2393 msgid "Find line movements within and across files" msgstr "" -#: builtin/blame.c:2393 +#: builtin/blame.c:2394 msgid "n,m" msgstr "" -#: builtin/blame.c:2393 +#: builtin/blame.c:2394 msgid "Process only line range n,m, counting from 1" msgstr "" @@ -2097,269 +2112,269 @@ msgid "" " '%s', even though it is merged to HEAD." msgstr "" -#: builtin/branch.c:181 +#: builtin/branch.c:163 +#, c-format +msgid "Couldn't look up commit object for '%s'" +msgstr "" + +#: builtin/branch.c:167 +#, c-format +msgid "" +"The branch '%s' is not fully merged.\n" +"If you are sure you want to delete it, run 'git branch -D %s'." +msgstr "" + +#: builtin/branch.c:180 +msgid "Update of config-file failed" +msgstr "" + +#: builtin/branch.c:208 msgid "cannot use -a with -d" msgstr "" -#: builtin/branch.c:187 +#: builtin/branch.c:214 msgid "Couldn't look up commit object for HEAD" msgstr "" -#: builtin/branch.c:192 +#: builtin/branch.c:222 #, c-format msgid "Cannot delete the branch '%s' which you are currently on." msgstr "" -#: builtin/branch.c:203 +#: builtin/branch.c:235 #, c-format msgid "remote branch '%s' not found." msgstr "" -#: builtin/branch.c:204 +#: builtin/branch.c:236 #, c-format msgid "branch '%s' not found." msgstr "" -#: builtin/branch.c:211 -#, c-format -msgid "Couldn't look up commit object for '%s'" -msgstr "" - -#: builtin/branch.c:217 -#, c-format -msgid "" -"The branch '%s' is not fully merged.\n" -"If you are sure you want to delete it, run 'git branch -D %s'." -msgstr "" - -#: builtin/branch.c:226 +#: builtin/branch.c:250 #, c-format msgid "Error deleting remote branch '%s'" msgstr "" -#: builtin/branch.c:227 +#: builtin/branch.c:251 #, c-format msgid "Error deleting branch '%s'" msgstr "" -#: builtin/branch.c:234 +#: builtin/branch.c:258 #, c-format msgid "Deleted remote branch %s (was %s).\n" msgstr "" -#: builtin/branch.c:235 +#: builtin/branch.c:259 #, c-format msgid "Deleted branch %s (was %s).\n" msgstr "" -#: builtin/branch.c:240 -msgid "Update of config-file failed" -msgstr "" - -#: builtin/branch.c:338 +#: builtin/branch.c:361 #, c-format msgid "branch '%s' does not point at a commit" msgstr "" -#: builtin/branch.c:410 +#: builtin/branch.c:433 #, c-format msgid "[%s: behind %d]" msgstr "" -#: builtin/branch.c:412 +#: builtin/branch.c:435 #, c-format msgid "[behind %d]" msgstr "" -#: builtin/branch.c:416 +#: builtin/branch.c:439 #, c-format msgid "[%s: ahead %d]" msgstr "" -#: builtin/branch.c:418 +#: builtin/branch.c:441 #, c-format msgid "[ahead %d]" msgstr "" -#: builtin/branch.c:421 +#: builtin/branch.c:444 #, c-format msgid "[%s: ahead %d, behind %d]" msgstr "" -#: builtin/branch.c:424 +#: builtin/branch.c:447 #, c-format msgid "[ahead %d, behind %d]" msgstr "" -#: builtin/branch.c:537 +#: builtin/branch.c:560 msgid "(no branch)" msgstr "" -#: builtin/branch.c:602 +#: builtin/branch.c:625 msgid "some refs could not be read" msgstr "" -#: builtin/branch.c:615 +#: builtin/branch.c:638 msgid "cannot rename the current branch while not on any." msgstr "" -#: builtin/branch.c:625 +#: builtin/branch.c:648 #, c-format msgid "Invalid branch name: '%s'" msgstr "" -#: builtin/branch.c:640 +#: builtin/branch.c:663 msgid "Branch rename failed" msgstr "" -#: builtin/branch.c:644 +#: builtin/branch.c:667 #, c-format msgid "Renamed a misnamed branch '%s' away" msgstr "" -#: builtin/branch.c:648 +#: builtin/branch.c:671 #, c-format msgid "Branch renamed to %s, but HEAD is not updated!" msgstr "" -#: builtin/branch.c:655 +#: builtin/branch.c:678 msgid "Branch is renamed, but update of config-file failed" msgstr "" -#: builtin/branch.c:670 +#: builtin/branch.c:693 #, c-format msgid "malformed object name %s" msgstr "" -#: builtin/branch.c:694 +#: builtin/branch.c:717 #, c-format msgid "could not write branch description template: %s" msgstr "" -#: builtin/branch.c:724 +#: builtin/branch.c:747 msgid "Generic options" msgstr "" -#: builtin/branch.c:726 +#: builtin/branch.c:749 msgid "show hash and subject, give twice for upstream branch" msgstr "" -#: builtin/branch.c:727 +#: builtin/branch.c:750 msgid "suppress informational messages" msgstr "" -#: builtin/branch.c:728 +#: builtin/branch.c:751 msgid "set up tracking mode (see git-pull(1))" msgstr "" -#: builtin/branch.c:730 +#: builtin/branch.c:753 msgid "change upstream info" msgstr "" -#: builtin/branch.c:734 +#: builtin/branch.c:757 msgid "use colored output" msgstr "" -#: builtin/branch.c:735 +#: builtin/branch.c:758 msgid "act on remote-tracking branches" msgstr "" -#: builtin/branch.c:738 builtin/branch.c:744 builtin/branch.c:765 -#: builtin/branch.c:771 builtin/commit.c:1362 builtin/commit.c:1363 -#: builtin/commit.c:1364 builtin/commit.c:1365 builtin/tag.c:470 +#: builtin/branch.c:761 builtin/branch.c:767 builtin/branch.c:788 +#: builtin/branch.c:794 builtin/commit.c:1376 builtin/commit.c:1377 +#: builtin/commit.c:1378 builtin/commit.c:1379 builtin/tag.c:470 msgid "commit" msgstr "" -#: builtin/branch.c:739 builtin/branch.c:745 +#: builtin/branch.c:762 builtin/branch.c:768 msgid "print only branches that contain the commit" msgstr "" -#: builtin/branch.c:751 +#: builtin/branch.c:774 msgid "Specific git-branch actions:" msgstr "" -#: builtin/branch.c:752 +#: builtin/branch.c:775 msgid "list both remote-tracking and local branches" msgstr "" -#: builtin/branch.c:754 +#: builtin/branch.c:777 msgid "delete fully merged branch" msgstr "" -#: builtin/branch.c:755 +#: builtin/branch.c:778 msgid "delete branch (even if not merged)" msgstr "" -#: builtin/branch.c:756 +#: builtin/branch.c:779 msgid "move/rename a branch and its reflog" msgstr "" -#: builtin/branch.c:757 +#: builtin/branch.c:780 msgid "move/rename a branch, even if target exists" msgstr "" -#: builtin/branch.c:758 +#: builtin/branch.c:781 msgid "list branch names" msgstr "" -#: builtin/branch.c:759 +#: builtin/branch.c:782 msgid "create the branch's reflog" msgstr "" -#: builtin/branch.c:761 +#: builtin/branch.c:784 msgid "edit the description for the branch" msgstr "" -#: builtin/branch.c:762 +#: builtin/branch.c:785 msgid "force creation (when already exists)" msgstr "" -#: builtin/branch.c:765 +#: builtin/branch.c:788 msgid "print only not merged branches" msgstr "" -#: builtin/branch.c:771 +#: builtin/branch.c:794 msgid "print only merged branches" msgstr "" -#: builtin/branch.c:775 +#: builtin/branch.c:798 msgid "list branches in columns" msgstr "" -#: builtin/branch.c:788 +#: builtin/branch.c:811 msgid "Failed to resolve HEAD as a valid ref." msgstr "" -#: builtin/branch.c:793 builtin/clone.c:561 +#: builtin/branch.c:816 builtin/clone.c:561 msgid "HEAD not found below refs/heads!" msgstr "" -#: builtin/branch.c:813 +#: builtin/branch.c:836 msgid "--column and --verbose are incompatible" msgstr "" -#: builtin/branch.c:864 +#: builtin/branch.c:887 #, c-format msgid "branch '%s' does not exist" msgstr "" -#: builtin/branch.c:876 +#: builtin/branch.c:899 #, c-format msgid "Branch '%s' has no upstream information" msgstr "" -#: builtin/branch.c:891 +#: builtin/branch.c:914 msgid "-a and -r options to 'git branch' do not make sense with a branch name" msgstr "" -#: builtin/branch.c:894 +#: builtin/branch.c:917 #, c-format msgid "" "The --set-upstream flag is deprecated and will be removed. Consider using --" "track or --set-upstream-to\n" msgstr "" -#: builtin/branch.c:911 +#: builtin/branch.c:934 #, c-format msgid "" "\n" @@ -2367,12 +2382,12 @@ msgid "" "\n" msgstr "" -#: builtin/branch.c:912 +#: builtin/branch.c:935 #, c-format msgid " git branch -d %s\n" msgstr "" -#: builtin/branch.c:913 +#: builtin/branch.c:936 #, c-format msgid " git branch --set-upstream-to %s\n" msgstr "" @@ -2561,11 +2576,6 @@ msgstr "" msgid "path '%s' is unmerged" msgstr "" -#: builtin/checkout.c:333 builtin/checkout.c:534 builtin/clone.c:586 -#: builtin/merge.c:811 -msgid "unable to write new index file" -msgstr "" - #: builtin/checkout.c:448 msgid "you need to resolve your current index first" msgstr "" @@ -2594,7 +2604,7 @@ msgstr "" msgid "Switched to and reset branch '%s'\n" msgstr "" -#: builtin/checkout.c:618 +#: builtin/checkout.c:618 builtin/checkout.c:955 #, c-format msgid "Switched to a new branch '%s'\n" msgstr "" @@ -2659,124 +2669,124 @@ msgstr "" msgid "reference is not a tree: %s" msgstr "" -#: builtin/checkout.c:961 +#: builtin/checkout.c:964 msgid "paths cannot be used with switching branches" msgstr "" -#: builtin/checkout.c:964 builtin/checkout.c:968 +#: builtin/checkout.c:967 builtin/checkout.c:971 #, c-format msgid "'%s' cannot be used with switching branches" msgstr "" -#: builtin/checkout.c:972 builtin/checkout.c:975 builtin/checkout.c:980 -#: builtin/checkout.c:983 +#: builtin/checkout.c:975 builtin/checkout.c:978 builtin/checkout.c:983 +#: builtin/checkout.c:986 #, c-format msgid "'%s' cannot be used with '%s'" msgstr "" -#: builtin/checkout.c:988 +#: builtin/checkout.c:991 #, c-format msgid "Cannot switch branch to a non-commit '%s'" msgstr "" -#: builtin/checkout.c:1009 builtin/gc.c:177 +#: builtin/checkout.c:1012 builtin/gc.c:177 msgid "suppress progress reporting" msgstr "" -#: builtin/checkout.c:1010 builtin/checkout.c:1012 builtin/clone.c:89 +#: builtin/checkout.c:1013 builtin/checkout.c:1015 builtin/clone.c:89 #: builtin/remote.c:169 builtin/remote.c:171 msgid "branch" msgstr "" -#: builtin/checkout.c:1011 +#: builtin/checkout.c:1014 msgid "create and checkout a new branch" msgstr "" -#: builtin/checkout.c:1013 +#: builtin/checkout.c:1016 msgid "create/reset and checkout a branch" msgstr "" -#: builtin/checkout.c:1014 +#: builtin/checkout.c:1017 msgid "create reflog for new branch" msgstr "" -#: builtin/checkout.c:1015 +#: builtin/checkout.c:1018 msgid "detach the HEAD at named commit" msgstr "" -#: builtin/checkout.c:1016 +#: builtin/checkout.c:1019 msgid "set upstream info for new branch" msgstr "" -#: builtin/checkout.c:1018 +#: builtin/checkout.c:1021 msgid "new branch" msgstr "" -#: builtin/checkout.c:1018 +#: builtin/checkout.c:1021 msgid "new unparented branch" msgstr "" -#: builtin/checkout.c:1019 +#: builtin/checkout.c:1022 msgid "checkout our version for unmerged files" msgstr "" -#: builtin/checkout.c:1021 +#: builtin/checkout.c:1024 msgid "checkout their version for unmerged files" msgstr "" -#: builtin/checkout.c:1023 +#: builtin/checkout.c:1026 msgid "force checkout (throw away local modifications)" msgstr "" -#: builtin/checkout.c:1024 +#: builtin/checkout.c:1027 msgid "perform a 3-way merge with the new branch" msgstr "" -#: builtin/checkout.c:1025 builtin/merge.c:215 +#: builtin/checkout.c:1028 builtin/merge.c:215 msgid "update ignored files (default)" msgstr "" -#: builtin/checkout.c:1026 builtin/log.c:1111 parse-options.h:241 +#: builtin/checkout.c:1029 builtin/log.c:1116 parse-options.h:241 msgid "style" msgstr "" -#: builtin/checkout.c:1027 +#: builtin/checkout.c:1030 msgid "conflict style (merge or diff3)" msgstr "" -#: builtin/checkout.c:1030 +#: builtin/checkout.c:1033 msgid "second guess 'git checkout no-such-branch'" msgstr "" -#: builtin/checkout.c:1054 +#: builtin/checkout.c:1057 msgid "-b, -B and --orphan are mutually exclusive" msgstr "" -#: builtin/checkout.c:1071 +#: builtin/checkout.c:1074 msgid "--track needs a branch name" msgstr "" -#: builtin/checkout.c:1078 +#: builtin/checkout.c:1081 msgid "Missing branch name; try -b" msgstr "" -#: builtin/checkout.c:1113 +#: builtin/checkout.c:1116 msgid "invalid path specification" msgstr "" -#: builtin/checkout.c:1120 +#: builtin/checkout.c:1123 #, c-format msgid "" "Cannot update paths and switch to branch '%s' at the same time.\n" "Did you intend to checkout '%s' which can not be resolved as commit?" msgstr "" -#: builtin/checkout.c:1125 +#: builtin/checkout.c:1128 #, c-format msgid "git checkout: --detach does not take a path argument '%s'" msgstr "" -#: builtin/checkout.c:1129 +#: builtin/checkout.c:1132 msgid "" "git checkout: --ours/--theirs, --force and --merge are incompatible when\n" "checking out of the index." @@ -2798,8 +2808,8 @@ msgstr "" msgid "remove whole directories" msgstr "" -#: builtin/clean.c:56 builtin/describe.c:413 builtin/grep.c:803 -#: builtin/ls-files.c:491 builtin/name-rev.c:231 builtin/show-ref.c:199 +#: builtin/clean.c:56 builtin/describe.c:413 builtin/grep.c:717 +#: builtin/ls-files.c:491 builtin/name-rev.c:231 builtin/show-ref.c:182 msgid "pattern" msgstr "" @@ -2920,7 +2930,7 @@ msgstr "" msgid "path to git-upload-pack on the remote" msgstr "" -#: builtin/clone.c:93 builtin/fetch.c:83 builtin/grep.c:748 +#: builtin/clone.c:93 builtin/fetch.c:83 builtin/grep.c:662 msgid "depth" msgstr "" @@ -3134,93 +3144,93 @@ msgid "" "Otherwise, please use 'git reset'\n" msgstr "" -#: builtin/commit.c:257 +#: builtin/commit.c:258 msgid "failed to unpack HEAD tree object" msgstr "" -#: builtin/commit.c:299 +#: builtin/commit.c:300 msgid "unable to create temporary index" msgstr "" -#: builtin/commit.c:305 +#: builtin/commit.c:306 msgid "interactive add failed" msgstr "" -#: builtin/commit.c:338 builtin/commit.c:359 builtin/commit.c:409 +#: builtin/commit.c:339 builtin/commit.c:360 builtin/commit.c:410 msgid "unable to write new_index file" msgstr "" -#: builtin/commit.c:390 +#: builtin/commit.c:391 msgid "cannot do a partial commit during a merge." msgstr "" -#: builtin/commit.c:392 +#: builtin/commit.c:393 msgid "cannot do a partial commit during a cherry-pick." msgstr "" -#: builtin/commit.c:402 +#: builtin/commit.c:403 msgid "cannot read the index" msgstr "" -#: builtin/commit.c:422 +#: builtin/commit.c:423 msgid "unable to write temporary index file" msgstr "" -#: builtin/commit.c:509 builtin/commit.c:515 +#: builtin/commit.c:511 builtin/commit.c:517 #, c-format msgid "invalid commit: %s" msgstr "" -#: builtin/commit.c:538 +#: builtin/commit.c:540 msgid "malformed --author parameter" msgstr "" -#: builtin/commit.c:558 +#: builtin/commit.c:560 #, c-format msgid "Malformed ident string: '%s'" msgstr "" -#: builtin/commit.c:596 builtin/commit.c:629 builtin/commit.c:952 +#: builtin/commit.c:598 builtin/commit.c:631 builtin/commit.c:954 #, c-format msgid "could not lookup commit %s" msgstr "" -#: builtin/commit.c:608 builtin/shortlog.c:296 +#: builtin/commit.c:610 builtin/shortlog.c:296 #, c-format msgid "(reading log message from standard input)\n" msgstr "" -#: builtin/commit.c:610 +#: builtin/commit.c:612 msgid "could not read log from standard input" msgstr "" -#: builtin/commit.c:614 +#: builtin/commit.c:616 #, c-format msgid "could not read log file '%s'" msgstr "" -#: builtin/commit.c:620 +#: builtin/commit.c:622 msgid "commit has empty message" msgstr "" -#: builtin/commit.c:636 +#: builtin/commit.c:638 msgid "could not read MERGE_MSG" msgstr "" -#: builtin/commit.c:640 +#: builtin/commit.c:642 msgid "could not read SQUASH_MSG" msgstr "" -#: builtin/commit.c:644 +#: builtin/commit.c:646 #, c-format msgid "could not read '%s'" msgstr "" -#: builtin/commit.c:705 +#: builtin/commit.c:707 msgid "could not write commit template" msgstr "" -#: builtin/commit.c:716 +#: builtin/commit.c:718 #, c-format msgid "" "\n" @@ -3230,7 +3240,7 @@ msgid "" "and try again.\n" msgstr "" -#: builtin/commit.c:721 +#: builtin/commit.c:723 #, c-format msgid "" "\n" @@ -3240,356 +3250,364 @@ msgid "" "and try again.\n" msgstr "" -#: builtin/commit.c:733 +#: builtin/commit.c:735 msgid "" "Please enter the commit message for your changes. Lines starting\n" "with '#' will be ignored, and an empty message aborts the commit.\n" msgstr "" -#: builtin/commit.c:738 +#: builtin/commit.c:740 msgid "" "Please enter the commit message for your changes. Lines starting\n" "with '#' will be kept; you may remove them yourself if you want to.\n" "An empty message aborts the commit.\n" msgstr "" -#: builtin/commit.c:751 +#: builtin/commit.c:753 #, c-format msgid "%sAuthor: %s" msgstr "" -#: builtin/commit.c:758 +#: builtin/commit.c:760 #, c-format msgid "%sCommitter: %s" msgstr "" -#: builtin/commit.c:778 +#: builtin/commit.c:780 msgid "Cannot read index" msgstr "" -#: builtin/commit.c:815 +#: builtin/commit.c:817 msgid "Error building trees" msgstr "" -#: builtin/commit.c:830 builtin/tag.c:361 +#: builtin/commit.c:832 builtin/tag.c:361 #, c-format msgid "Please supply the message using either -m or -F option.\n" msgstr "" -#: builtin/commit.c:927 +#: builtin/commit.c:929 #, c-format msgid "No existing author found with '%s'" msgstr "" -#: builtin/commit.c:942 builtin/commit.c:1142 +#: builtin/commit.c:944 builtin/commit.c:1148 #, c-format msgid "Invalid untracked files mode '%s'" msgstr "" -#: builtin/commit.c:982 +#: builtin/commit.c:984 msgid "Using both --reset-author and --author does not make sense" msgstr "" -#: builtin/commit.c:993 +#: builtin/commit.c:995 msgid "You have nothing to amend." msgstr "" -#: builtin/commit.c:996 +#: builtin/commit.c:998 msgid "You are in the middle of a merge -- cannot amend." msgstr "" -#: builtin/commit.c:998 +#: builtin/commit.c:1000 msgid "You are in the middle of a cherry-pick -- cannot amend." msgstr "" -#: builtin/commit.c:1001 +#: builtin/commit.c:1003 msgid "Options --squash and --fixup cannot be used together" msgstr "" -#: builtin/commit.c:1011 +#: builtin/commit.c:1013 msgid "Only one of -c/-C/-F/--fixup can be used." msgstr "" -#: builtin/commit.c:1013 +#: builtin/commit.c:1015 msgid "Option -m cannot be combined with -c/-C/-F/--fixup." msgstr "" -#: builtin/commit.c:1021 +#: builtin/commit.c:1023 msgid "--reset-author can be used only with -C, -c or --amend." msgstr "" -#: builtin/commit.c:1038 +#: builtin/commit.c:1040 msgid "Only one of --include/--only/--all/--interactive/--patch can be used." msgstr "" -#: builtin/commit.c:1040 +#: builtin/commit.c:1042 msgid "No paths with --include/--only does not make sense." msgstr "" -#: builtin/commit.c:1042 +#: builtin/commit.c:1044 msgid "Clever... amending the last one with dirty index." msgstr "" -#: builtin/commit.c:1044 +#: builtin/commit.c:1046 msgid "Explicit paths specified without -i nor -o; assuming --only paths..." msgstr "" -#: builtin/commit.c:1054 builtin/tag.c:577 +#: builtin/commit.c:1056 builtin/tag.c:577 #, c-format msgid "Invalid cleanup mode %s" msgstr "" -#: builtin/commit.c:1059 +#: builtin/commit.c:1061 msgid "Paths with -a does not make sense." msgstr "" -#: builtin/commit.c:1156 builtin/commit.c:1384 +#: builtin/commit.c:1067 builtin/commit.c:1202 +msgid "--long and -z are incompatible" +msgstr "" + +#: builtin/commit.c:1162 builtin/commit.c:1398 msgid "show status concisely" msgstr "" -#: builtin/commit.c:1158 builtin/commit.c:1386 +#: builtin/commit.c:1164 builtin/commit.c:1400 msgid "show branch information" msgstr "" -#: builtin/commit.c:1160 builtin/commit.c:1388 builtin/push.c:389 +#: builtin/commit.c:1166 builtin/commit.c:1402 builtin/push.c:389 msgid "machine-readable output" msgstr "" -#: builtin/commit.c:1163 builtin/commit.c:1390 +#: builtin/commit.c:1169 builtin/commit.c:1404 +msgid "show status in long format (default)" +msgstr "" + +#: builtin/commit.c:1172 builtin/commit.c:1407 msgid "terminate entries with NUL" msgstr "" -#: builtin/commit.c:1165 builtin/commit.c:1393 builtin/fast-export.c:636 +#: builtin/commit.c:1174 builtin/commit.c:1410 builtin/fast-export.c:636 #: builtin/fast-export.c:639 builtin/tag.c:461 msgid "mode" msgstr "" -#: builtin/commit.c:1166 builtin/commit.c:1393 +#: builtin/commit.c:1175 builtin/commit.c:1410 msgid "show untracked files, optional modes: all, normal, no. (Default: all)" msgstr "" -#: builtin/commit.c:1169 +#: builtin/commit.c:1178 msgid "show ignored files" msgstr "" -#: builtin/commit.c:1170 parse-options.h:151 +#: builtin/commit.c:1179 parse-options.h:151 msgid "when" msgstr "" -#: builtin/commit.c:1171 +#: builtin/commit.c:1180 msgid "" "ignore changes to submodules, optional when: all, dirty, untracked. " "(Default: all)" msgstr "" -#: builtin/commit.c:1173 +#: builtin/commit.c:1182 msgid "list untracked files in columns" msgstr "" -#: builtin/commit.c:1242 +#: builtin/commit.c:1256 msgid "couldn't look up newly created commit" msgstr "" -#: builtin/commit.c:1244 +#: builtin/commit.c:1258 msgid "could not parse newly created commit" msgstr "" -#: builtin/commit.c:1285 +#: builtin/commit.c:1299 msgid "detached HEAD" msgstr "" -#: builtin/commit.c:1287 +#: builtin/commit.c:1301 msgid " (root-commit)" msgstr "" -#: builtin/commit.c:1354 +#: builtin/commit.c:1368 msgid "suppress summary after successful commit" msgstr "" -#: builtin/commit.c:1355 +#: builtin/commit.c:1369 msgid "show diff in commit message template" msgstr "" -#: builtin/commit.c:1357 +#: builtin/commit.c:1371 msgid "Commit message options" msgstr "" -#: builtin/commit.c:1358 builtin/tag.c:459 +#: builtin/commit.c:1372 builtin/tag.c:459 msgid "read message from file" msgstr "" -#: builtin/commit.c:1359 +#: builtin/commit.c:1373 msgid "author" msgstr "" -#: builtin/commit.c:1359 +#: builtin/commit.c:1373 msgid "override author for commit" msgstr "" -#: builtin/commit.c:1360 builtin/gc.c:178 +#: builtin/commit.c:1374 builtin/gc.c:178 msgid "date" msgstr "" -#: builtin/commit.c:1360 +#: builtin/commit.c:1374 msgid "override date for commit" msgstr "" -#: builtin/commit.c:1361 builtin/merge.c:206 builtin/notes.c:537 +#: builtin/commit.c:1375 builtin/merge.c:206 builtin/notes.c:537 #: builtin/notes.c:694 builtin/tag.c:457 msgid "message" msgstr "" -#: builtin/commit.c:1361 +#: builtin/commit.c:1375 msgid "commit message" msgstr "" -#: builtin/commit.c:1362 +#: builtin/commit.c:1376 msgid "reuse and edit message from specified commit" msgstr "" -#: builtin/commit.c:1363 +#: builtin/commit.c:1377 msgid "reuse message from specified commit" msgstr "" -#: builtin/commit.c:1364 +#: builtin/commit.c:1378 msgid "use autosquash formatted message to fixup specified commit" msgstr "" -#: builtin/commit.c:1365 +#: builtin/commit.c:1379 msgid "use autosquash formatted message to squash specified commit" msgstr "" -#: builtin/commit.c:1366 +#: builtin/commit.c:1380 msgid "the commit is authored by me now (used with -C/-c/--amend)" msgstr "" -#: builtin/commit.c:1367 builtin/log.c:1068 builtin/revert.c:109 +#: builtin/commit.c:1381 builtin/log.c:1073 builtin/revert.c:109 msgid "add Signed-off-by:" msgstr "" -#: builtin/commit.c:1368 +#: builtin/commit.c:1382 msgid "use specified template file" msgstr "" -#: builtin/commit.c:1369 +#: builtin/commit.c:1383 msgid "force edit of commit" msgstr "" -#: builtin/commit.c:1370 +#: builtin/commit.c:1384 msgid "default" msgstr "" -#: builtin/commit.c:1370 builtin/tag.c:462 +#: builtin/commit.c:1384 builtin/tag.c:462 msgid "how to strip spaces and #comments from message" msgstr "" -#: builtin/commit.c:1371 +#: builtin/commit.c:1385 msgid "include status in commit message template" msgstr "" -#: builtin/commit.c:1372 builtin/merge.c:213 builtin/tag.c:463 +#: builtin/commit.c:1386 builtin/merge.c:213 builtin/tag.c:463 msgid "key id" msgstr "" -#: builtin/commit.c:1373 builtin/merge.c:214 +#: builtin/commit.c:1387 builtin/merge.c:214 msgid "GPG sign commit" msgstr "" #. end commit message options -#: builtin/commit.c:1376 +#: builtin/commit.c:1390 msgid "Commit contents options" msgstr "" -#: builtin/commit.c:1377 +#: builtin/commit.c:1391 msgid "commit all changed files" msgstr "" -#: builtin/commit.c:1378 +#: builtin/commit.c:1392 msgid "add specified files to index for commit" msgstr "" -#: builtin/commit.c:1379 +#: builtin/commit.c:1393 msgid "interactively add files" msgstr "" -#: builtin/commit.c:1380 +#: builtin/commit.c:1394 msgid "interactively add changes" msgstr "" -#: builtin/commit.c:1381 +#: builtin/commit.c:1395 msgid "commit only specified files" msgstr "" -#: builtin/commit.c:1382 +#: builtin/commit.c:1396 msgid "bypass pre-commit hook" msgstr "" -#: builtin/commit.c:1383 +#: builtin/commit.c:1397 msgid "show what would be committed" msgstr "" -#: builtin/commit.c:1391 +#: builtin/commit.c:1408 msgid "amend previous commit" msgstr "" -#: builtin/commit.c:1392 +#: builtin/commit.c:1409 msgid "bypass post-rewrite hook" msgstr "" -#: builtin/commit.c:1397 +#: builtin/commit.c:1414 msgid "ok to record an empty change" msgstr "" -#: builtin/commit.c:1400 +#: builtin/commit.c:1417 msgid "ok to record a change with an empty message" msgstr "" -#: builtin/commit.c:1432 +#: builtin/commit.c:1449 msgid "could not parse HEAD commit" msgstr "" -#: builtin/commit.c:1470 builtin/merge.c:508 +#: builtin/commit.c:1487 builtin/merge.c:508 #, c-format msgid "could not open '%s' for reading" msgstr "" -#: builtin/commit.c:1477 +#: builtin/commit.c:1494 #, c-format msgid "Corrupt MERGE_HEAD file (%s)" msgstr "" -#: builtin/commit.c:1484 +#: builtin/commit.c:1501 msgid "could not read MERGE_MODE" msgstr "" -#: builtin/commit.c:1503 +#: builtin/commit.c:1520 #, c-format msgid "could not read commit message: %s" msgstr "" -#: builtin/commit.c:1517 +#: builtin/commit.c:1534 #, c-format msgid "Aborting commit; you did not edit the message.\n" msgstr "" -#: builtin/commit.c:1522 +#: builtin/commit.c:1539 #, c-format msgid "Aborting commit due to empty commit message.\n" msgstr "" -#: builtin/commit.c:1537 builtin/merge.c:935 builtin/merge.c:960 +#: builtin/commit.c:1554 builtin/merge.c:832 builtin/merge.c:857 msgid "failed to write commit object" msgstr "" -#: builtin/commit.c:1558 +#: builtin/commit.c:1575 msgid "cannot lock HEAD ref" msgstr "" -#: builtin/commit.c:1562 +#: builtin/commit.c:1579 msgid "cannot update HEAD ref" msgstr "" -#: builtin/commit.c:1573 +#: builtin/commit.c:1590 msgid "" "Repository has been updated, but unable to write\n" "new_index file. Check that disk is not full or quota is\n" @@ -3600,115 +3618,115 @@ msgstr "" msgid "git config [options]" msgstr "" -#: builtin/config.c:52 +#: builtin/config.c:51 msgid "Config file location" msgstr "" -#: builtin/config.c:53 +#: builtin/config.c:52 msgid "use global config file" msgstr "" -#: builtin/config.c:54 +#: builtin/config.c:53 msgid "use system config file" msgstr "" -#: builtin/config.c:55 +#: builtin/config.c:54 msgid "use repository config file" msgstr "" -#: builtin/config.c:56 +#: builtin/config.c:55 msgid "use given config file" msgstr "" -#: builtin/config.c:57 +#: builtin/config.c:56 msgid "Action" msgstr "" -#: builtin/config.c:58 +#: builtin/config.c:57 msgid "get value: name [value-regex]" msgstr "" -#: builtin/config.c:59 +#: builtin/config.c:58 msgid "get all values: key [value-regex]" msgstr "" -#: builtin/config.c:60 +#: builtin/config.c:59 msgid "get values for regexp: name-regex [value-regex]" msgstr "" -#: builtin/config.c:61 +#: builtin/config.c:60 msgid "replace all matching variables: name value [value_regex]" msgstr "" -#: builtin/config.c:62 +#: builtin/config.c:61 msgid "add a new variable: name value" msgstr "" -#: builtin/config.c:63 +#: builtin/config.c:62 msgid "remove a variable: name [value-regex]" msgstr "" -#: builtin/config.c:64 +#: builtin/config.c:63 msgid "remove all matches: name [value-regex]" msgstr "" -#: builtin/config.c:65 +#: builtin/config.c:64 msgid "rename section: old-name new-name" msgstr "" -#: builtin/config.c:66 +#: builtin/config.c:65 msgid "remove a section: name" msgstr "" -#: builtin/config.c:67 +#: builtin/config.c:66 msgid "list all" msgstr "" -#: builtin/config.c:68 +#: builtin/config.c:67 msgid "open an editor" msgstr "" -#: builtin/config.c:69 builtin/config.c:70 +#: builtin/config.c:68 builtin/config.c:69 msgid "slot" msgstr "" -#: builtin/config.c:69 +#: builtin/config.c:68 msgid "find the color configured: [default]" msgstr "" -#: builtin/config.c:70 +#: builtin/config.c:69 msgid "find the color setting: [stdout-is-tty]" msgstr "" -#: builtin/config.c:71 +#: builtin/config.c:70 msgid "Type" msgstr "" -#: builtin/config.c:72 +#: builtin/config.c:71 msgid "value is \"true\" or \"false\"" msgstr "" -#: builtin/config.c:73 +#: builtin/config.c:72 msgid "value is decimal number" msgstr "" -#: builtin/config.c:74 +#: builtin/config.c:73 msgid "value is --bool or --int" msgstr "" -#: builtin/config.c:75 +#: builtin/config.c:74 msgid "value is a path (file or directory name)" msgstr "" -#: builtin/config.c:76 +#: builtin/config.c:75 msgid "Other" msgstr "" -#: builtin/config.c:77 +#: builtin/config.c:76 msgid "terminate values with NUL byte" msgstr "" -#: builtin/config.c:78 +#: builtin/config.c:77 msgid "respect include directives on lookup" msgstr "" @@ -3851,31 +3869,31 @@ msgstr "" msgid "'%s': not a regular file or symlink" msgstr "" -#: builtin/diff.c:224 +#: builtin/diff.c:228 #, c-format msgid "invalid option: %s" msgstr "" -#: builtin/diff.c:301 +#: builtin/diff.c:305 msgid "Not a git repository" msgstr "" -#: builtin/diff.c:344 +#: builtin/diff.c:348 #, c-format msgid "invalid object '%s' given." msgstr "" -#: builtin/diff.c:349 +#: builtin/diff.c:353 #, c-format msgid "more than %d trees given: '%s'" msgstr "" -#: builtin/diff.c:359 +#: builtin/diff.c:363 #, c-format msgid "more than two blobs given: '%s'" msgstr "" -#: builtin/diff.c:367 +#: builtin/diff.c:371 #, c-format msgid "unhandled object '%s' given." msgstr "" @@ -3988,7 +4006,7 @@ msgstr "" msgid "deepen history of shallow clone" msgstr "" -#: builtin/fetch.c:85 builtin/log.c:1083 +#: builtin/fetch.c:85 builtin/log.c:1088 msgid "dir" msgstr "" @@ -4155,8 +4173,8 @@ msgstr "" msgid "git fmt-merge-msg [-m ] [--log[=]|--no-log] [--file ]" msgstr "" -#: builtin/fmt-merge-msg.c:653 builtin/fmt-merge-msg.c:656 builtin/grep.c:787 -#: builtin/merge.c:188 builtin/show-branch.c:656 builtin/show-ref.c:192 +#: builtin/fmt-merge-msg.c:653 builtin/fmt-merge-msg.c:656 builtin/grep.c:701 +#: builtin/merge.c:188 builtin/show-branch.c:656 builtin/show-ref.c:175 #: builtin/tag.c:448 parse-options.h:133 parse-options.h:235 msgid "n" msgstr "" @@ -4312,225 +4330,225 @@ msgstr "" msgid "grep: failed to create thread: %s" msgstr "" -#: builtin/grep.c:455 +#: builtin/grep.c:365 #, c-format msgid "Failed to chdir: %s" msgstr "" -#: builtin/grep.c:531 builtin/grep.c:565 +#: builtin/grep.c:443 builtin/grep.c:478 #, c-format msgid "unable to read tree (%s)" msgstr "" -#: builtin/grep.c:579 +#: builtin/grep.c:493 #, c-format msgid "unable to grep from object of type %s" msgstr "" -#: builtin/grep.c:637 +#: builtin/grep.c:551 #, c-format msgid "switch `%c' expects a numerical value" msgstr "" -#: builtin/grep.c:654 +#: builtin/grep.c:568 #, c-format msgid "cannot open '%s'" msgstr "" -#: builtin/grep.c:729 +#: builtin/grep.c:643 msgid "search in index instead of in the work tree" msgstr "" -#: builtin/grep.c:731 +#: builtin/grep.c:645 msgid "find in contents not managed by git" msgstr "" -#: builtin/grep.c:733 +#: builtin/grep.c:647 msgid "search in both tracked and untracked files" msgstr "" -#: builtin/grep.c:735 +#: builtin/grep.c:649 msgid "search also in ignored files" msgstr "" -#: builtin/grep.c:738 +#: builtin/grep.c:652 msgid "show non-matching lines" msgstr "" -#: builtin/grep.c:740 +#: builtin/grep.c:654 msgid "case insensitive matching" msgstr "" -#: builtin/grep.c:742 +#: builtin/grep.c:656 msgid "match patterns only at word boundaries" msgstr "" -#: builtin/grep.c:744 +#: builtin/grep.c:658 msgid "process binary files as text" msgstr "" -#: builtin/grep.c:746 +#: builtin/grep.c:660 msgid "don't match patterns in binary files" msgstr "" -#: builtin/grep.c:749 +#: builtin/grep.c:663 msgid "descend at most levels" msgstr "" -#: builtin/grep.c:753 +#: builtin/grep.c:667 msgid "use extended POSIX regular expressions" msgstr "" -#: builtin/grep.c:756 +#: builtin/grep.c:670 msgid "use basic POSIX regular expressions (default)" msgstr "" -#: builtin/grep.c:759 +#: builtin/grep.c:673 msgid "interpret patterns as fixed strings" msgstr "" -#: builtin/grep.c:762 +#: builtin/grep.c:676 msgid "use Perl-compatible regular expressions" msgstr "" -#: builtin/grep.c:765 +#: builtin/grep.c:679 msgid "show line numbers" msgstr "" -#: builtin/grep.c:766 +#: builtin/grep.c:680 msgid "don't show filenames" msgstr "" -#: builtin/grep.c:767 +#: builtin/grep.c:681 msgid "show filenames" msgstr "" -#: builtin/grep.c:769 +#: builtin/grep.c:683 msgid "show filenames relative to top directory" msgstr "" -#: builtin/grep.c:771 +#: builtin/grep.c:685 msgid "show only filenames instead of matching lines" msgstr "" -#: builtin/grep.c:773 +#: builtin/grep.c:687 msgid "synonym for --files-with-matches" msgstr "" -#: builtin/grep.c:776 +#: builtin/grep.c:690 msgid "show only the names of files without match" msgstr "" -#: builtin/grep.c:778 +#: builtin/grep.c:692 msgid "print NUL after filenames" msgstr "" -#: builtin/grep.c:780 +#: builtin/grep.c:694 msgid "show the number of matches instead of matching lines" msgstr "" -#: builtin/grep.c:781 +#: builtin/grep.c:695 msgid "highlight matches" msgstr "" -#: builtin/grep.c:783 +#: builtin/grep.c:697 msgid "print empty line between matches from different files" msgstr "" -#: builtin/grep.c:785 +#: builtin/grep.c:699 msgid "show filename only once above matches from same file" msgstr "" -#: builtin/grep.c:788 +#: builtin/grep.c:702 msgid "show context lines before and after matches" msgstr "" -#: builtin/grep.c:791 +#: builtin/grep.c:705 msgid "show context lines before matches" msgstr "" -#: builtin/grep.c:793 +#: builtin/grep.c:707 msgid "show context lines after matches" msgstr "" -#: builtin/grep.c:794 +#: builtin/grep.c:708 msgid "shortcut for -C NUM" msgstr "" -#: builtin/grep.c:797 +#: builtin/grep.c:711 msgid "show a line with the function name before matches" msgstr "" -#: builtin/grep.c:799 +#: builtin/grep.c:713 msgid "show the surrounding function" msgstr "" -#: builtin/grep.c:802 +#: builtin/grep.c:716 msgid "read patterns from file" msgstr "" -#: builtin/grep.c:804 +#: builtin/grep.c:718 msgid "match " msgstr "" -#: builtin/grep.c:806 +#: builtin/grep.c:720 msgid "combine patterns specified with -e" msgstr "" -#: builtin/grep.c:818 +#: builtin/grep.c:732 msgid "indicate hit with exit status without output" msgstr "" -#: builtin/grep.c:820 +#: builtin/grep.c:734 msgid "show only matches from files that match all patterns" msgstr "" -#: builtin/grep.c:822 +#: builtin/grep.c:736 msgid "show parse tree for grep expression" msgstr "" -#: builtin/grep.c:826 +#: builtin/grep.c:740 msgid "pager" msgstr "" -#: builtin/grep.c:826 +#: builtin/grep.c:740 msgid "show matching files in the pager" msgstr "" -#: builtin/grep.c:829 +#: builtin/grep.c:743 msgid "allow calling of grep(1) (ignored by this build)" msgstr "" -#: builtin/grep.c:830 builtin/show-ref.c:201 +#: builtin/grep.c:744 builtin/show-ref.c:184 msgid "show usage" msgstr "" -#: builtin/grep.c:921 +#: builtin/grep.c:811 msgid "no pattern given." msgstr "" -#: builtin/grep.c:935 +#: builtin/grep.c:825 #, c-format msgid "bad object %s" msgstr "" -#: builtin/grep.c:976 +#: builtin/grep.c:866 msgid "--open-files-in-pager only works on the worktree" msgstr "" -#: builtin/grep.c:999 +#: builtin/grep.c:889 msgid "--cached or --untracked cannot be used with --no-index." msgstr "" -#: builtin/grep.c:1004 +#: builtin/grep.c:894 msgid "--no-index or --untracked cannot be used with revs." msgstr "" -#: builtin/grep.c:1007 +#: builtin/grep.c:897 msgid "--[no-]exclude-standard cannot be used for tracked contents." msgstr "" -#: builtin/grep.c:1015 +#: builtin/grep.c:905 msgid "both --cached and trees are given." msgstr "" @@ -5114,219 +5132,219 @@ msgstr "" msgid "Final output: %d %s\n" msgstr "" -#: builtin/log.c:403 builtin/log.c:494 +#: builtin/log.c:405 builtin/log.c:497 #, c-format msgid "Could not read object %s" msgstr "" -#: builtin/log.c:518 +#: builtin/log.c:521 #, c-format msgid "Unknown type: %d" msgstr "" -#: builtin/log.c:608 +#: builtin/log.c:613 msgid "format.headers without value" msgstr "" -#: builtin/log.c:682 +#: builtin/log.c:687 msgid "name of output directory is too long" msgstr "" -#: builtin/log.c:693 +#: builtin/log.c:698 #, c-format msgid "Cannot open patch file %s" msgstr "" -#: builtin/log.c:707 +#: builtin/log.c:712 msgid "Need exactly one range." msgstr "" -#: builtin/log.c:715 +#: builtin/log.c:720 msgid "Not a range." msgstr "" -#: builtin/log.c:789 +#: builtin/log.c:794 msgid "Cover letter needs email format" msgstr "" -#: builtin/log.c:862 +#: builtin/log.c:867 #, c-format msgid "insane in-reply-to: %s" msgstr "" -#: builtin/log.c:890 +#: builtin/log.c:895 msgid "git format-patch [options] [ | ]" msgstr "" -#: builtin/log.c:935 +#: builtin/log.c:940 msgid "Two output directories?" msgstr "" -#: builtin/log.c:1063 +#: builtin/log.c:1068 msgid "use [PATCH n/m] even with a single patch" msgstr "" -#: builtin/log.c:1066 +#: builtin/log.c:1071 msgid "use [PATCH] even with multiple patches" msgstr "" -#: builtin/log.c:1070 +#: builtin/log.c:1075 msgid "print patches to standard out" msgstr "" -#: builtin/log.c:1072 +#: builtin/log.c:1077 msgid "generate a cover letter" msgstr "" -#: builtin/log.c:1074 +#: builtin/log.c:1079 msgid "use simple number sequence for output file names" msgstr "" -#: builtin/log.c:1075 +#: builtin/log.c:1080 msgid "sfx" msgstr "" -#: builtin/log.c:1076 +#: builtin/log.c:1081 msgid "use instead of '.patch'" msgstr "" -#: builtin/log.c:1078 +#: builtin/log.c:1083 msgid "start numbering patches at instead of 1" msgstr "" -#: builtin/log.c:1080 +#: builtin/log.c:1085 msgid "Use [] instead of [PATCH]" msgstr "" -#: builtin/log.c:1083 +#: builtin/log.c:1088 msgid "store resulting files in " msgstr "" -#: builtin/log.c:1086 +#: builtin/log.c:1091 msgid "don't strip/add [PATCH]" msgstr "" -#: builtin/log.c:1089 +#: builtin/log.c:1094 msgid "don't output binary diffs" msgstr "" -#: builtin/log.c:1091 +#: builtin/log.c:1096 msgid "don't include a patch matching a commit upstream" msgstr "" -#: builtin/log.c:1093 +#: builtin/log.c:1098 msgid "show patch format instead of default (patch + stat)" msgstr "" -#: builtin/log.c:1095 +#: builtin/log.c:1100 msgid "Messaging" msgstr "" -#: builtin/log.c:1096 +#: builtin/log.c:1101 msgid "header" msgstr "" -#: builtin/log.c:1097 +#: builtin/log.c:1102 msgid "add email header" msgstr "" -#: builtin/log.c:1098 builtin/log.c:1100 +#: builtin/log.c:1103 builtin/log.c:1105 msgid "email" msgstr "" -#: builtin/log.c:1098 +#: builtin/log.c:1103 msgid "add To: header" msgstr "" -#: builtin/log.c:1100 +#: builtin/log.c:1105 msgid "add Cc: header" msgstr "" -#: builtin/log.c:1102 +#: builtin/log.c:1107 msgid "message-id" msgstr "" -#: builtin/log.c:1103 +#: builtin/log.c:1108 msgid "make first mail a reply to " msgstr "" -#: builtin/log.c:1104 builtin/log.c:1107 +#: builtin/log.c:1109 builtin/log.c:1112 msgid "boundary" msgstr "" -#: builtin/log.c:1105 +#: builtin/log.c:1110 msgid "attach the patch" msgstr "" -#: builtin/log.c:1108 +#: builtin/log.c:1113 msgid "inline the patch" msgstr "" -#: builtin/log.c:1112 +#: builtin/log.c:1117 msgid "enable message threading, styles: shallow, deep" msgstr "" -#: builtin/log.c:1114 +#: builtin/log.c:1119 msgid "signature" msgstr "" -#: builtin/log.c:1115 +#: builtin/log.c:1120 msgid "add a signature" msgstr "" -#: builtin/log.c:1117 +#: builtin/log.c:1122 msgid "don't print the patch filenames" msgstr "" -#: builtin/log.c:1157 +#: builtin/log.c:1163 #, c-format msgid "bogus committer info %s" msgstr "" -#: builtin/log.c:1202 +#: builtin/log.c:1208 msgid "-n and -k are mutually exclusive." msgstr "" -#: builtin/log.c:1204 +#: builtin/log.c:1210 msgid "--subject-prefix and -k are mutually exclusive." msgstr "" -#: builtin/log.c:1212 +#: builtin/log.c:1218 msgid "--name-only does not make sense" msgstr "" -#: builtin/log.c:1214 +#: builtin/log.c:1220 msgid "--name-status does not make sense" msgstr "" -#: builtin/log.c:1216 +#: builtin/log.c:1222 msgid "--check does not make sense" msgstr "" -#: builtin/log.c:1239 +#: builtin/log.c:1245 msgid "standard output, or directory, which one?" msgstr "" -#: builtin/log.c:1241 +#: builtin/log.c:1247 #, c-format msgid "Could not create directory '%s'" msgstr "" -#: builtin/log.c:1394 +#: builtin/log.c:1400 msgid "Failed to create output files" msgstr "" -#: builtin/log.c:1443 +#: builtin/log.c:1449 msgid "git cherry [-v] [ [ []]]" msgstr "" -#: builtin/log.c:1498 +#: builtin/log.c:1504 #, c-format msgid "" "Could not find a tracked remote branch, please specify manually.\n" msgstr "" -#: builtin/log.c:1511 builtin/log.c:1513 builtin/log.c:1525 +#: builtin/log.c:1517 builtin/log.c:1519 builtin/log.c:1531 #, c-format msgid "Unknown commit %s" msgstr "" @@ -5603,35 +5621,31 @@ msgstr "" msgid "git write-tree failed to write a tree" msgstr "" -#: builtin/merge.c:678 -msgid "failed to read the cache" -msgstr "" - -#: builtin/merge.c:709 +#: builtin/merge.c:656 msgid "Not handling anything other than two heads merge." msgstr "" -#: builtin/merge.c:723 +#: builtin/merge.c:670 #, c-format msgid "Unknown option for merge-recursive: -X%s" msgstr "" -#: builtin/merge.c:737 +#: builtin/merge.c:684 #, c-format msgid "unable to write %s" msgstr "" -#: builtin/merge.c:876 +#: builtin/merge.c:773 #, c-format msgid "Could not read from '%s'" msgstr "" -#: builtin/merge.c:885 +#: builtin/merge.c:782 #, c-format msgid "Not committing merge; use 'git commit' to complete the merge.\n" msgstr "" -#: builtin/merge.c:891 +#: builtin/merge.c:788 msgid "" "Please enter a commit message to explain why this merge is necessary,\n" "especially if it merges an updated upstream into a topic branch.\n" @@ -5640,140 +5654,140 @@ msgid "" "the commit.\n" msgstr "" -#: builtin/merge.c:915 +#: builtin/merge.c:812 msgid "Empty commit message." msgstr "" -#: builtin/merge.c:927 +#: builtin/merge.c:824 #, c-format msgid "Wonderful.\n" msgstr "" -#: builtin/merge.c:992 +#: builtin/merge.c:889 #, c-format msgid "Automatic merge failed; fix conflicts and then commit the result.\n" msgstr "" -#: builtin/merge.c:1008 +#: builtin/merge.c:905 #, c-format msgid "'%s' is not a commit" msgstr "" -#: builtin/merge.c:1049 +#: builtin/merge.c:946 msgid "No current branch." msgstr "" -#: builtin/merge.c:1051 +#: builtin/merge.c:948 msgid "No remote for the current branch." msgstr "" -#: builtin/merge.c:1053 +#: builtin/merge.c:950 msgid "No default upstream defined for the current branch." msgstr "" -#: builtin/merge.c:1058 +#: builtin/merge.c:955 #, c-format msgid "No remote tracking branch for %s from %s" msgstr "" -#: builtin/merge.c:1145 builtin/merge.c:1302 +#: builtin/merge.c:1042 builtin/merge.c:1199 #, c-format msgid "%s - not something we can merge" msgstr "" -#: builtin/merge.c:1213 +#: builtin/merge.c:1110 msgid "There is no merge to abort (MERGE_HEAD missing)." msgstr "" -#: builtin/merge.c:1229 git-pull.sh:31 +#: builtin/merge.c:1126 git-pull.sh:31 msgid "" "You have not concluded your merge (MERGE_HEAD exists).\n" "Please, commit your changes before you can merge." msgstr "" -#: builtin/merge.c:1232 git-pull.sh:34 +#: builtin/merge.c:1129 git-pull.sh:34 msgid "You have not concluded your merge (MERGE_HEAD exists)." msgstr "" -#: builtin/merge.c:1236 +#: builtin/merge.c:1133 msgid "" "You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n" "Please, commit your changes before you can merge." msgstr "" -#: builtin/merge.c:1239 +#: builtin/merge.c:1136 msgid "You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists)." msgstr "" -#: builtin/merge.c:1248 +#: builtin/merge.c:1145 msgid "You cannot combine --squash with --no-ff." msgstr "" -#: builtin/merge.c:1253 +#: builtin/merge.c:1150 msgid "You cannot combine --no-ff with --ff-only." msgstr "" -#: builtin/merge.c:1260 +#: builtin/merge.c:1157 msgid "No commit specified and merge.defaultToUpstream not set." msgstr "" -#: builtin/merge.c:1292 +#: builtin/merge.c:1189 msgid "Can merge only exactly one commit into empty head" msgstr "" -#: builtin/merge.c:1295 +#: builtin/merge.c:1192 msgid "Squash commit into empty head not supported yet" msgstr "" -#: builtin/merge.c:1297 +#: builtin/merge.c:1194 msgid "Non-fast-forward commit does not make sense into an empty head" msgstr "" -#: builtin/merge.c:1412 +#: builtin/merge.c:1309 #, c-format msgid "Updating %s..%s\n" msgstr "" -#: builtin/merge.c:1450 +#: builtin/merge.c:1348 #, c-format msgid "Trying really trivial in-index merge...\n" msgstr "" -#: builtin/merge.c:1457 +#: builtin/merge.c:1355 #, c-format msgid "Nope.\n" msgstr "" -#: builtin/merge.c:1489 +#: builtin/merge.c:1387 msgid "Not possible to fast-forward, aborting." msgstr "" -#: builtin/merge.c:1512 builtin/merge.c:1591 +#: builtin/merge.c:1410 builtin/merge.c:1489 #, c-format msgid "Rewinding the tree to pristine...\n" msgstr "" -#: builtin/merge.c:1516 +#: builtin/merge.c:1414 #, c-format msgid "Trying merge strategy %s...\n" msgstr "" -#: builtin/merge.c:1582 +#: builtin/merge.c:1480 #, c-format msgid "No merge strategy handled the merge.\n" msgstr "" -#: builtin/merge.c:1584 +#: builtin/merge.c:1482 #, c-format msgid "Merge with strategy %s failed.\n" msgstr "" -#: builtin/merge.c:1593 +#: builtin/merge.c:1491 #, c-format msgid "Using the %s to prepare resolving by hand.\n" msgstr "" -#: builtin/merge.c:1605 +#: builtin/merge.c:1503 #, c-format msgid "Automatic merge went well; stopped before committing as requested\n" msgstr "" @@ -6323,143 +6337,143 @@ msgstr "" msgid "deflate error (%d)" msgstr "" -#: builtin/pack-objects.c:2398 +#: builtin/pack-objects.c:2397 #, c-format msgid "unsupported index version %s" msgstr "" -#: builtin/pack-objects.c:2402 +#: builtin/pack-objects.c:2401 #, c-format msgid "bad index version '%s'" msgstr "" -#: builtin/pack-objects.c:2425 +#: builtin/pack-objects.c:2424 #, c-format msgid "option %s does not accept negative form" msgstr "" -#: builtin/pack-objects.c:2429 +#: builtin/pack-objects.c:2428 #, c-format msgid "unable to parse value '%s' for option %s" msgstr "" -#: builtin/pack-objects.c:2448 +#: builtin/pack-objects.c:2447 msgid "do not show progress meter" msgstr "" -#: builtin/pack-objects.c:2450 +#: builtin/pack-objects.c:2449 msgid "show progress meter" msgstr "" -#: builtin/pack-objects.c:2452 +#: builtin/pack-objects.c:2451 msgid "show progress meter during object writing phase" msgstr "" -#: builtin/pack-objects.c:2455 +#: builtin/pack-objects.c:2454 msgid "similar to --all-progress when progress meter is shown" msgstr "" -#: builtin/pack-objects.c:2456 +#: builtin/pack-objects.c:2455 msgid "version[,offset]" msgstr "" -#: builtin/pack-objects.c:2457 +#: builtin/pack-objects.c:2456 msgid "write the pack index file in the specified idx format version" msgstr "" -#: builtin/pack-objects.c:2460 +#: builtin/pack-objects.c:2459 msgid "maximum size of each output pack file" msgstr "" -#: builtin/pack-objects.c:2462 +#: builtin/pack-objects.c:2461 msgid "ignore borrowed objects from alternate object store" msgstr "" -#: builtin/pack-objects.c:2464 +#: builtin/pack-objects.c:2463 msgid "ignore packed objects" msgstr "" -#: builtin/pack-objects.c:2466 +#: builtin/pack-objects.c:2465 msgid "limit pack window by objects" msgstr "" -#: builtin/pack-objects.c:2468 +#: builtin/pack-objects.c:2467 msgid "limit pack window by memory in addition to object limit" msgstr "" -#: builtin/pack-objects.c:2470 +#: builtin/pack-objects.c:2469 msgid "maximum length of delta chain allowed in the resulting pack" msgstr "" -#: builtin/pack-objects.c:2472 +#: builtin/pack-objects.c:2471 msgid "reuse existing deltas" msgstr "" -#: builtin/pack-objects.c:2474 +#: builtin/pack-objects.c:2473 msgid "reuse existing objects" msgstr "" -#: builtin/pack-objects.c:2476 +#: builtin/pack-objects.c:2475 msgid "use OFS_DELTA objects" msgstr "" -#: builtin/pack-objects.c:2478 +#: builtin/pack-objects.c:2477 msgid "use threads when searching for best delta matches" msgstr "" -#: builtin/pack-objects.c:2480 +#: builtin/pack-objects.c:2479 msgid "do not create an empty pack output" msgstr "" -#: builtin/pack-objects.c:2482 +#: builtin/pack-objects.c:2481 msgid "read revision arguments from standard input" msgstr "" -#: builtin/pack-objects.c:2484 +#: builtin/pack-objects.c:2483 msgid "limit the objects to those that are not yet packed" msgstr "" -#: builtin/pack-objects.c:2487 +#: builtin/pack-objects.c:2486 msgid "include objects reachable from any reference" msgstr "" -#: builtin/pack-objects.c:2490 +#: builtin/pack-objects.c:2489 msgid "include objects referred by reflog entries" msgstr "" -#: builtin/pack-objects.c:2493 +#: builtin/pack-objects.c:2492 msgid "output pack to stdout" msgstr "" -#: builtin/pack-objects.c:2495 +#: builtin/pack-objects.c:2494 msgid "include tag objects that refer to objects to be packed" msgstr "" -#: builtin/pack-objects.c:2497 +#: builtin/pack-objects.c:2496 msgid "keep unreachable objects" msgstr "" -#: builtin/pack-objects.c:2498 parse-options.h:141 +#: builtin/pack-objects.c:2497 parse-options.h:141 msgid "time" msgstr "" -#: builtin/pack-objects.c:2499 +#: builtin/pack-objects.c:2498 msgid "unpack unreachable objects newer than