From: Junio C Hamano Date: Fri, 25 Jan 2013 20:34:52 +0000 (-0800) Subject: Merge branch 'jc/doc-maintainer' X-Git-Tag: v1.8.2-rc0~86 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/bb9aa109fd3df92cde642d67ba8a331c555d11ae?hp=e510f2d610b30013675c9b08bae92a9cfc4b5907 Merge branch 'jc/doc-maintainer' Describe tools for automation that were invented since this document was originally written. * jc/doc-maintainer: howto/maintain: document "### match next" convention in jch/pu branch howto/maintain: mark titles for asciidoc Documentation: update "howto maintain git" --- diff --git a/.gitignore b/.gitignore index a188a82bb1..6669bf0c6c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,8 @@ /GIT-BUILD-OPTIONS /GIT-CFLAGS /GIT-LDFLAGS -/GIT-GUI-VARS /GIT-PREFIX +/GIT-PYTHON-VARS /GIT-SCRIPT-DEFINES /GIT-USER-AGENT /GIT-VERSION-FILE @@ -22,6 +22,7 @@ /git-bundle /git-cat-file /git-check-attr +/git-check-ignore /git-check-ref-format /git-checkout /git-checkout-index @@ -124,7 +125,8 @@ /git-remote-ftps /git-remote-fd /git-remote-ext -/git-remote-testgit +/git-remote-testpy +/git-remote-testsvn /git-repack /git-replace /git-repo-config @@ -170,7 +172,6 @@ /git-whatchanged /git-write-tree /git-core-*/?* -/gitk-git/gitk-wish /gitweb/GITWEB-BUILD-OPTIONS /gitweb/gitweb.cgi /gitweb/static/gitweb.js @@ -197,6 +198,7 @@ /test-string-list /test-subprocess /test-svn-fe +/test-wildmatch /common-cmds.h *.tar.gz *.dsc diff --git a/.mailmap b/.mailmap index bcf4f8770f..c7e8618300 100644 --- a/.mailmap +++ b/.mailmap @@ -9,7 +9,9 @@ Alex Bennée Alexander Gavrilov Aneesh Kumar K.V Brian M. Carlson +Cheng Renquan Chris Shoemaker +Dan Johnson Dana L. How Dana L. How Daniel Barkalow @@ -18,14 +20,18 @@ David Kågedal David S. Miller Deskin Miller Dirk Süsserott +Eric S. Raymond Erik Faye-Lund Fredrik Kuivinen +Frédéric Heitzmann H. Peter Anvin H. Peter Anvin H. Peter Anvin Horst H. von Brand İsmail Dönmez +Jakub Narębski Jay Soffian +Jeff King Joachim Berdal Haga Johannes Sixt Johannes Sixt @@ -41,12 +47,21 @@ Junio C Hamano Junio C Hamano Junio C Hamano Karl Hasselström +Kevin Leung Kent Engstrom Lars Doelle Lars Doelle Li Hong +Linus Torvalds +Linus Torvalds +Linus Torvalds +Linus Torvalds +Linus Torvalds +Linus Torvalds Lukas Sandström -Martin Langhoff +Marc-André Lureau +Mark Rada +Martin Langhoff Martin von Zweigbergk Michael Coleman Michael J Gruber @@ -63,11 +78,13 @@ Ralf Thielow Ramsay Allan Jones René Scharfe Robert Fitzsimons +Robert Zeh Sam Vilain Santi Béjar Sean Estabrooks Shawn O. Pearce Steven Grimm +Tay Ray Chuan Theodore Ts'o Thomas Rast Tony Luck diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines index 57da6aadeb..69f7e9b76c 100644 --- a/Documentation/CodingGuidelines +++ b/Documentation/CodingGuidelines @@ -112,6 +112,14 @@ For C programs: - We try to keep to at most 80 characters per line. + - We try to support a wide range of C compilers to compile git with, + including old ones. That means that you should not use C99 + initializers, even if a lot of compilers grok it. + + - Variables have to be declared at the beginning of the block. + + - NULL pointers shall be written as NULL, not as 0. + - When declaring pointers, the star sides with the variable name, i.e. "char *string", not "char* string" or "char * string". This makes it easier to understand code diff --git a/Documentation/Makefile b/Documentation/Makefile index 267dfe135d..971977b8aa 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -21,11 +21,33 @@ ARTICLES += git-tools ARTICLES += git-bisect-lk2009 # with their own formatting rules. SP_ARTICLES = user-manual +SP_ARTICLES += howto/new-command 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/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)) @@ -156,8 +178,6 @@ all: html man html: $(DOC_HTML) -$(DOC_HTML) $(DOC_MAN1) $(DOC_MAN5) $(DOC_MAN7): asciidoc.conf - man: man1 man5 man7 man1: $(DOC_MAN1) man5: $(DOC_MAN5) @@ -231,11 +251,11 @@ 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 -$(MAN_HTML): %.html : %.txt +$(MAN_HTML): %.html : %.txt asciidoc.conf $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ $(ASCIIDOC) -b xhtml11 -d manpage -f asciidoc.conf \ $(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) -o $@+ $< && \ @@ -248,7 +268,7 @@ manpage-base-url.xsl: manpage-base-url.xsl.in $(QUIET_XMLTO)$(RM) $@ && \ $(XMLTO) -m $(MANPAGE_XSL) $(XMLTO_EXTRA) man $< -%.xml : %.txt +%.xml : %.txt asciidoc.conf $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ $(ASCIIDOC) -b docbook -d manpage -f asciidoc.conf \ $(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) -o $@+ $< && \ @@ -264,7 +284,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 asciidoc.conf $(QUIET_ASCIIDOC)$(ASCIIDOC) -b xhtml11 -f asciidoc.conf \ $(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) $*.txt @@ -309,7 +329,7 @@ $(patsubst %.txt,%.texi,$(MAN_TXT)): %.texi : %.xml howto-index.txt: howto-index.sh $(wildcard howto/*.txt) $(QUIET_GEN)$(RM) $@+ $@ && \ - '$(SHELL_PATH_SQ)' ./howto-index.sh $(wildcard howto/*.txt) >$@+ && \ + '$(SHELL_PATH_SQ)' ./howto-index.sh $(sort $(wildcard howto/*.txt)) >$@+ && \ mv $@+ $@ $(patsubst %,%.html,$(ARTICLES)) : %.html : %.txt diff --git a/Documentation/RelNotes/1.8.0.2.txt b/Documentation/RelNotes/1.8.0.2.txt new file mode 100644 index 0000000000..8497e051de --- /dev/null +++ b/Documentation/RelNotes/1.8.0.2.txt @@ -0,0 +1,34 @@ +Git v1.8.0.2 Release Notes +========================== + +Fixes since v1.8.0.1 +-------------------- + + * Various codepaths have workaround for a common misconfiguration to + spell "UTF-8" as "utf8", but it was not used uniformly. Most + notably, mailinfo (which is used by "git am") lacked this support. + + * 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. + + * When "--stat-count" hides a diffstat for binary contents, the total + number of added and removed lines at the bottom was computed + incorrectly. + + * 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. + + * "diff --shortstat" miscounted the total number of affected files + when there were unmerged paths. + + * "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.0.3.txt b/Documentation/RelNotes/1.8.0.3.txt new file mode 100644 index 0000000000..92b1e4b363 --- /dev/null +++ b/Documentation/RelNotes/1.8.0.3.txt @@ -0,0 +1,14 @@ +Git v1.8.0.3 Release Notes +========================== + +Fixes since v1.8.0.2 +-------------------- + + * "git log -p -S" did not apply the textconv filter while + looking for the . + + * In the documentation, some invalid example e-mail addresses were + formatted into mailto: links. + +Also contains many documentation updates backported from the 'master' +branch that is preparing for the upcoming 1.8.1 release. diff --git a/Documentation/RelNotes/1.8.1.1.txt b/Documentation/RelNotes/1.8.1.1.txt new file mode 100644 index 0000000000..6cde07ba29 --- /dev/null +++ b/Documentation/RelNotes/1.8.1.1.txt @@ -0,0 +1,87 @@ +Git 1.8.1.1 Release Notes +========================= + +Fixes since v1.8.1 +------------------ + + * The attribute mechanism didn't allow limiting attributes to be + applied to only a single directory itself with "path/" like the + exclude mechanism does. + + * When attempting to read the XDG-style $HOME/.config/git/config and + finding that $HOME/.config/git is a file, we gave a wrong error + message, instead of treating the case as "a custom config file does + not exist there" and moving on. + + * After failing to create a temporary file using mkstemp(), failing + pathname was not reported correctly on some platforms. + + * http transport was wrong to ask for the username when the + authentication is done by certificate identity. + + * The behaviour visible to the end users was confusing, when they + attempt to kill a process spawned in the editor that was in turn + launched by Git with SIGINT (or SIGQUIT), as Git would catch that + signal and die. We ignore these signals now. + + * A child process that was killed by a signal (e.g. SIGINT) was + reported in an inconsistent way depending on how the process was + spawned by us, with or without a shell in between. + + * After "git add -N" and then writing a tree object out of the + index, the cache-tree data structure got corrupted. + + * "git apply" misbehaved when fixing whitespace breakages by removing + excess trailing blank lines in some corner cases. + + * A tar archive created by "git archive" recorded a directory in a + way that made NetBSD's implementation of "tar" sometimes unhappy. + + * When "git clone --separate-git-dir=$over_there" is interrupted, it + failed to remove the real location of the $GIT_DIR it created. + This was most visible when interrupting a submodule update. + + * "git fetch --mirror" and fetch that uses other forms of refspec + with wildcard used to attempt to update a symbolic ref that match + the wildcard on the receiving end, which made little sense (the + real ref that is pointed at by the symbolic ref would be updated + anyway). Symbolic refs no longer are affected by such a fetch. + + * The "log --graph" codepath fell into infinite loop in some + corner cases. + + * "git merge" started calling prepare-commit-msg hook like "git + commit" does some time ago, but forgot to pay attention to the exit + status of the hook. + + * "git pack-refs" that ran in parallel to another process that + created new refs had a race that can lose new ones. + + * When a line to be wrapped has a solid run of non space characters + whose length exactly is the wrap width, "git shortlog -w" failed + to add a newline after such a line. + + * The way "git svn" asked for password using SSH_ASKPASS and + GIT_ASKPASS was not in line with the rest of the system. + + * "gitweb", when sorting by age to show repositories with new + activities first, used to sort repositories with absolutely + nothing in it early, which was not very useful. + + * "gitweb", when sorting by age to show repositories with new + activities first, used to sort repositories with absolutely + nothing in it early, which was not very useful. + + * When autoconf is used, any build on a different commit always ran + "config.status --recheck" even when unnecessary. + + * Some scripted programs written in Python did not get updated when + PYTHON_PATH changed. + + * We have been carrying a translated and long-unmaintained copy of an + old version of the tutorial; removed. + + * Portability issues in many self-test scripts have been addressed. + + +Also contains other minor fixes and documentation updates. diff --git a/Documentation/RelNotes/1.8.1.2.txt b/Documentation/RelNotes/1.8.1.2.txt new file mode 100644 index 0000000000..76ad0b3d01 --- /dev/null +++ b/Documentation/RelNotes/1.8.1.2.txt @@ -0,0 +1,13 @@ +Git 1.8.1.2 Release Notes +========================= + +Fixes since v1.8.1.1 +-------------------- + + * "git archive" did not record uncompressed size in the header when + streaming a zip archive, which confused some implementations of unzip. + + * When users spelled "cc:" in lowercase in the fake "header" in the + trailer part, "git send-email" failed to pick up the addresses from + there. As e-mail headers field names are case insensitive, this + script should follow suit and treat "cc:" and "Cc:" the same way. diff --git a/Documentation/RelNotes/1.8.1.txt b/Documentation/RelNotes/1.8.1.txt new file mode 100644 index 0000000000..d6f9555923 --- /dev/null +++ b/Documentation/RelNotes/1.8.1.txt @@ -0,0 +1,241 @@ +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 to replace it. + + +Updates since v1.8.0 +-------------------- + +UI, Workflows & Features + + * Command-line completion scripts for tcsh and zsh have been added. + + * "git-prompt" scriptlet (in contrib/completion) can be told to paint + pieces of the hints in the prompt string in colors. + + * Some documentation pages that used to ship only in the plain text + format are now formatted in HTML as well. + + * 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. + + * 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. This can be disabled with the + advice.statusHints configuration variable. + + * "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. + + * 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. + + * "git format-patch" learned the "--notes=" option to give + notes for the commit after the three-dash lines in its output. + + * "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 log --grep=" learned to honor the "grep.patterntype" + configuration set to "perl". + + * "git replace -d " now interprets as an extended + SHA-1 (e.g. HEAD~4 is allowed), 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 to do so. + + * "git send-email" used to prompt for the sender address, even when + the committer identity 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 $symref". + + +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. + + * A new remote-helper interface for Mercurial has been added to + contrib/remote-helpers. + + * The documentation for git(1) was pointing at a page at an external + site for the list of authors that no longer existed. The link has + been updated to point at an alternative site. + + +Performance, Internal Implementation, etc. + + * Compilation on Cygwin with newer header files are supported now. + + * A couple of low-level implementation updates on MinGW. + + * The logic to generate the initial advertisement from "upload-pack" + (i.e. 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. + + * 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. + + * Update "remote tracking branch" in the documentation to + "remote-tracking branch". + + * "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. + + * Various rfc2047 quoting issues around a non-ASCII name on the + From: line in the output from format-patch have been corrected. + + * 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. + + * 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. + + * 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. + + * Syntax highlighting in "gitweb" was not quite working. + + * 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. + + * Various codepaths have workaround for a common misconfiguration to + spell "UTF-8" as "utf8", but it was not used uniformly. Most + notably, mailinfo (which is used by "git am") lacked this support. + + * 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. + + * When "--stat-count" hides a diffstat for binary contents, the total + number of added and removed lines at the bottom was computed + incorrectly. + + * 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. + + * "diff --shortstat" miscounted the total number of affected files + when there were unmerged paths. + + * "update-ref -d --deref SYM" to delete a ref through a symbolic ref + that points to it did not remove it correctly. diff --git a/Documentation/RelNotes/1.8.2.txt b/Documentation/RelNotes/1.8.2.txt new file mode 100644 index 0000000000..0ae40f3b25 --- /dev/null +++ b/Documentation/RelNotes/1.8.2.txt @@ -0,0 +1,296 @@ +Git v1.8.2 Release Notes +======================== + +Backward compatibility notes +---------------------------- + +In the upcoming major release (tentatively called 1.8.2), 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. + + +Updates since v1.8.1 +-------------------- + +UI, Workflows & Features + + * Initial ports to QNX and z/OS UNIX System Services have started. + + * Output from the tests is coloured using "green is okay, yellow is + questionable, red is bad and blue is informative" scheme. + + * In bare repositories, "git shortlog" and other commands now read + mailmap files from the tip of the history, to help running these + tools in server settings. + + * Color specifiers, e.g. "%C(blue)Hello%C(reset)", used in the + "--format=" option of "git log" and friends can be disabled when + the output is not sent to a terminal by prefixing them with + "auto,", e.g. "%C(auto,blue)Hello%C(auto,reset)". + + * Scripts can ask Git that wildcard patterns in pathspecs they give do + not have any significance, i.e. take them as literal strings. + + * The pathspec code learned to grok "foo/**/bar" as a pattern that + matches "bar" in 0-or-more levels of subdirectory in "foo". + + * "git blame" (and "git diff") learned the "--no-follow" option. + + * "git cherry-pick" can be used to replay a root commit to an unborn + branch. + + * "git commit" can be told to use --cleanup=whitespace by setting the + configuration variable commit.cleanup to 'whitespace'. + + * "git fetch --mirror" and fetch that uses other forms of refspec + with wildcard used to attempt to update a symbolic ref that match + the wildcard on the receiving end, which made little sense (the + real ref that is pointed at by the symbolic ref would be updated + anyway). Symbolic refs no longer are affected by such a fetch. + + * "git format-patch" now detects more cases in which a whole branch + is being exported, and uses the description for the branch, when + asked to write a cover letter for the series. + + * "git format-patch" learned "-v $count" option, and prepends a + string "v$count-" to the names of its output files, and also + automatically sets the subject prefix to "PATCH v$count". This + allows patches from rerolled series to be stored under different + names and makes it easier to reuse cover letter messsages. + + * "git log" and friends can be told with --use-mailmap option to + rewrite the names and email addresses of people using the mailmap + mechanism. + + * "git push" now requires "-f" to update a tag, even if it is a + fast-forward, as tags are meant to be fixed points. + + * When "git rebase" fails to generate patches to be applied (e.g. due + to oom), it failed to detect the failure and instead behaved as if + there were nothing to do. A workaround to use a temporary file has + been applied, but we probably would want to revisit this later, as + it hurts the common case of not failing at all. + + * Input and preconditions to "git reset" has been loosened where + appropriate. "git reset $fromtree Makefile" requires $fromtree to + be any tree (it used to require it to be a commit), for example. + "git reset" (without options or parameters) used to error out when + you do not have any commits in your history, but it now gives you + an empty index (to match non-existent commit you are not even on). + + * "git submodule" started learning a new mode to integrate with the + tip of the remote branch (as opposed to integrating with the commit + recorded in the superproject's gitlink). + + +Foreign Interface + + * "git fast-export" has been updated for its use in the context of + the remote helper interface. + + * A new remote helper to interact with bzr has been added to contrib/. + + * "git p4" got various bugfixes around its branch handling. + + * The remote helper to interact with Hg in contrib/ has seen a few + fixes. + + +Performance, Internal Implementation, etc. + + * "git fsck" has been taught to be pickier about entries in tree + objects that should not be there, e.g. ".", ".git", and "..". + + * Matching paths with common forms of pathspecs that contain wildcard + characters has been optimized further. + + * "git reset" internals has been reworked and should be faster in + general. We tried to be careful not to break any behaviour but + there could be corner cases, especially when running the command + from a conflicted state, that we may have missed. + + * The implementation of "imap-send" has been updated to reuse xml + quoting code from http-push codepath. + + * There is a simple-minded checker for the test scripts in t/ + directory to catch most common mistakes (it is not enabled by + default). + + +Also contains minor documentation updates and code clean-ups. + + +Fixes since v1.8.1 +------------------ + +Unless otherwise noted, all the fixes since v1.8.1 in the maintenance +track are contained in this release (see release notes to them for +details). + + * An element on GIT_CEILING_DIRECTORIES list that does not name the + real path to a directory (i.e. a symbolic link) could have caused + the GIT_DIR discovery logic to escape the ceiling. + (merge 059b379 mh/ceiling later to maint). + + * When attempting to read the XDG-style $HOME/.config/git/config and + finding that $HOME/.config/git is a file, we gave a wrong error + message, instead of treating the case as "a custom config file does + not exist there" and moving on. + (merge 8f2bbe4 jn/warn-on-inaccessible-loosen later to maint). + + * The behaviour visible to the end users was confusing, when they + attempt to kill a process spawned in the editor that was in turn + launched by Git with SIGINT (or SIGQUIT), as Git would catch that + signal and die. We ignore these signals now. + (merge 1250857 pf/editor-ignore-sigint later to maint). + + * A child process that was killed by a signal (e.g. SIGINT) was + reported in an inconsistent way depending on how the process was + spawned by us, with or without a shell in between. + (merge 709ca73 jk/unify-exit-code-by-receiving-signal later to maint). + + * After failing to create a temporary file using mkstemp(), failing + pathname was not reported correctly on some platforms. + (merge f7be59b jc/mkstemp-more-careful-error-reporting later to maint). + + * The attribute mechanism didn't allow limiting attributes to be + applied to only a single directory itself with "path/" like the + exclude mechanism does. The initial implementation of this that + was merged to 'maint' and 1.8.1.2 was with a severe performance + degradations and needs to merge a fix-up topic. + (merge 9db9eec nd/fix-directory-attrs-off-by-one later to maint). + + * "git apply" misbehaved when fixing whitespace breakages by removing + excess trailing blank lines. + (merge 5de7166 jc/apply-trailing-blank-removal later to maint). + + * A tar archive created by "git archive" recorded a directory in a + way that made NetBSD's implementation of "tar" sometimes unhappy. + (merge 22f0dcd rs/leave-base-name-in-name-field-of-tar later to maint). + + * "git archive" did not record uncompressed size in the header when + streaming a zip archive, which confused some implementations of unzip. + (merge 5ea2c84 rs/zip-with-uncompressed-size-in-the-header later to maint). + + * "git clean" showed what it was going to do, but sometimes end up + finding that it was not allowed to do so, which resulted in a + confusing output (e.g. after saying that it will remove an + untracked directory, it found an embedded git repository there + which it is not allowed to remove). It now performs the actions + and then reports the outcome more faithfully. + (merge f538a91 zk/clean-report-failure later to maint). + + * When "git clone --separate-git-dir=$over_there" is interrupted, it + failed to remove the real location of the $GIT_DIR it created. + This was most visible when interrupting a submodule update. + (merge 9be1980 jl/interrupt-clone-remove-separate-git-dir later to maint). + + * The way "git svn" asked for password using SSH_ASKPASS and + GIT_ASKPASS was not in line with the rest of the system. + (merge e9263e4 ss/svn-prompt later to maint). + + * The --graph code fell into infinite loop when asked to do what the + code did not expect. + (merge 656197a mk/maint-graph-infinity-loop later to maint). + + * http transport was wrong to ask for the username when the + authentication is done by certificate identity. + (merge 75e9a40 rb/http-cert-cred-no-username-prompt later to maint). + + * "git pack-refs" that ran in parallel to another process that + created new refs had a nasty race. + (merge b3f1280 jk/repack-ref-racefix later to maint). + + * After "git add -N" and then writing a tree object out of the + index, the cache-tree data structure got corrupted. + (merge eec3e7e nd/invalidate-i-t-a-cache-tree later to maint). + + * "git clone" used to allow --bare and --separate-git-dir=$there + options at the same time, which was nonsensical. + (merge 95b63f1 nd/clone-no-separate-git-dir-with-bare later to maint). + + * "git rebase --preserve-merges" lost empty merges in recent versions + of Git. + (merge 9869778 ph/rebase-preserve-all-merges later to maint). + + * "git merge --no-edit" computed who were involved in the work done + on the side branch, even though that information is to be discarded + without getting seen in the editor. + (merge 9bcbb1c jc/maint-fmt-merge-msg-no-edit-lose-credit later to maint). + + * "git merge" started calling prepare-commit-msg hook like "git + commit" does some time ago, but forgot to pay attention to the exit + status of the hook. + (merge 3e4141d ap/merge-stop-at-prepare-commit-msg-failure later to maint). + + * When users spell "cc:" in lowercase in the fake "header" in the + trailer part, "git send-email" failed to pick up the addresses from + there. As e-mail headers field names are case insensitive, this + script should follow suit and treat "cc:" and "Cc:" the same way. + (merge 6310071 nz/send-email-headers-are-case-insensitive later to maint). + + * Output from "git status --ignored" showed an unexpected interaction + with "--untracked". + (merge a45fb69 ap/status-ignored-in-ignored-directory later to maint). + + * "gitweb", when sorting by age to show repositories with new + activities first, used to sort repositories with absolutely + nothing in it early, which was not very useful. + (merge 28dae18 md/gitweb-sort-by-age later to maint). + + * "gitweb"'s code to sanitize control characters before passing it to + "highlight" filter lost known-to-be-safe control characters by + mistake. + (merge 0e901d2 os/gitweb-highlight-uncaptured later to maint). + + * When a line to be wrapped has a solid run of non space characters + whose length exactly is the wrap width, "git shortlog -w" failed + to add a newline after such a line. + (merge e0db176 sp/shortlog-missing-lf later to maint). + + * Command line completion leaked an unnecessary error message while + looking for possible matches with paths in . + (merge ca87dd6 ds/completion-silence-in-tree-path-probe later to maint). + + * Command line completion for "tcsh" emitted an unwanted space + after completing a single directory name. + (merge 92f1c04 mk/complete-tcsh later to maint). + + * Some shells do not behave correctly when IFS is unset; work it + around by explicitly setting it to the default value. + (merge 393050c jc/maint-fbsd-sh-ifs-workaround later to maint). + + * Some scripted programs written in Python did not get updated when + PYTHON_PATH changed. + (cherry-pick 96a4647fca54031974cd6ad1 later to maint). + + * When autoconf is used, any build on a different commit always ran + "config.status --recheck" even when unnecessary. + (merge 1226504 jn/less-reconfigure later to maint). + + * We have been carrying a translated and long-unmaintained copy of an + old version of the tutorial; removed. + (merge 0a85441 ta/remove-stale-translated-tut later to maint). + + * t4014, t9502 and t0200 tests had various portability issues that + broke on OpenBSD. + (merge 27f6342 jc/maint-test-portability later to maint). + + * t9020 and t3600 tests had various portability issues. + (merge 5a02966 jc/test-portability later to maint). + + * t9200 runs "cvs init" on a directory that already exists, but a + platform can configure this fail for the current user (e.g. you + need to be in the cvsadmin group on NetBSD 6.0). + (merge 8666df0 jc/test-cvs-no-init-in-existing-dir later to maint). + + * t9020 and t9810 had a few non-portable shell script construct. + (merge 2797914 tb/test-t9020-no-which later to maint). + (merge 6f4e505 tb/test-t9810-no-sed-i later to maint). diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index 0dbf2c9843..90133d8c3b 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -1,65 +1,5 @@ -Checklist (and a short version for the impatient): - - Commits: - - - make commits of logical units - - check for unnecessary whitespace with "git diff --check" - before committing - - do not check in commented out code or unneeded files - - the first line of the commit message should be a short - description (50 characters is the soft limit, see DISCUSSION - in git-commit(1)), and should skip the full stop - - the body should provide a meaningful commit message, which: - . explains the problem the change tries to solve, iow, what - is wrong with the current code without the change. - . justifies the way the change solves the problem, iow, why - the result with the change is better. - . alternate solutions considered but discarded, if any. - - describe changes in imperative mood, e.g. "make xyzzy do frotz" - instead of "[This patch] makes xyzzy do frotz" or "[I] changed - xyzzy to do frotz", as if you are giving orders to the codebase - to change its behaviour. - - try to make sure your explanation can be understood without - external resources. Instead of giving a URL to a mailing list - archive, summarize the relevant points of the discussion. - - add a "Signed-off-by: Your Name " line to the - commit message (or just use the option "-s" when committing) - to confirm that you agree to the Developer's Certificate of Origin - - make sure that you have tests for the bug you are fixing - - make sure that the test suite passes after your commit - - Patch: - - - use "git format-patch -M" to create the patch - - do not PGP sign your patch - - do not attach your patch, but read in the mail - body, unless you cannot teach your mailer to - leave the formatting of the patch alone. - - be careful doing cut & paste into your mailer, not to - corrupt whitespaces. - - provide additional information (which is unsuitable for - the commit message) between the "---" and the diffstat - - if you change, add, or remove a command line option or - make some other user interface change, the associated - documentation should be updated as well. - - if your name is not writable in ASCII, make sure that - you send off a message in the correct encoding. - - send the patch to the list (git@vger.kernel.org) and the - maintainer (gitster@pobox.com) if (and only if) the patch - is ready for inclusion. If you use git-send-email(1), - please test it first by sending email to yourself. - - see below for instructions specific to your mailer - -Long version: - -I started reading over the SubmittingPatches document for Linux -kernel, primarily because I wanted to have a document similar to -it for the core GIT to make sure people understand what they are -doing when they write "Signed-off-by" line. - -But the patch submission requirements are a lot more relaxed -here on the technical/contents front, because the core GIT is -thousand times smaller ;-). So here is only the relevant bits. +Here are some guidelines for people who want to contribute their code +to this software. (0) Decide what to base your work on. @@ -86,6 +26,10 @@ change is relevant to. wait until some of the dependent topics graduate to 'master', and rebase your work. + - Some parts of the system have dedicated maintainers with their own + repositories (see the section "Subsystems" below). Changes to + these parts should be based on their trees. + To find the tip of a topic branch, run "git log --first-parent master..pu" and look for the merge commit. The second parent of this commit is the tip of the topic branch. @@ -113,26 +57,53 @@ change, the approach taken by the change, and if relevant how this differs substantially from the prior version, are all good things to have. +Make sure that you have tests for the bug you are fixing. + +When adding a new feature, make sure that you have new tests to show +the feature triggers the new behaviour when it should, and to show the +feature does not trigger when it shouldn't. Also make sure that the +test suite passes after your commit. Do not forget to update the +documentation to describe the updated behaviour. + Oh, another thing. I am picky about whitespaces. Make sure your changes do not trigger errors with the sample pre-commit hook shipped in templates/hooks--pre-commit. To help ensure this does not happen, run git diff --check on your changes before you commit. -(1a) Try to be nice to older C compilers +(2) Describe your changes well. + +The first line of the commit message should be a short description (50 +characters is the soft limit, see DISCUSSION in git-commit(1)), and +should skip the full stop. It is also conventional in most cases to +prefix the first line with "area: " where the area is a filename or +identifier for the general area of the code being modified, e.g. + + . archive: ustar header checksum is computed unsigned + . git-cherry-pick.txt: clarify the use of revision range notation + +If in doubt which identifier to use, run "git log --no-merges" on the +files you are modifying to see the current conventions. -We try to support a wide range of C compilers to compile -git with. That means that you should not use C99 initializers, even -if a lot of compilers grok it. +The body should provide a meaningful commit message, which: -Also, variables have to be declared at the beginning of the block -(you can check this with gcc, using the -Wdeclaration-after-statement -option). + . explains the problem the change tries to solve, iow, what is wrong + with the current code without the change. -Another thing: NULL pointers shall be written as NULL, not as 0. + . justifies the way the change solves the problem, iow, why the + result with the change is better. + . alternate solutions considered but discarded, if any. -(2) Generate your patch using git tools out of your commits. +Describe your changes in imperative mood, e.g. "make xyzzy do frotz" +instead of "[This patch] makes xyzzy do frotz" or "[I] changed xyzzy +to do frotz", as if you are giving orders to the codebase to change +its behaviour. Try to make sure your explanation can be understood +without external resources. Instead of giving a URL to a mailing list +archive, summarize the relevant points of the discussion. + + +(3) Generate your patch using git tools out of your commits. git based diff tools generate unidiff which is the preferred format. @@ -140,22 +111,27 @@ You do not have to be afraid to use -M option to "git diff" or "git format-patch", if your patch involves file renames. The receiving end can handle them just fine. -Please make sure your patch does not include any extra files -which do not belong in a patch submission. Make sure to review +Please make sure your patch does not add commented out debugging code, +or include any extra files which do not relate to what your patch +is trying to achieve. Make sure to review your patch after generating it, to ensure accuracy. Before sending out, please make sure it cleanly applies to the "master" branch head. If you are preparing a work based on "next" branch, that is fine, but please mark it as such. -(3) Sending your patches. +(4) Sending your patches. People on the git mailing list need to be able to read and comment on the changes you are submitting. It is important for a developer to be able to "quote" your changes, using standard e-mail tools, so that they may comment on specific portions of your code. For this reason, all patches should be submitted -"inline". WARNING: Be wary of your MUAs word-wrap +"inline". If your log message (including your name on the +Signed-off-by line) is not writable in ASCII, make sure that +you send off a message in the correct encoding. + +WARNING: Be wary of your MUAs word-wrap corrupting your patch. Do not cut-n-paste your patch; you can lose tabs that way if you are not careful. @@ -179,7 +155,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 @@ -207,19 +184,25 @@ patch, format it as "multipart/signed", not a text/plain message that starts with '-----BEGIN PGP SIGNED MESSAGE-----'. That is not a text/plain, it's something else. -Unless your patch is a very trivial and an obviously correct one, -first send it with "To:" set to the mailing list, with "cc:" listing +Send your patch with "To:" set to the mailing list, with "cc:" listing people who are involved in the area you are touching (the output from "git blame $path" and "git shortlog --no-merges $path" would help to -identify them), to solicit comments and reviews. After the list -reached a consensus that it is a good idea to apply the patch, re-send -it with "To:" set to the maintainer and optionally "cc:" the list for -inclusion. Do not forget to add trailers such as "Acked-by:", -"Reviewed-by:" and "Tested-by:" after your "Signed-off-by:" line as -necessary. +identify them), to solicit comments and reviews. + +After the list reached a consensus that it is a good idea to apply the +patch, re-send it with "To:" set to the maintainer [*1*] and "cc:" the +list [*2*] for inclusion. + +Do not forget to add trailers such as "Acked-by:", "Reviewed-by:" and +"Tested-by:" lines as necessary to credit people who helped your +patch. + [Addresses] + *1* The current maintainer: gitster@pobox.com + *2* The mailing list: git@vger.kernel.org -(4) Sign your work + +(5) Sign your work To improve tracking of who did what, we've borrowed the "sign-off" procedure from the Linux kernel project on patches @@ -289,6 +272,26 @@ If you like, you can put extra tags at the end: You can also create your own tag or use one that's in common usage such as "Thanks-to:", "Based-on-patch-by:", or "Mentored-by:". +------------------------------------------------ +Subsystems with dedicated maintainers + +Some parts of the system have dedicated maintainers with their own +repositories. + + - git-gui/ comes from git-gui project, maintained by Pat Thoyts: + + git://repo.or.cz/git-gui.git + + - gitk-git/ comes from Paul Mackerras's gitk project: + + git://ozlabs.org/~paulus/gitk + + - po/ comes from the localization coordinator, Jiang Xin: + + https://github.com/git-l10n/git-po/ + +Patches to these parts should be based on their trees. + ------------------------------------------------ An ideal patch flow diff --git a/Documentation/config.txt b/Documentation/config.txt index 11f320b962..b87f744643 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -140,10 +140,11 @@ advice.*:: can tell Git that you do not need help by setting these to 'false': + -- - pushNonFastForward:: + pushUpdateRejected:: Set this variable to 'false' if you want to disable - 'pushNonFFCurrent', 'pushNonFFDefault', and - 'pushNonFFMatching' simultaneously. + 'pushNonFFCurrent', 'pushNonFFDefault', + 'pushNonFFMatching', and 'pushAlreadyExists' + simultaneously. pushNonFFCurrent:: Advice shown when linkgit:git-push[1] fails due to a non-fast-forward update to the current branch. @@ -158,11 +159,15 @@ advice.*:: 'matching refs' explicitly (i.e. you used ':', or specified a refspec that isn't your current branch) and it resulted in a non-fast-forward error. + pushAlreadyExists:: + Shown when linkgit:git-push[1] rejects an update that + does not qualify for fast-forwarding (e.g., a tag.) statusHints:: Show directions on how to proceed from the current - state in the output of linkgit:git-status[1] and in + state in the output of linkgit:git-status[1], in the template shown when writing commit messages in - linkgit:git-commit[1]. + linkgit:git-commit[1], and in the help message shown + by linkgit:git-checkout[1] when switching branch. commitBeforeMerge:: Advice shown when linkgit:git-merge[1] refuses to merge to avoid overwriting local changes. @@ -538,14 +543,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 @@ -734,6 +739,12 @@ branch..rebase:: it unless you understand the implications (see linkgit:git-rebase[1] for details). +branch..description:: + Branch description, can be edited with + `git branch --edit-description`. Branch description is + automatically added in the format-patch cover letter or + request-pull summary. + browser..cmd:: Specify the command to invoke the specified browser. The specified command is evaluated in shell with the URLs passed @@ -912,6 +923,15 @@ column.tag:: Specify whether to output tag listing in `git tag` in columns. See `column.ui` for details. +commit.cleanup:: + This setting overrides the default of the `--cleanup` option in + `git commit`. See linkgit:git-commit[1] for details. Changing the + default can be useful when you always want to keep lines that begin + with comment character `#` in your log message, in which case you + would do `git config commit.cleanup whitespace` (note that you will + have to remove the help lines that begin with `#` in the commit log + template yourself, if you do this). + commit.status:: A boolean to enable/disable inclusion of status information in the commit message template when using an editor to prepare the commit @@ -962,12 +982,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 @@ -1356,6 +1370,12 @@ help.autocorrect:: value is 0 - the command will be just shown but not executed. This is the default. +help.htmlpath:: + Specify the path where the HTML documentation resides. File system paths + and URLs are supported. HTML pages will be prefixed with this path when + help is displayed in the 'web' format. This defaults to the documentation + path of your Git installation. + http.proxy:: Override the HTTP proxy, normally configured using the 'http_proxy', 'https_proxy', and 'all_proxy' environment variables (see @@ -1514,6 +1534,10 @@ log.showroot:: Tools like linkgit:git-log[1] or linkgit:git-whatchanged[1], which normally hide the root commit will now show it. True by default. +log.mailmap:: + If true, makes linkgit:git-log[1], linkgit:git-show[1], and + linkgit:git-whatchanged[1] assume `--use-mailmap`. + mailmap.file:: The location of an augmenting mailmap file. The default mailmap, located in the root of the repository, is loaded @@ -1522,6 +1546,14 @@ mailmap.file:: subdirectory, or somewhere outside of the repository itself. See linkgit:git-shortlog[1] and linkgit:git-blame[1]. +mailmap.blob:: + Like `mailmap.file`, but consider the value as a reference to a + blob in the repository. If both `mailmap.file` and + `mailmap.blob` are given, both are parsed, with entries from + `mailmap.file` taking precedence. In a bare repository, this + defaults to `HEAD:.mailmap`. In a non-bare repository, it + defaults to empty. + man.viewer:: Specify the programs that may be used to display help in the 'man' format. See linkgit:git-help[1]. @@ -2000,6 +2032,12 @@ submodule..update:: URL and other values found in the `.gitmodules` file. See linkgit:git-submodule[1] and linkgit:gitmodules[5] for details. +submodule..branch:: + The remote branch name for a submodule, used by `git submodule + update --remote`. Set this option to override the value found in + the `.gitmodules` file. See linkgit:git-submodule[1] and + linkgit:gitmodules[5] for details. + submodule..fetchRecurseSubmodules:: This option can be used to control recursive fetching of this submodule. It can be overridden by using the --[no-]recurse-submodules diff --git a/Documentation/diff-config.txt b/Documentation/diff-config.txt index 67a90a828c..4314ad0fbb 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..39f2c5074c 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. @@ -308,7 +309,11 @@ endif::git-log[] index (i.e. amount of addition/deletions compared to the file's size). For example, `-M90%` means git should consider a delete/add pair to be a rename if more than 90% of the file - hasn't changed. + hasn't changed. Without a `%` sign, the number is to be read as + a fraction, with a decimal point before it. I.e., `-M5` becomes + 0.5, and is thus the same as `-M50%`. Similarly, `-M05` is + the same as `-M5%`. To limit detection to exact renames, use + `-M100%`. -C[]:: --find-copies[=]:: diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt index b4d6476ac8..6e98bdf149 100644 --- a/Documentation/fetch-options.txt +++ b/Documentation/fetch-options.txt @@ -57,14 +57,11 @@ endif::git-pull[] ifndef::git-pull[] -t:: --tags:: - Most of the tags are fetched automatically as branch - heads are downloaded, but tags that do not point at - objects reachable from the branch heads that are being - tracked will not be fetched by this mechanism. This - flag lets all tags and their associated objects be - downloaded. The default behavior for a remote may be - specified with the remote..tagopt setting. See - linkgit:git-config[1]. + This is a short-hand for giving "refs/tags/*:refs/tags/*" + refspec from the command line, to ask all tags to be fetched + and stored locally. Because this acts as an explicit + refspec, the default refspecs (configured with the + remote.$name.fetch variable) are overridden and not used. --recurse-submodules[=yes|on-demand|no]:: This option controls if and under what conditions new commits of 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-bundle.txt b/Documentation/git-bundle.txt index 16a6b0aceb..bc023cc5f3 100644 --- a/Documentation/git-bundle.txt +++ b/Documentation/git-bundle.txt @@ -112,13 +112,12 @@ machineA$ git bundle create file.bundle master machineA$ git tag -f lastR2bundle master ---------------- -Then you transfer file.bundle to the target machine B. If you are creating -the repository on machine B, then you can clone from the bundle as if it -were a remote repository instead of creating an empty repository and then -pulling or fetching objects from the bundle: +Then you transfer file.bundle to the target machine B. Because this +bundle does not require any existing object to be extracted, you can +create a new repository on machine B by cloning from it: ---------------- -machineB$ git clone /home/me/tmp/file.bundle R2 +machineB$ git clone -b master /home/me/tmp/file.bundle R2 ---------------- This will define a remote called "origin" in the resulting repository that diff --git a/Documentation/git-check-ignore.txt b/Documentation/git-check-ignore.txt new file mode 100644 index 0000000000..854e4d0c42 --- /dev/null +++ b/Documentation/git-check-ignore.txt @@ -0,0 +1,89 @@ +git-check-ignore(1) +=================== + +NAME +---- +git-check-ignore - Debug gitignore / exclude files + + +SYNOPSIS +-------- +[verse] +'git check-ignore' [options] pathname... +'git check-ignore' [options] --stdin < + +DESCRIPTION +----------- + +For each pathname given via the command-line or from a file via +`--stdin`, show the pattern from .gitignore (or other input files to +the exclude mechanism) that decides if the pathname is excluded or +included. Later patterns within a file take precedence over earlier +ones. + +OPTIONS +------- +-q, --quiet:: + Don't output anything, just set exit status. This is only + valid with a single pathname. + +-v, --verbose:: + Also output details about the matching pattern (if any) + for each given pathname. + +--stdin:: + Read file names from stdin instead of from the command-line. + +-z:: + The output format is modified to be machine-parseable (see + below). If `--stdin` is also given, input paths are separated + with a NUL character instead of a linefeed character. + +OUTPUT +------ + +By default, any of the given pathnames which match an ignore pattern +will be output, one per line. If no pattern matches a given path, +nothing will be output for that path; this means that path will not be +ignored. + +If `--verbose` is specified, the output is a series of lines of the form: + + + + is the path of a file being queried, is the +matching pattern, is the pattern's source file, and +is the line number of the pattern within that source. If the pattern +contained a `!` prefix or `/` suffix, it will be preserved in the +output. will be an absolute path when referring to the file +configured by `core.excludesfile`, or relative to the repository root +when referring to `.git/info/exclude` or a per-directory exclude file. + +If `-z` is specified, the pathnames in the output are delimited by the +null character; if `--verbose` is also specified then null characters +are also used instead of colons and hard tabs: + + + + +EXIT STATUS +----------- + +0:: + One or more of the provided paths is ignored. + +1:: + None of the provided paths are ignored. + +128:: + A fatal error was encountered. + +SEE ALSO +-------- +linkgit:gitignore[5] +linkgit:gitconfig[5] +linkgit:git-ls-files[5] + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 7958a47006..6f04d22f5e 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -21,18 +21,34 @@ or the specified tree. If no paths are given, 'git checkout' will also update `HEAD` to set the specified branch as the current branch. -'git checkout' []:: +'git checkout' :: + To prepare for working on , switch to it by updating + the index and the files in the working tree, and by pointing + HEAD at the branch. Local modifications to the files in the + working tree are kept, so that they can be committed to the + . ++ +If is not found but there does exist a tracking branch in +exactly one remote (call it ) with a matching name, treat as +equivalent to ++ +------------ +$ git checkout -b --track / +------------ ++ +You could omit , in which case the command degenerates to +"check out the current branch", which is a glorified no-op with a +rather expensive side-effects to show only the tracking information, +if exists, for the current branch. + 'git checkout' -b|-B []:: -'git checkout' [--detach] []:: - This form switches branches by updating the index, working - tree, and HEAD to reflect the specified branch or commit. -+ -If `-b` is given, a new branch is created as if linkgit:git-branch[1] -were called and then checked out; in this case you can -use the `--track` or `--no-track` options, which will be passed to -'git branch'. As a convenience, `--track` without `-b` implies branch -creation; see the description of `--track` below. + Specifying `-b` causes a new branch to be created as if + linkgit:git-branch[1] were called and then checked out. In + this case you can use the `--track` or `--no-track` options, + which will be passed to 'git branch'. As a convenience, + `--track` without `-b` implies branch creation; see the + description of `--track` below. + If `-B` is given, is created if it doesn't exist; otherwise, it is reset. This is the transactional equivalent of @@ -45,6 +61,21 @@ $ git checkout that is to say, the branch is not reset/created unless "git checkout" is successful. +'git checkout' --detach []:: +'git checkout' :: + + Prepare to work on top of , by detaching HEAD at it + (see "DETACHED HEAD" section), and updating the index and the + files in the working tree. Local modifications to the files + in the working tree are kept, so that the resulting working + tree will be the state recorded in the commit plus the local + modifications. ++ +Passing `--detach` forces this behavior in the case of a (without +the option, giving a branch name to the command would check out the branch, +instead of detaching HEAD at it), or the current commit, +if no is specified. + 'git checkout' [-p|--patch] [] [--] ...:: When or `--patch` are given, 'git checkout' does *not* diff --git a/Documentation/git-commit-tree.txt b/Documentation/git-commit-tree.txt index 6d5a04c83b..a221169515 100644 --- a/Documentation/git-commit-tree.txt +++ b/Documentation/git-commit-tree.txt @@ -72,13 +72,13 @@ if set: GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL GIT_COMMITTER_DATE - EMAIL (nb "<", ">" and "\n"s are stripped) In case (some of) these environment variables are not set, the information is taken from the configuration items user.name and user.email, or, if not -present, system user name and the hostname used for outgoing mail (taken +present, the environment variable EMAIL, or, if that is not set, +system user name and the hostname used for outgoing mail (taken from `/etc/mailname` and falling back to the fully qualified hostname when that file does not exist). diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 15cec8601c..41b27da325 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 @@ -175,7 +179,9 @@ OPTIONS only if the message is to be edited. Otherwise only whitespace removed. The 'verbatim' mode does not change message at all, 'whitespace' removes just leading/trailing whitespace lines - and 'strip' removes both whitespace and commentary. + and 'strip' removes both whitespace and commentary. The default + can be changed by the 'commit.cleanup' configuration variable + (see linkgit:git-config[1]). -e:: --edit:: @@ -280,6 +286,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-config.txt b/Documentation/git-config.txt index eaea079165..9ae2508f3f 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -240,6 +240,10 @@ GIT_CONFIG:: Using the "--global" option forces this to ~/.gitconfig. Using the "--system" option forces this to $(prefix)/etc/gitconfig. +GIT_CONFIG_NOSYSTEM:: + Whether to skip reading settings from the system-wide + $(prefix)/etc/gitconfig file. See linkgit:git[1] for details. + See also <>. diff --git a/Documentation/git-cvsimport.txt b/Documentation/git-cvsimport.txt index 6695ab3b4b..9d5353e8be 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 @@ -211,11 +213,9 @@ Problems related to tags: * Multiple tags on the same revision are not imported. If you suspect that any of these issues may apply to the repository you -want to import consider using these alternative tools which proved to be -more stable in practice: +want to imort, consider using cvs2git: -* cvs2git (part of cvs2svn), `http://cvs2svn.tigris.org` -* parsecvs, `http://cgit.freedesktop.org/~keithp/parsecvs` +* cvs2git (part of cvs2svn), `http://subversion.apache.org/` GIT --- diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt index 88d814af0e..940c2ba66a 100644 --- a/Documentation/git-cvsserver.txt +++ b/Documentation/git-cvsserver.txt @@ -359,6 +359,43 @@ Operations supported All the operations required for normal use are supported, including checkout, diff, status, update, log, add, remove, commit. + +Most CVS command arguments that read CVS tags or revision numbers +(typically -r) work, and also support any git refspec +(tag, branch, commit ID, etc). +However, CVS revision numbers for non-default branches are not well +emulated, and cvs log does not show tags or branches at +all. (Non-main-branch CVS revision numbers superficially resemble CVS +revision numbers, but they actually encode a git commit ID directly, +rather than represent the number of revisions since the branch point.) + +Note that there are two ways to checkout a particular branch. +As described elsewhere on this page, the "module" parameter +of cvs checkout is interpreted as a branch name, and it becomes +the main branch. It remains the main branch for a given sandbox +even if you temporarily make another branch sticky with +cvs update -r. Alternatively, the -r argument can indicate +some other branch to actually checkout, even though the module +is still the "main" branch. Tradeoffs (as currently +implemented): Each new "module" creates a new database on disk with +a history for the given module, and after the database is created, +operations against that main branch are fast. Or alternatively, +-r doesn't take any extra disk space, but may be significantly slower for +many operations, like cvs update. + +If you want to refer to a git refspec that has characters that are +not allowed by CVS, you have two options. First, it may just work +to supply the git refspec directly to the appropriate CVS -r argument; +some CVS clients don't seem to do much sanity checking of the argument. +Second, if that fails, you can use a special character escape mechanism +that only uses characters that are valid in CVS tags. A sequence +of 4 or 5 characters of the form (underscore (`"_"`), dash (`"-"`), +one or two characters, and dash (`"-"`)) can encode various characters based +on the one or two letters: `"s"` for slash (`"/"`), `"p"` for +period (`"."`), `"u"` for underscore (`"_"`), or two hexadecimal digits +for any byte value at all (typically an ASCII number, or perhaps a part +of a UTF-8 encoded character). + Legacy monitoring operations are not supported (edit, watch and related). Exports and tagging (tags and branches) are not supported at this stage. diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt index f8d0819113..f8c06013f3 100644 --- a/Documentation/git-diff.txt +++ b/Documentation/git-diff.txt @@ -12,6 +12,7 @@ SYNOPSIS 'git diff' [options] [] [--] [...] 'git diff' [options] --cached [] [--] [...] 'git diff' [options] [--] [...] +'git diff' [options] 'git diff' [options] [--no-index] [--] DESCRIPTION @@ -55,6 +56,11 @@ directories. This behavior can be forced by --no-index. This is to view the changes between two arbitrary . +'git diff' [options] :: + + This form is to view the differences between the raw + contents of two blob objects. + 'git diff' [--options] .. [--] [...]:: This is synonymous to the previous form. If on @@ -72,8 +78,7 @@ directories. This behavior can be forced by --no-index. Just in case if you are doing something exotic, it should be noted that all of the in the above description, except in the last two forms that use ".." notations, can be any -. The third form ('git diff ') can also -be used to compare two objects. +. For a more complete list of ways to spell , see "SPECIFYING REVISIONS" section in linkgit:gitrevisions[7]. diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt index 959e4d3aee..bf1a02a80d 100644 --- a/Documentation/git-fast-import.txt +++ b/Documentation/git-fast-import.txt @@ -33,38 +33,46 @@ the frontend program in use. OPTIONS ------- ---date-format=:: - Specify the type of dates the frontend will supply to - fast-import within `author`, `committer` and `tagger` commands. - See ``Date Formats'' below for details about which formats - are supported, and their syntax. - --- done:: - Terminate with error if there is no 'done' command at the - end of the stream. --force:: Force updating modified existing branches, even if doing so would cause commits to be lost (as the new commit does not contain the old commit). ---max-pack-size=:: - Maximum size of each output packfile. - The default is unlimited. +--quiet:: + Disable all non-fatal output, making fast-import silent when it + is successful. This option disables the output shown by + \--stats. ---big-file-threshold=:: - Maximum size of a blob that fast-import will attempt to - create a delta for, expressed in bytes. The default is 512m - (512 MiB). Some importers may wish to lower this on systems - with constrained memory. +--stats:: + Display some basic statistics about the objects fast-import has + created, the packfiles they were stored into, and the + memory used by fast-import during this run. Showing this output + is currently the default, but can be disabled with \--quiet. ---depth=:: - Maximum delta depth, for blob and tree deltification. - Default is 10. +Options for Frontends +~~~~~~~~~~~~~~~~~~~~~ ---active-branches=:: - Maximum number of branches to maintain active at once. - See ``Memory Utilization'' below for details. Default is 5. +--cat-blob-fd=:: + Write responses to `cat-blob` and `ls` queries to the + file descriptor instead of `stdout`. Allows `progress` + output intended for the end-user to be separated from other + output. + +--date-format=:: + Specify the type of dates the frontend will supply to + fast-import within `author`, `committer` and `tagger` commands. + See ``Date Formats'' below for details about which formats + are supported, and their syntax. + +--done:: + Terminate with error if there is no `done` command at the end of + the stream. This option might be useful for detecting errors + that cause the frontend to terminate before it has started to + write a stream. + +Locations of Marks Files +~~~~~~~~~~~~~~~~~~~~~~~~ --export-marks=:: Dumps the internal marks table to when complete. @@ -87,31 +95,33 @@ OPTIONS Like --import-marks but instead of erroring out, silently skips the file if it does not exist. ---relative-marks:: +--[no-]relative-marks:: After specifying --relative-marks the paths specified with --import-marks= and --export-marks= are relative to an internal directory in the current repository. In git-fast-import this means that the paths are relative to the .git/info/fast-import directory. However, other importers may use a different location. ++ +Relative and non-relative marks may be combined by interweaving +--(no-)-relative-marks with the --(import|export)-marks= options. ---no-relative-marks:: - Negates a previous --relative-marks. Allows for combining - relative and non-relative marks by interweaving - --(no-)-relative-marks with the --(import|export)-marks= - options. +Performance and Compression Tuning +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ---cat-blob-fd=:: - Write responses to `cat-blob` and `ls` queries to the - file descriptor instead of `stdout`. Allows `progress` - output intended for the end-user to be separated from other - output. +--active-branches=:: + Maximum number of branches to maintain active at once. + See ``Memory Utilization'' below for details. Default is 5. ---done:: - Require a `done` command at the end of the stream. - This option might be useful for detecting errors that - cause the frontend to terminate before it has started to - write a stream. +--big-file-threshold=:: + Maximum size of a blob that fast-import will attempt to + create a delta for, expressed in bytes. The default is 512m + (512 MiB). Some importers may wish to lower this on systems + with constrained memory. + +--depth=:: + Maximum delta depth, for blob and tree deltification. + Default is 10. --export-pack-edges=:: After creating a packfile, print a line of data to @@ -122,16 +132,9 @@ OPTIONS as these commits can be used as edge points during calls to 'git pack-objects'. ---quiet:: - Disable all non-fatal output, making fast-import silent when it - is successful. This option disables the output shown by - \--stats. - ---stats:: - Display some basic statistics about the objects fast-import has - created, the packfiles they were stored into, and the - memory used by fast-import during this run. Showing this output - is currently the default, but can be disabled with \--quiet. +--max-pack-size=:: + Maximum size of each output packfile. + The default is unlimited. Performance @@ -427,7 +430,7 @@ they made it. Here `` is the person's display name (for example ``Com M Itter'') and `` is the person's email address -(``cm@example.com''). `LT` and `GT` are the literal less-than (\x3c) +(``\cm@example.com''). `LT` and `GT` are the literal less-than (\x3c) and greater-than (\x3e) symbols. These are required to delimit the email address from the other fields in the line. Note that `` and `` are free-form and may contain any sequence @@ -562,8 +565,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-for-each-ref.txt b/Documentation/git-for-each-ref.txt index db55a4e0bb..f2e08d11c1 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -117,7 +117,7 @@ returns an empty string instead. As a special case for the date-type fields, you may specify a format for the date by adding one of `:default`, `:relative`, `:short`, `:local`, -`:iso8601` or `:rfc2822` to the end of the fieldname; e.g. +`:iso8601`, `:rfc2822` or `:raw` to the end of the fieldname; e.g. `%(taggerdate:relative)`. diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 6d43f56279..9a914d0159 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -18,9 +18,9 @@ SYNOPSIS [--start-number ] [--numbered-files] [--in-reply-to=Message-Id] [--suffix=.] [--ignore-if-in-upstream] - [--subject-prefix=Subject-Prefix] + [--subject-prefix=Subject-Prefix] [(--reroll-count|-v) ] [--to=] [--cc=] - [--cover-letter] [--quiet] + [--cover-letter] [--quiet] [--notes[=]] [] [ | ] @@ -166,6 +166,15 @@ will want to ensure that threading is disabled for `git send-email`. allows for useful naming of a patch series, and can be combined with the `--numbered` option. +-v :: +--reroll-count=:: + Mark the series as the -th iteration of the topic. The + output filenames have `v` pretended to them, and the + subject prefix ("PATCH" by default, but configurable via the + `--subject-prefix` option) has ` v` appended to it. E.g. + `--reroll-count=4` may produce `v4-0001-add-makefile.patch` + file that has "Subject: [PATCH v4 1/20] Add makefile" in it. + --to=:: Add a `To:` header to the email headers. This is in addition to any configured headers, and may be used multiple times. @@ -191,6 +200,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-log.txt b/Documentation/git-log.txt index 585dac40ba..22c0d6e4b1 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -47,6 +47,11 @@ OPTIONS Print out the ref name given on the command line by which each commit was reached. +--use-mailmap:: + Use mailmap file to map author and committer names and email + to canonical real names and email addresses. See + linkgit:git-shortlog[1]. + --full-diff:: Without this flag, "git log -p ..." shows commits that touch the specified paths, and diffs about the same specified @@ -167,7 +172,7 @@ log.showroot:: `git log -p` output would be shown without a diff attached. The default is `true`. -mailmap.file:: +mailmap.*:: See linkgit:git-shortlog[1]. notes.displayRef:: 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-p4.txt b/Documentation/git-p4.txt index beff6229c8..f70ef9ded2 100644 --- a/Documentation/git-p4.txt +++ b/Documentation/git-p4.txt @@ -112,6 +112,11 @@ will be fetched and consulted first during a 'git p4 sync'. Since importing directly from p4 is considerably slower than pulling changes from a git remote, this can be useful in a multi-developer environment. +If there are multiple branches, doing 'git p4 sync' will automatically +use the "BRANCH DETECTION" algorithm to try to partition new changes +into the right branch. This can be overridden with the '--branch' +option to specify just a single branch to update. + Rebase ~~~~~~ @@ -173,9 +178,11 @@ subsequent 'sync' operations. --branch :: Import changes into given branch. If the branch starts with - 'refs/', it will be used as is, otherwise the path 'refs/heads/' - will be prepended. The default branch is 'master'. If used - with an initial clone, no HEAD will be checked out. + 'refs/', it will be used as is. Otherwise if it does not start + with 'p4/', that prefix is added. The branch is assumed to + name a remote tracking, but this can be modified using + '--import-local', or by giving a full ref name. The default + branch is 'master'. + This example imports a new remote "p4/proj2" into an existing git repository: @@ -287,6 +294,11 @@ These options can be used to modify 'git p4 submit' behavior. to bypass the prompt, causing conflicting commits to be automatically skipped, or to quit trying to apply commits, without prompting. +--branch :: + After submitting, sync this named branch instead of the default + p4/master. See the "Sync options" section above for more + information. + Rebase options ~~~~~~~~~~~~~~ These options can be used to modify 'git p4 rebase' behavior. @@ -394,8 +406,10 @@ the path elements in the p4 repository. The example above relied on the presence of the p4 branch. Without p4 branches, the same result will occur with: ---- +git init depot +cd depot git config git-p4.branchList main:branch1 -git p4 clone --detect-branches //depot@all +git p4 clone --detect-branches //depot@all . ---- diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 6d19d59ce5..c964b796be 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -51,10 +51,11 @@ be named. If `:` is omitted, the same ref as will be updated. + The object referenced by is used to update the reference -on the remote side, but by default this is only allowed if the -update can fast-forward . By having the optional leading `+`, -you can tell git to update the ref even when the update is not a -fast-forward. This does *not* attempt to merge into . See +on the remote side. By default this is only allowed if is not +a tag (annotated or lightweight), and then only if it can fast-forward +. By having the optional leading `+`, you can tell git to update +the ref even if it is not allowed by default (e.g., it is not a +fast-forward.) This does *not* attempt to merge into . See EXAMPLES below for details. + `tag ` means the same as `refs/tags/:refs/tags/`. @@ -385,11 +386,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..6d696e0f90 100644 --- a/Documentation/git-remote-helpers.txt +++ b/Documentation/git-remote-helpers.txt @@ -35,6 +35,37 @@ transport protocols, such as 'git-remote-http', 'git-remote-https', 'git-remote-ftp' and 'git-remote-ftps'. They implement the capabilities 'fetch', 'option', and 'push'. +INVOCATION +---------- + +Remote helper programs are invoked with one or (optionally) two +arguments. The first argument specifies a remote repository as in git; +it is either the name of a configured remote or a URL. The second +argument specifies a URL; it is usually of the form +'://
', but any arbitrary string is possible. +The 'GIT_DIR' environment variable is set up for the remote helper +and can be used to determine where to store additional data or from +which directory to invoke auxiliary git commands. + +When git encounters a URL of the form '://
', where +'' is a protocol that it cannot handle natively, it +automatically invokes 'git remote-' with the full URL as +the second argument. If such a URL is encountered directly on the +command line, the first argument is the same as the second, and if it +is encountered in a configured remote, the first argument is the name +of that remote. + +A URL of the form '::
' explicitly instructs git to +invoke 'git remote-' with '
' as the second +argument. If such a URL is encountered directly on the command line, +the first argument is '
', and if it is encountered in a +configured remote, the first argument is the name of that remote. + +Additionally, when a configured remote has 'remote..vcs' set to +'', git explicitly invokes 'git remote-' with +'' as the first argument. If set, the second argument is +'remote..url'; otherwise, the second argument is omitted. + INPUT FORMAT ------------ @@ -57,53 +88,17 @@ Each remote helper is expected to support only a subset of commands. The operations a helper supports are declared to git in the response to the `capabilities` command (see COMMANDS, below). -'option':: - For specifying settings like `verbosity` (how much output to - write to stderr) and `depth` (how much history is wanted in the - case of a shallow clone) that affect how other commands are - carried out. - -'connect':: - For fetching and pushing using git's native packfile protocol - that requires a bidirectional, full-duplex connection. - -'push':: - For listing remote refs and pushing specified objects from the - local object store to remote refs. - -'fetch':: - For listing remote refs and fetching the associated history to - the local object store. - -'import':: - For listing remote refs and fetching the associated history as - a fast-import stream. - -'refspec' :: - This modifies the 'import' capability, allowing the produced - fast-import stream to modify refs in a private namespace - instead of writing to refs/heads or refs/remotes directly. - It is recommended that all importers providing the 'import' - capability use this. -+ -A helper advertising the capability -`refspec refs/heads/*:refs/svn/origin/branches/*` -is saying that, when it is asked to `import refs/heads/topic`, the -stream it outputs will update the `refs/svn/origin/branches/topic` -ref. -+ -This capability can be advertised multiple times. The first -applicable refspec takes precedence. The left-hand of refspecs -advertised with this capability must cover all refs reported by -the list command. If no 'refspec' capability is advertised, -there is an implied `refspec *:*`. +In the following, we list all defined capabilities and for +each we list which commands a helper with that capability +must provide. Capabilities for Pushing -~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^ 'connect':: Can attempt to connect to 'git receive-pack' (for pushing), - 'git upload-pack', etc for communication using the - packfile protocol. + 'git upload-pack', etc for communication using + git's native packfile protocol. This + requires a bidirectional, full-duplex connection. + Supported commands: 'connect'. @@ -113,16 +108,26 @@ Supported commands: 'connect'. + Supported commands: 'list for-push', 'push'. -If a helper advertises both 'connect' and 'push', git will use -'connect' if possible and fall back to 'push' if the helper requests -so when connecting (see the 'connect' command under COMMANDS). +'export':: + Can discover remote refs and push specified objects from a + fast-import stream to remote refs. ++ +Supported commands: 'list for-push', 'export'. + +If a helper advertises 'connect', git will use it if possible and +fall back to another capability if the helper requests so when +connecting (see the 'connect' command under COMMANDS). +When choosing between 'push' and 'export', git prefers 'push'. +Other frontends may have some other order of preference. + Capabilities for Fetching -~~~~~~~~~~~~~~~~~~~~~~~~~ +^^^^^^^^^^^^^^^^^^^^^^^^^ 'connect':: Can try to connect to 'git upload-pack' (for fetching), 'git receive-pack', etc for communication using the - packfile protocol. + git's native packfile protocol. This + requires a bidirectional, full-duplex connection. + Supported commands: 'connect'. @@ -144,14 +149,27 @@ connecting (see the 'connect' command under COMMANDS). When choosing between 'fetch' and 'import', git prefers 'fetch'. Other frontends may have some other order of preference. +Miscellaneous capabilities +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +'option':: + For specifying settings like `verbosity` (how much output to + write to stderr) and `depth` (how much history is wanted in the + case of a shallow clone) that affect how other commands are + carried out. + 'refspec' :: - This modifies the 'import' capability. + This modifies the 'import' capability, allowing the produced + fast-import stream to modify refs in a private namespace + instead of writing to refs/heads or refs/remotes directly. + It is recommended that all importers providing the 'import' + capability use this. + -A helper advertising +A helper advertising the capability `refspec refs/heads/*:refs/svn/origin/branches/*` -in its capabilities is saying that, when it handles -`import refs/heads/topic`, the stream it outputs will update the -`refs/svn/origin/branches/topic` ref. +is saying that, when it is asked to `import refs/heads/topic`, the +stream it outputs will update the `refs/svn/origin/branches/topic` +ref. + This capability can be advertised multiple times. The first applicable refspec takes precedence. The left-hand of refspecs @@ -159,36 +177,33 @@ advertised with this capability must cover all refs reported by the list command. If no 'refspec' capability is advertised, there is an implied `refspec *:*`. -INVOCATION ----------- +'bidi-import':: + This modifies the 'import' capability. + 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. -Remote helper programs are invoked with one or (optionally) two -arguments. The first argument specifies a remote repository as in git; -it is either the name of a configured remote or a URL. The second -argument specifies a URL; it is usually of the form -'://
', but any arbitrary string is possible. -The 'GIT_DIR' environment variable is set up for the remote helper -and can be used to determine where to store additional data or from -which directory to invoke auxiliary git commands. +'export-marks' :: + This modifies the 'export' capability, instructing git to dump the + internal marks table to when complete. For details, + read up on '--export-marks=' in linkgit:git-fast-export[1]. + +'import-marks' :: + This modifies the 'export' capability, instructing git to load the + marks specified in before processing any input. For details, + read up on '--import-marks=' in linkgit:git-fast-export[1]. -When git encounters a URL of the form '://
', where -'' is a protocol that it cannot handle natively, it -automatically invokes 'git remote-' with the full URL as -the second argument. If such a URL is encountered directly on the -command line, the first argument is the same as the second, and if it -is encountered in a configured remote, the first argument is the name -of that remote. -A URL of the form '::
' explicitly instructs git to -invoke 'git remote-' with '
' as the second -argument. If such a URL is encountered directly on the command line, -the first argument is '
', and if it is encountered in a -configured remote, the first argument is the name of that remote. -Additionally, when a configured remote has 'remote..vcs' set to -'', git explicitly invokes 'git remote-' with -'' as the first argument. If set, the second argument is -'remote..url'; otherwise, the second argument is omitted. COMMANDS -------- @@ -198,9 +213,11 @@ Commands are given by the caller on the helper's standard input, one per line. 'capabilities':: Lists the capabilities of the helper, one per line, ending with a blank line. Each capability may be preceded with '*', - which marks them mandatory for git version using the remote - helper to understand (unknown mandatory capability is fatal - error). + which marks them mandatory for git versions using the remote + helper to understand. Any unknown mandatory capability is a + fatal error. ++ +Support for this command is mandatory. 'list':: Lists the refs, one per line, in the format " @@ -210,9 +227,20 @@ Commands are given by the caller on the helper's standard input, one per line. the name; unrecognized attributes are ignored. The list ends with a blank line. + -If 'push' is supported this may be called as 'list for-push' -to obtain the current refs prior to sending one or more 'push' -commands to the helper. +See REF LIST ATTRIBUTES for a list of currently defined attributes. ++ +Supported if the helper has the "fetch" or "import" capability. + +'list for-push':: + Similar to 'list', except that it is used if and only if + the caller wants to the resulting ref list to prepare + push commands. + A helper supporting both push and fetch can use this + to distinguish for which operation the output of 'list' + is going to be used, possibly reducing the amount + of work that needs to be performed. ++ +Supported if the helper has the "push" or "export" capability. 'option' :: Sets the transport helper option to . Outputs a @@ -222,6 +250,8 @@ commands to the helper. for it). Options should be set before other commands, and may influence the behavior of those commands. + +See OPTIONS for a list of currently defined options. ++ Supported if the helper has the "option" capability. 'fetch' :: @@ -230,7 +260,7 @@ Supported if the helper has the "option" capability. per line, terminated with a blank line. Outputs a single blank line when all fetch commands in the same batch are complete. Only objects which were reported - in the ref list with a sha1 may be fetched this way. + in the output of 'list' with a sha1 may be fetched this way. + Optionally may output a 'lock ' line indicating a file under GIT_DIR/objects/pack which is keeping a pack until refs can be @@ -286,8 +316,29 @@ terminated with a blank line. For each batch of 'import', the remote helper should produce a fast-import stream terminated by a 'done' command. + +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. +'export':: + Instructs the remote helper that any subsequent input is + part of a fast-import stream (generated by 'git fast-export') + containing objects which should be pushed to the remote. ++ +Especially useful for interoperability with a foreign versioning +system. ++ +The 'export-marks' and 'import-marks' capabilities, if specified, +affect this command in so far as they are passed on to 'git +fast-export', which then will load/store a table of marks for +local objects. This can be used to implement for incremental +operations. ++ +Supported if the helper has the "export" capability. + 'connect' :: Connects to given service. Standard input and standard output of helper are connected to specified service (git prefix is @@ -313,10 +364,9 @@ capabilities reported by the helper. REF LIST ATTRIBUTES ------------------- -'for-push':: - The caller wants to use the ref list to prepare push - commands. A helper might chose to acquire the ref list by - opening a different type of connection to the destination. +The 'list' command produces a list of refs in which each ref +may be followed by a list of attributes. The following ref list +attributes are defined. 'unchanged':: This ref is unchanged since the last import or fetch, although @@ -324,6 +374,10 @@ REF LIST ATTRIBUTES OPTIONS ------- + +The following options are defined and (under suitable circumstances) +set by git if the remote helper has the 'option' capability. + 'option verbosity' :: Changes the verbosity of messages displayed by the helper. A value of 0 for means that processes operate diff --git a/Documentation/git-remote-testgit.txt b/Documentation/git-remote-testgit.txt index 2a67d456a3..612a625ced 100644 --- a/Documentation/git-remote-testgit.txt +++ b/Documentation/git-remote-testgit.txt @@ -19,7 +19,7 @@ testcase for the remote-helper functionality, and as an example to show remote-helper authors one possible implementation. The best way to learn more is to read the comments and source code in -'git-remote-testgit.py'. +'git-remote-testgit'. SEE ALSO -------- diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index 117e3743a6..a404b47b7b 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -8,20 +8,20 @@ git-reset - Reset current HEAD to the specified state SYNOPSIS -------- [verse] -'git reset' [-q] [] [--] ... -'git reset' (--patch | -p) [] [--] [...] -'git reset' (--soft | --mixed | --hard | --merge | --keep) [-q] [] +'git reset' [-q] [] [--] ... +'git reset' (--patch | -p) [] [--] [...] +'git reset' [--soft | --mixed | --hard | --merge | --keep] [-q] [] DESCRIPTION ----------- -In the first and second form, copy entries from to the index. +In the first and second form, copy entries from to the index. In the third form, set the current branch head (HEAD) to , optionally -modifying index and working tree to match. The defaults to HEAD -in all forms. +modifying index and working tree to match. The / defaults +to HEAD in all forms. -'git reset' [-q] [] [--] ...:: +'git reset' [-q] [] [--] ...:: This form resets the index entries for all to their - state at . (It does not affect the working tree, nor + state at . (It does not affect the working tree, nor the current branch.) + This means that `git reset ` is the opposite of `git add @@ -34,20 +34,20 @@ Alternatively, using linkgit:git-checkout[1] and specifying a commit, you can copy the contents of a path out of a commit to the index and to the working tree in one go. -'git reset' (--patch | -p) [] [--] [...]:: +'git reset' (--patch | -p) [] [--] [...]:: Interactively select hunks in the difference between the index - and (defaults to HEAD). The chosen hunks are applied + and (defaults to HEAD). The chosen hunks are applied in reverse to the index. + 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-shortlog.txt b/Documentation/git-shortlog.txt index afeb4cdf16..c308e91537 100644 --- a/Documentation/git-shortlog.txt +++ b/Documentation/git-shortlog.txt @@ -56,6 +56,9 @@ OPTIONS line of each entry is indented by `indent1` spaces, and the second and subsequent lines are indented by `indent2` spaces. `width`, `indent1`, and `indent2` default to 76, 6 and 9 respectively. ++ +If width is `0` (zero) then indent the lines of the output without wrapping +them. MAPPING AUTHORS diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index 0aa4e20eae..711ffe17a7 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -144,7 +144,7 @@ drop [-q|--quiet] []:: Remove a single stashed state from the stash list. When no `` is given, it removes the latest one. i.e. `stash@{0}`, otherwise - `` must a valid stash log reference of the form + `` must be a valid stash log reference of the form `stash@{}`. create:: 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 a65f38e184..b1996f1a63 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -9,11 +9,11 @@ git-submodule - Initialize, update or inspect submodules SYNOPSIS -------- [verse] -'git submodule' [--quiet] add [-b ] [-f|--force] +'git submodule' [--quiet] add [-b ] [-f|--force] [--name ] [--reference ] [--] [] 'git submodule' [--quiet] status [--cached] [--recursive] [--] [...] 'git submodule' [--quiet] init [--] [...] -'git submodule' [--quiet] update [--init] [-N|--no-fetch] [--rebase] +'git submodule' [--quiet] update [--init] [--remote] [-N|--no-fetch] [--rebase] [--reference ] [--merge] [--recursive] [--] [...] 'git submodule' [--quiet] summary [--cached|--files] [(-n|--summary-limit) ] [commit] [--] [...] @@ -208,6 +208,8 @@ OPTIONS -b:: --branch:: Branch of repository to add as submodule. + The name of the branch is recorded as `submodule..branch` in + `.gitmodules` for `update --remote`. -f:: --force:: @@ -236,6 +238,27 @@ OPTIONS (the default). This limit only applies to modified submodules. The size is always limited to 1 for added/deleted/typechanged submodules. +--remote:: + This option is only valid for the update command. Instead of using + the superproject's recorded SHA-1 to update the submodule, use the + status of the submodule's remote tracking branch. The remote used + is branch's remote (`branch..remote`), defaulting to `origin`. + The remote branch used defaults to `master`, but the branch name may + be overridden by setting the `submodule..branch` option in + either `.gitmodules` or `.git/config` (with `.git/config` taking + precedence). ++ +This works for any of the supported update procedures (`--checkout`, +`--rebase`, etc.). The only change is the source of the target SHA-1. +For example, `submodule update --remote --merge` will merge upstream +submodule changes into the submodules, while `submodule update +--merge` will merge superproject gitlink changes into the submodules. ++ +In order to ensure a current tracking branch state, `update --remote` +fetches the submodule's remote repository before calculating the +SHA-1. If you don't want to fetch, you should use `submodule update +--remote --no-fetch`. + -N:: --no-fetch:: This option is only valid for the update command. @@ -265,6 +288,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..34d438b0ab 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; @@ -339,6 +346,16 @@ Any other arguments are passed directly to 'git log' corresponding git commit hash (this can optionally be followed by a tree-ish to specify which branch should be searched). When given a tree-ish, returns the corresponding SVN revision number. ++ +--before;; + Don't require an exact match if given an SVN revision, instead find + the commit corresponding to the state of the SVN repository (on the + current branch) at the specified revision. ++ +--after;; + Don't require an exact match if given an SVN revision; if there is + not an exact match return the closest match searching forward in the + history. 'set-tree':: You should consider using 'dcommit' instead of this command. @@ -621,10 +638,19 @@ ADVANCED OPTIONS Default: "svn" --follow-parent:: + This option is only relevant if we are tracking branches (using + one of the repository layout options --trunk, --tags, + --branches, --stdlayout). For each tracked branch, try to find + out where its revision was copied from, and set + a suitable parent in the first git commit for the branch. This is especially helpful when we're tracking a directory - that has been moved around within the repository, or if we - started tracking a branch and never tracked the trunk it was - descended from. This feature is enabled by default, use + that has been moved around within the repository. If this + feature is disabled, the branches created by 'git svn' will all + be linear and not share any history, meaning that there will be + no information on where branches were branched off or merged. + However, following long/convoluted histories can take a long + time, so disabling this feature may speed up the cloning + process. This feature is enabled by default, use --no-follow-parent to disable it. + [verse] @@ -732,7 +758,8 @@ for rewriteRoot and rewriteUUID which can be used together. BASIC EXAMPLES -------------- -Tracking and contributing to the trunk of a Subversion-managed project: +Tracking and contributing to the trunk of a Subversion-managed project +(ignoring tags and branches): ------------------------------------------------------------------------ # Clone a repo (like git clone): @@ -757,8 +784,10 @@ Tracking and contributing to an entire Subversion-managed project (complete with a trunk, tags and branches): ------------------------------------------------------------------------ -# Clone a repo (like git clone): - git svn clone http://svn.example.com/project -T trunk -b branches -t tags +# Clone a repo with standard SVN directory layout (like git clone): + git svn clone http://svn.example.com/project --stdlayout +# Or, if the repo uses a non-standard directory layout: + git svn clone http://svn.example.com/project -T tr -b branch -t tag # View all branches and tags you have cloned: git branch -r # Create a new branch in SVN @@ -823,6 +852,52 @@ inside git back upstream to SVN users. Therefore it is advised that users keep history as linear as possible inside git to ease compatibility with SVN (see the CAVEATS section below). +HANDLING OF SVN BRANCHES +------------------------ +If 'git svn' is configured to fetch branches (and --follow-branches +is in effect), it sometimes creates multiple git branches for one +SVN branch, where the addtional branches have names of the form +'branchname@nnn' (with nnn an SVN revision number). These additional +branches are created if 'git svn' cannot find a parent commit for the +first commit in an SVN branch, to connect the branch to the history of +the other branches. + +Normally, the first commit in an SVN branch consists +of a copy operation. 'git svn' will read this commit to get the SVN +revision the branch was created from. It will then try to find the +git commit that corresponds to this SVN revision, and use that as the +parent of the branch. However, it is possible that there is no suitable +git commit to serve as parent. This will happen, among other reasons, +if the SVN branch is a copy of a revision that was not fetched by 'git +svn' (e.g. because it is an old revision that was skipped with +'--revision'), or if in SVN a directory was copied that is not tracked +by 'git svn' (such as a branch that is not tracked at all, or a +subdirectory of a tracked branch). In these cases, 'git svn' will still +create a git branch, but instead of using an existing git commit as the +parent of the branch, it will read the SVN history of the directory the +branch was copied from and create appropriate git commits. This is +indicated by the message "Initializing parent: ". + +Additionally, it will create a special branch named +'@', where is the SVN revision +number the branch was copied from. This branch will point to the newly +created parent commit of the branch. If in SVN the branch was deleted +and later recreated from a different version, there will be multiple +such branches with an '@'. + +Note that this may mean that multiple git commits are created for a +single SVN revision. + +An example: in an SVN repository with a standard +trunk/tags/branches layout, a directory trunk/sub is created in r.100. +In r.200, trunk/sub is branched by copying it to branches/. 'git svn +clone -s' will then create a branch 'sub'. It will also create new git +commits for r.100 through r.199 and use these as the history of branch +'sub'. Thus there will be two git commits for each revision from r.100 +to r.199 (one containing trunk/, one containing trunk/sub/). Finally, +it will create a branch 'sub@200' pointing to the new parent commit of +branch 'sub' (i.e. the commit for r.200 and trunk/sub/). + CAVEATS ------- @@ -864,6 +939,21 @@ already dcommitted. It is considered bad practice to --amend commits you've already pushed to a remote repository for other users, and dcommit with SVN is analogous to that. +When cloning an SVN repository, if none of the options for describing +the repository layout is used (--trunk, --tags, --branches, +--stdlayout), 'git svn clone' will create a git repository with +completely linear history, where branches and tags appear as separate +directories in the working copy. While this is the easiest way to get a +copy of a complete repository, for projects with many branches it will +lead to a working copy many times larger than just the trunk. Thus for +projects using the standard directory structure (trunk/branches/tags), +it is recommended to clone with option '--stdlayout'. If the project +uses a non-standard structure, and/or if branches and tags are not +required, it is easiest to only clone one directory (typically trunk), +without giving any repository layout options. If the full history with +branches and tags is required, the options '--trunk' / '--branches' / +'--tags' must be used. + When using multiple --branches or --tags, 'git svn' does not automatically handle name collisions (for example, if two branches from different paths have the same name, or if a branch and a tag have the same name). In these cases, @@ -887,6 +977,12 @@ the possible corner cases (git doesn't do it, either). Committing renamed and copied files is fully supported if they're similar enough for git to detect them. +In SVN, it is possible (though discouraged) to commit changes to a tag +(because a tag is just a directory copy, thus technically the same as a +branch). When cloning an SVN repository, 'git svn' cannot know if such a +commit to a tag will happen in the future. Thus it acts conservatively +and imports all SVN tags as branches, prefixing the tag name with 'tags/'. + CONFIGURATION ------------- 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-tag.txt b/Documentation/git-tag.txt index 247534e908..6470cffd32 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -129,7 +129,7 @@ This option is only applicable when listing tags without annotation lines. CONFIGURATION ------------- By default, 'git tag' in sign-with-default mode (-s) will use your -committer identity (of the form "Your Name ") to +committer identity (of the form "Your Name <\your@email.address>") to find a key. If you want to use a different default key, you can specify it in the repository configuration as follows: diff --git a/Documentation/git.txt b/Documentation/git.txt index cbe0883534..555250dfa0 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,9 +43,17 @@ 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.1/git.html[documentation for release 1.8.0.1] +* link:v1.8.1.1/git.html[documentation for release 1.8.1.1] * release notes for + link:RelNotes/1.8.1.1.txt[1.8.1.1], + link:RelNotes/1.8.1.txt[1.8.1]. + +* link:v1.8.0.3/git.html[documentation for release 1.8.0.3] + +* release notes for + link:RelNotes/1.8.0.3.txt[1.8.0.3], + link:RelNotes/1.8.0.2.txt[1.8.0.2], link:RelNotes/1.8.0.1.txt[1.8.0.1], link:RelNotes/1.8.0.txt[1.8.0]. @@ -421,6 +429,11 @@ help ...`. Do not use replacement refs to replace git objects. See linkgit:git-replace[1] for more information. +--literal-pathspecs:: + Treat pathspecs literally, rather than as glob patterns. This is + equivalent to setting the `GIT_LITERAL_PATHSPECS` environment + variable to `1`. + GIT COMMANDS ------------ @@ -649,6 +662,7 @@ git so take care if using Cogito etc. If the 'GIT_DIR' environment variable is set then it specifies a path to use instead of the default `.git` for the base of the repository. + The '--git-dir' command-line option also sets this value. 'GIT_WORK_TREE':: Set the path to the working tree. The value will not be @@ -764,6 +778,14 @@ for further details. and read the password from its STDOUT. See also the 'core.askpass' option in linkgit:git-config[1]. +'GIT_CONFIG_NOSYSTEM':: + Whether to skip reading settings from the system-wide + `$(prefix)/etc/gitconfig` file. This environment variable can + be used along with `$HOME` and `$XDG_CONFIG_HOME` to create a + predictable environment for a picky script, or you can set it + temporarily to avoid using a buggy `/etc/gitconfig` file while + waiting for someone with sufficient permissions to fix it. + 'GIT_FLUSH':: If this environment variable is set to "1", then commands such as 'git blame' (in incremental mode), 'git rev-list', 'git log', @@ -788,6 +810,16 @@ for further details. as a file path and will try to write the trace messages into it. +GIT_LITERAL_PATHSPECS:: + Setting this variable to `1` will cause git to treat all + pathspecs literally, rather than as glob patterns. For example, + running `GIT_LITERAL_PATHSPECS=1 git log -- '*.c'` will search + for commits that touch the path `*.c`, not any paths that the + glob `*.c` matches. You might want this if you are feeding + literal paths to git (e.g., paths previously given to you by + `git ls-tree`, `--raw` diff output, etc). + + Discussion[[Discussion]] ------------------------ @@ -868,8 +900,10 @@ Authors ------- Git was started by Linus Torvalds, and is currently maintained by Junio C Hamano. Numerous contributions have come from the git mailing list -. For a more complete list of contributors, see -http://git-scm.com/about. If you have a clone of git.git itself, the +. http://www.ohloh.net/p/git/contributors/summary +gives you a more complete list of contributors. + +If you have a clone of git.git itself, the output of linkgit:git-shortlog[1] and linkgit:git-blame[1] can show you the authors for specific parts of the project. 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/githooks.txt b/Documentation/githooks.txt index b9003fed24..d839233dff 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -176,6 +176,35 @@ save and restore any form of metadata associated with the working tree (eg: permissions/ownership, ACLS, etc). See contrib/hooks/setgitperms.perl for an example of how to do this. +pre-push +~~~~~~~~ + +This hook is called by 'git push' and can be used to prevent a push from taking +place. The hook is called with two parameters which provide the name and +location of the destination remote, if a named remote is not being used both +values will be the same. + +Information about what is to be pushed is provided on the hook's standard +input with lines of the form: + + SP SP SP LF + +For instance, if the command +git push origin master:foreign+ were run the +hook would receive a line like the following: + + refs/heads/master 67890 refs/heads/foreign 12345 + +although the full, 40-character SHA1s would be supplied. If the foreign ref +does not yet exist the `` will be 40 `0`. If a ref is to be +deleted, the `` will be supplied as `(delete)` and the `` will be 40 `0`. If the local commit was specified by something other +than a name which could be expanded (such as `HEAD~`, or a SHA1) it will be +supplied as it was originally given. + +If this hook exits with a non-zero status, 'git push' will abort without +pushing anything. Information about why the push is rejected may be sent +to the user by writing to standard error. + [[pre-receive]] pre-receive ~~~~~~~~~~~ diff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt index 1b82fe1969..0da205fd97 100644 --- a/Documentation/gitignore.txt +++ b/Documentation/gitignore.txt @@ -108,6 +108,25 @@ PATTERN FORMAT For example, "/{asterisk}.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c". +Two consecutive asterisks ("`**`") in patterns matched against +full pathname may have special meaning: + + - A leading "`**`" followed by a slash means match in all + directories. For example, "`**/foo`" matches file or directory + "`foo`" anywhere, the same as pattern "`foo`". "**/foo/bar" + matches file or directory "`bar`" anywhere that is directly + under directory "`foo`". + + - A trailing "/**" matches everything inside. For example, + "abc/**" matches all files inside directory "abc", relative + to the location of the `.gitignore` file, with infinite depth. + + - A slash followed by two consecutive asterisks then a slash + matches zero or more directories. For example, "`a/**/b`" + matches "`a/b`", "`a/x/b`", "`a/x/y/b`" and so on. + + - Other consecutive asterisks are considered invalid. + NOTES ----- @@ -165,8 +184,10 @@ The second .gitignore prevents git from ignoring SEE ALSO -------- -linkgit:git-rm[1], linkgit:git-update-index[1], -linkgit:gitrepository-layout[5] +linkgit:git-rm[1], +linkgit:git-update-index[1], +linkgit:gitrepository-layout[5], +linkgit:git-check-ignore[1] GIT --- diff --git a/Documentation/gitmodules.txt b/Documentation/gitmodules.txt index 4effd78902..52d7ae4313 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:: @@ -47,6 +49,11 @@ submodule..update:: This config option is overridden if 'git submodule update' is given the '--merge', '--rebase' or '--checkout' options. +submodule..branch:: + A remote branch name for tracking updates in the upstream submodule. + If the option is not specified, it defaults to 'master'. See the + `--remote` documentation in linkgit:git-submodule[1] for details. + submodule..fetchRecurseSubmodules:: This option can be used to control recursive fetching of this submodule. If this option is also present in the submodules entry in diff --git a/Documentation/howto/maintain-git.txt b/Documentation/howto/maintain-git.txt index e4fc2fa4f3..816c791502 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 +=================== Activities ---------- diff --git a/Documentation/howto/new-command.txt b/Documentation/howto/new-command.txt new file mode 100644 index 0000000000..36502f6718 --- /dev/null +++ b/Documentation/howto/new-command.txt @@ -0,0 +1,104 @@ +From: Eric S. Raymond +Abstract: This is how-to documentation for people who want to add extension + commands to git. It should be read alongside api-builtin.txt. +Content-type: text/asciidoc + +How to integrate 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. + +1. Don't forget to sign off your patch! + +2. Append your command name to one of the variables BUILTIN_OBJS, +EXTRA_PROGRAMS, SCRIPT_SH, SCRIPT_PERL or SCRIPT_PYTHON. + +3. Drop its test in the t directory. + +4. 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. + +5. If your command has any dependency on a particular version of +your language, document it in the INSTALL file. + +6. 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. + +7. 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/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/mailmap.txt b/Documentation/mailmap.txt index 288f04e70c..4a8c276529 100644 --- a/Documentation/mailmap.txt +++ b/Documentation/mailmap.txt @@ -1,5 +1,6 @@ If the file `.mailmap` exists at the toplevel of the repository, or at -the location pointed to by the mailmap.file configuration option, it +the location pointed to by the mailmap.file or mailmap.blob +configuration options, it is used to map author and committer names and email addresses to canonical real names and email addresses. @@ -46,7 +47,7 @@ Jane Doe Joe R. Developer ------------ -Note how there is no need for an entry for , because the +Note how there is no need for an entry for ``, because the real name of that author is already correct. Example 2: Your repository contains commits from the following diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index d9eddedc72..105f18a6f9 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -144,7 +144,11 @@ The placeholders are: - '%Cgreen': switch color to green - '%Cblue': switch color to blue - '%Creset': reset color -- '%C(...)': color specification, as described in color.branch.* config option +- '%C(...)': color specification, as described in color.branch.* config option; + adding `auto,` at the beginning will emit color only when colors are + enabled for log output (by `color.diff`, `color.ui`, or `--color`, and + respecting the `auto` settings of the former if we are going to a + terminal) - '%m': left, right or boundary mark - '%n': newline - '%%': a raw '%' diff --git a/Documentation/pt_BR/gittutorial.txt b/Documentation/pt_BR/gittutorial.txt deleted file mode 100644 index beba065252..0000000000 --- a/Documentation/pt_BR/gittutorial.txt +++ /dev/null @@ -1,675 +0,0 @@ -gittutorial(7) -============== - -NOME ----- -gittutorial - Um tutorial de introdução ao git (para versão 1.5.1 ou mais nova) - -SINOPSE --------- -git * - -DESCRIÇÃO ------------ - -Este tutorial explica como importar um novo projeto para o git, -adicionar mudanças a ele, e compartilhar mudanças com outros -desenvolvedores. - -Se, ao invés disso, você está interessado primariamente em usar git para -obter um projeto, por exemplo, para testar a última versão, você pode -preferir começar com os primeiros dois capítulos de -link:user-manual.html[O Manual do Usuário Git]. - -Primeiro, note que você pode obter documentação para um comando como -`git log --graph` com: - ------------------------------------------------- -$ man git-log ------------------------------------------------- - -ou: - ------------------------------------------------- -$ git help log ------------------------------------------------- - -Com a última forma, você pode usar o visualizador de manual de sua -escolha; veja linkgit:git-help[1] para maior informação. - -É uma boa idéia informar ao git seu nome e endereço público de email -antes de fazer qualquer operação. A maneira mais fácil de fazê-lo é: - ------------------------------------------------- -$ git config --global user.name "Seu Nome Vem Aqui" -$ git config --global user.email voce@seudominio.exemplo.com ------------------------------------------------- - - -Importando um novo projeto ------------------------ - -Assuma que você tem um tarball project.tar.gz com seu trabalho inicial. -Você pode colocá-lo sob controle de revisão git da seguinte forma: - ------------------------------------------------- -$ tar xzf project.tar.gz -$ cd project -$ git init ------------------------------------------------- - -Git irá responder - ------------------------------------------------- -Initialized empty Git repository in .git/ ------------------------------------------------- - -Agora que você iniciou seu diretório de trabalho, você deve ter notado que um -novo diretório foi criado com o nome de ".git". - -A seguir, diga ao git para gravar um instantâneo do conteúdo de todos os -arquivos sob o diretório atual (note o '.'), com 'git-add': - ------------------------------------------------- -$ git add . ------------------------------------------------- - -Este instantâneo está agora armazenado em uma área temporária que o git -chama de "index" ou índice. Você pode armazenar permanentemente o -conteúdo do índice no repositório com 'git-commit': - ------------------------------------------------- -$ git commit ------------------------------------------------- - -Isto vai te pedir por uma mensagem de commit. Você agora gravou sua -primeira versão de seu projeto no git. - -Fazendo mudanças --------------- - -Modifique alguns arquivos, e, então, adicione seu conteúdo atualizado ao -índice: - ------------------------------------------------- -$ git add file1 file2 file3 ------------------------------------------------- - -Você está agora pronto para fazer o commit. Você pode ver o que está -para ser gravado usando 'git-diff' com a opção --cached: - ------------------------------------------------- -$ git diff --cached ------------------------------------------------- - -(Sem --cached, o comando 'git-diff' irá te mostrar quaisquer mudanças -que você tenha feito mas ainda não adicionou ao índice.) Você também -pode obter um breve sumário da situação com 'git-status': - ------------------------------------------------- -$ git status -# On branch master -# Changes to be committed: -# (use "git reset HEAD ..." to unstage) -# -# modified: file1 -# modified: file2 -# modified: file3 -# ------------------------------------------------- - -Se você precisar fazer qualquer outro ajuste, faça-o agora, e, então, -adicione qualquer conteúdo modificado ao índice. Finalmente, grave suas -mudanças com: - ------------------------------------------------- -$ git commit ------------------------------------------------- - -Ao executar esse comando, ele irá te pedir uma mensagem descrevendo a mudança, -e, então, irá gravar a nova versão do projeto. - -Alternativamente, ao invés de executar 'git-add' antes, você pode usar - ------------------------------------------------- -$ git commit -a ------------------------------------------------- - -o que irá automaticamente notar quaisquer arquivos modificados (mas não -novos), adicioná-los ao índices, e gravar, tudo em um único passo. - -Uma nota em mensagens de commit: Apesar de não ser exigido, é uma boa -idéia começar a mensagem com uma simples e curta (menos de 50 -caracteres) linha sumarizando a mudança, seguida de uma linha em branco -e, então, uma descrição mais detalhada. Ferramentas que transformam -commits em email, por exemplo, usam a primeira linha no campo de -cabeçalho "Subject:" e o resto no corpo. - -Git rastreia conteúdo, não arquivos ----------------------------- - -Muitos sistemas de controle de revisão provêem um comando `add` que diz -ao sistema para começar a rastrear mudanças em um novo arquivo. O -comando `add` do git faz algo mais simples e mais poderoso: 'git-add' é -usado tanto para arquivos novos e arquivos recentemente modificados, e -em ambos os casos, ele tira o instantâneo dos arquivos dados e armazena -o conteúdo no índice, pronto para inclusão do próximo commit. - -Visualizando a história do projeto ------------------------ - -Em qualquer ponto você pode visualizar a história das suas mudanças -usando - ------------------------------------------------- -$ git log ------------------------------------------------- - -Se você também quiser ver a diferença completa a cada passo, use - ------------------------------------------------- -$ git log -p ------------------------------------------------- - -Geralmente, uma visão geral da mudança é útil para ter a sensação de -cada passo - ------------------------------------------------- -$ git log --stat --summary ------------------------------------------------- - -Gerenciando "branches"/ramos ------------------ - -Um simples repositório git pode manter múltiplos ramos de -desenvolvimento. Para criar um novo ramo chamado "experimental", use - ------------------------------------------------- -$ git branch experimental ------------------------------------------------- - -Se você executar agora - ------------------------------------------------- -$ git branch ------------------------------------------------- - -você vai obter uma lista de todos os ramos existentes: - ------------------------------------------------- - experimental -* master ------------------------------------------------- - -O ramo "experimental" é o que você acaba de criar, e o ramo "master" é o -ramo padrão que foi criado pra você automaticamente. O asterisco marca -o ramo em que você está atualmente; digite - ------------------------------------------------- -$ git checkout experimental ------------------------------------------------- - -para mudar para o ramo experimental. Agora edite um arquivo, grave a -mudança, e mude de volta para o ramo master: - ------------------------------------------------- -(edita arquivo) -$ git commit -a -$ git checkout master ------------------------------------------------- - -Verifique que a mudança que você fez não está mais visível, já que ela -foi feita no ramo experimental e você está de volta ao ramo master. - -Você pode fazer uma mudança diferente no ramo master: - ------------------------------------------------- -(edit file) -$ git commit -a ------------------------------------------------- - -neste ponto, os dois ramos divergiram, com diferentes mudanças feitas em -cada um. Para unificar as mudanças feitas no experimental para o -master, execute - ------------------------------------------------- -$ git merge experimental ------------------------------------------------- - -Se as mudanças não conflitarem, estará pronto. Se existirem conflitos, -marcadores serão deixados nos arquivos problemáticos exibindo o -conflito; - ------------------------------------------------- -$ git diff ------------------------------------------------- - -vai exibir isto. Após você editar os arquivos para resolver os -conflitos, - ------------------------------------------------- -$ git commit -a ------------------------------------------------- - -irá gravar o resultado da unificação. Finalmente, - ------------------------------------------------- -$ gitk ------------------------------------------------- - -vai mostrar uma bela representação gráfica da história resultante. - -Neste ponto você pode remover seu ramo experimental com - ------------------------------------------------- -$ git branch -d experimental ------------------------------------------------- - -Este comando garante que as mudanças no ramo experimental já estão no -ramo atual. - -Se você desenvolve em um ramo ideia-louca, e se arrepende, você pode -sempre remover o ramo com - -------------------------------------- -$ git branch -D ideia-louca -------------------------------------- - -Ramos são baratos e fáceis, então isto é uma boa maneira de experimentar -alguma coisa. - -Usando git para colaboração ---------------------------- - -Suponha que Alice começou um novo projeto com um repositório git em -/home/alice/project, e que Bob, que tem um diretório home na mesma -máquina, quer contribuir. - -Bob começa com: - ------------------------------------------------- -bob$ git clone /home/alice/project myrepo ------------------------------------------------- - -Isso cria um novo diretório "myrepo" contendo um clone do repositório de -Alice. O clone está no mesmo pé que o projeto original, possuindo sua -própria cópia da história do projeto original. - -Bob então faz algumas mudanças e as grava: - ------------------------------------------------- -(editar arquivos) -bob$ git commit -a -(repetir conforme necessário) ------------------------------------------------- - -Quanto está pronto, ele diz a Alice para puxar as mudanças do -repositório em /home/bob/myrepo. Ela o faz com: - ------------------------------------------------- -alice$ cd /home/alice/project -alice$ git pull /home/bob/myrepo master ------------------------------------------------- - -Isto unifica as mudanças do ramo "master" do Bob ao ramo atual de Alice. -Se Alice fez suas próprias mudanças no intervalo, ela, então, pode -precisar corrigir manualmente quaisquer conflitos. (Note que o argumento -"master" no comando acima é, de fato, desnecessário, já que é o padrão.) - -O comando "pull" executa, então, duas operações: ele obtém mudanças de -um ramo remoto, e, então, as unifica no ramo atual. - -Note que, em geral, Alice gostaria que suas mudanças locais fossem -gravadas antes de iniciar este "pull". Se o trabalho de Bob conflita -com o que Alice fez desde que suas histórias se ramificaram, Alice irá -usar seu diretório de trabalho e o índice para resolver conflitos, e -mudanças locais existentes irão interferir com o processo de resolução -de conflitos (git ainda irá realizar a obtenção mas irá se recusar a -unificar --- Alice terá que se livrar de suas mudanças locais de alguma -forma e puxar de novo quando isso acontecer). - -Alice pode espiar o que Bob fez sem unificar primeiro, usando o comando -"fetch"; isto permite Alice inspecionar o que Bob fez, usando um símbolo -especial "FETCH_HEAD", com o fim de determinar se ele tem alguma coisa -que vale puxar, assim: - ------------------------------------------------- -alice$ git fetch /home/bob/myrepo master -alice$ git log -p HEAD..FETCH_HEAD ------------------------------------------------- - -Esta operação é segura mesmo se Alice tem mudanças locais não gravadas. -A notação de intervalo "HEAD..FETCH_HEAD" significa mostrar tudo que é -alcançável de FETCH_HEAD mas exclua tudo o que é alcançável de HEAD. -Alice já sabe tudo que leva a seu estado atual (HEAD), e revisa o que Bob -tem em seu estado (FETCH_HEAD) que ela ainda não viu com esse comando. - -Se Alice quer visualizar o que Bob fez desde que suas histórias se -ramificaram, ela pode disparar o seguinte comando: - ------------------------------------------------- -$ gitk HEAD..FETCH_HEAD ------------------------------------------------- - -Isto usa a mesma notação de intervalo que vimos antes com 'git log'. - -Alice pode querer ver o que ambos fizeram desde que ramificaram. Ela -pode usar a forma com três pontos ao invés da forma com dois pontos: - ------------------------------------------------- -$ gitk HEAD...FETCH_HEAD ------------------------------------------------- - -Isto significa "mostre tudo que é alcançável de qualquer um deles, mas -exclua tudo que é alcançável a partir de ambos". - -Por favor, note que essas notações de intervalo podem ser usadas tanto -com gitk quanto com "git log". - -Após inspecionar o que Bob fez, se não há nada urgente, Alice pode -decidir continuar trabalhando sem puxar de Bob. Se a história de Bob -tem alguma coisa que Alice precisa imediatamente, Alice pode optar por -separar seu trabalho em progresso primeiro, fazer um "pull", e, então, -finalmente, retomar seu trabalho em progresso em cima da história -resultante. - -Quando você está trabalhando em um pequeno grupo unido, não é incomum -interagir com o mesmo repositório várias e várias vezes. Definindo um -repositório remoto antes de tudo, você pode fazê-lo mais facilmente: - ------------------------------------------------- -alice$ git remote add bob /home/bob/myrepo ------------------------------------------------- - -Com isso, Alice pode executar a primeira parte da operação "pull" usando -o comando 'git-fetch' sem unificar suas mudanças com seu próprio ramo, -usando: - -------------------------------------- -alice$ git fetch bob -------------------------------------- - -Diferente da forma longa, quando Alice obteve de Bob usando um -repositório remoto antes definido com 'git-remote', o que foi obtido é -armazenado em um ramo remoto, neste caso `bob/master`. Então, após isso: - -------------------------------------- -alice$ git log -p master..bob/master -------------------------------------- - -mostra uma lista de todas as mudanças que Bob fez desde que ramificou do -ramo master de Alice. - -Após examinar essas mudanças, Alice pode unificá-las em seu ramo master: - -------------------------------------- -alice$ git merge bob/master -------------------------------------- - -Esse `merge` pode também ser feito puxando de seu próprio ramo remoto, -assim: - -------------------------------------- -alice$ git pull . remotes/bob/master -------------------------------------- - -Note que 'git pull' sempre unifica ao ramo atual, independente do que -mais foi passado na linha de comando. - -Depois, Bob pode atualizar seu repositório com as últimas mudanças de -Alice, usando - -------------------------------------- -bob$ git pull -------------------------------------- - -Note que ele não precisa dar o caminho do repositório de Alice; quando -Bob clonou seu repositório, o git armazenou a localização de seu -repositório na configuração do mesmo, e essa localização é usada -para puxar: - -------------------------------------- -bob$ git config --get remote.origin.url -/home/alice/project -------------------------------------- - -(A configuração completa criada por 'git-clone' é visível usando `git -config -l`, e a página de manual linkgit:git-config[1] explica o -significado de cada opção.) - -Git também mantém uma cópia limpa do ramo master de Alice sob o nome -"origin/master": - -------------------------------------- -bob$ git branch -r - origin/master -------------------------------------- - -Se Bob decidir depois em trabalhar em um host diferente, ele ainda pode -executar clones e puxar usando o protocolo ssh: - -------------------------------------- -bob$ git clone alice.org:/home/alice/project myrepo -------------------------------------- - -Alternativamente, o git tem um protocolo nativo, ou pode usar rsync ou -http; veja linkgit:git-pull[1] para detalhes. - -Git pode também ser usado em um modo parecido com CVS, com um -repositório central para o qual vários usuários empurram modificações; -veja linkgit:git-push[1] e linkgit:gitcvs-migration[7]. - -Explorando história ------------------ - -A história no git é representada como uma série de commits -interrelacionados. Nós já vimos que o comando 'git-log' pode listar -esses commits. Note que a primeira linha de cada entrada no log também -dá o nome para o commit: - -------------------------------------- -$ git log -commit c82a22c39cbc32576f64f5c6b3f24b99ea8149c7 -Author: Junio C Hamano -Date: Tue May 16 17:18:22 2006 -0700 - - merge-base: Clarify the comments on post processing. -------------------------------------- - -Nós podemos dar este nome ao 'git-show' para ver os detalhes sobre este -commit. - -------------------------------------- -$ git show c82a22c39cbc32576f64f5c6b3f24b99ea8149c7 -------------------------------------- - -Mas há outras formas de se referir aos commits. Você pode usar qualquer -parte inicial do nome que seja longo o bastante para identificar -unicamente o commit: - -------------------------------------- -$ git show c82a22c39c # os primeiros caracteres do nome são o bastante - # usualmente -$ git show HEAD # a ponta do ramo atual -$ git show experimental # a ponta do ramo "experimental" -------------------------------------- - -Todo commit normalmente tem um commit "pai" que aponta para o estado -anterior do projeto: - -------------------------------------- -$ git show HEAD^ # para ver o pai de HEAD -$ git show HEAD^^ # para ver o avô de HEAD -$ git show HEAD~4 # para ver o trisavô de HEAD -------------------------------------- - -Note que commits de unificação podem ter mais de um pai: - -------------------------------------- -$ git show HEAD^1 # mostra o primeiro pai de HEAD (o mesmo que HEAD^) -$ git show HEAD^2 # mostra o segundo pai de HEAD -------------------------------------- - -Você também pode dar aos commits nomes à sua escolha; após executar - -------------------------------------- -$ git tag v2.5 1b2e1d63ff -------------------------------------- - -você pode se referir a 1b2e1d63ff pelo nome "v2.5". Se você pretende -compartilhar esse nome com outras pessoas (por exemplo, para identificar -uma versão de lançamento), você deveria criar um objeto "tag", e talvez -assiná-lo; veja linkgit:git-tag[1] para detalhes. - -Qualquer comando git que precise conhecer um commit pode receber -quaisquer desses nomes. Por exemplo: - -------------------------------------- -$ git diff v2.5 HEAD # compara o HEAD atual com v2.5 -$ git branch stable v2.5 # inicia um novo ramo chamado "stable" baseado - # em v2.5 -$ git reset --hard HEAD^ # reseta seu ramo atual e seu diretório de - # trabalho a seu estado em HEAD^ -------------------------------------- - -Seja cuidadoso com o último comando: além de perder quaisquer mudanças -em seu diretório de trabalho, ele também remove todos os commits -posteriores desse ramo. Se esse ramo é o único ramo contendo esses -commits, eles serão perdidos. Também, não use 'git-reset' num ramo -publicamente visível de onde outros desenvolvedores puxam, já que vai -forçar unificações desnecessárias para que outros desenvolvedores limpem -a história. Se você precisa desfazer mudanças que você empurrou, use -'git-revert' no lugar. - -O comando 'git-grep' pode buscar strings em qualquer versão de seu -projeto, então - -------------------------------------- -$ git grep "hello" v2.5 -------------------------------------- - -procura por todas as ocorrências de "hello" em v2.5. - -Se você deixar de fora o nome do commit, 'git-grep' irá procurar -quaisquer dos arquivos que ele gerencia no diretório corrente. Então - -------------------------------------- -$ git grep "hello" -------------------------------------- - -é uma forma rápida de buscar somente os arquivos que são rastreados pelo -git. - -Muitos comandos git também recebem um conjunto de commits, o que pode -ser especificado de várias formas. Aqui estão alguns exemplos com 'git-log': - -------------------------------------- -$ git log v2.5..v2.6 # commits entre v2.5 e v2.6 -$ git log v2.5.. # commits desde v2.5 -$ git log --since="2 weeks ago" # commits das últimas 2 semanas -$ git log v2.5.. Makefile # commits desde v2.5 que modificam - # Makefile -------------------------------------- - -Você também pode dar ao 'git-log' um "intervalo" de commits onde o -primeiro não é necessariamente um ancestral do segundo; por exemplo, se -as pontas dos ramos "stable" e "master" divergiram de um commit -comum algum tempo atrás, então - -------------------------------------- -$ git log stable..master -------------------------------------- - -irá listar os commits feitos no ramo "master" mas não no ramo -"stable", enquanto - -------------------------------------- -$ git log master..stable -------------------------------------- - -irá listar a lista de commits feitos no ramo "stable" mas não no ramo -"master". - -O comando 'git-log' tem uma fraqueza: ele precisa mostrar os commits em -uma lista. Quando a história tem linhas de desenvolvimento que -divergiram e então foram unificadas novamente, a ordem em que 'git-log' -apresenta essas mudanças é irrelevante. - -A maioria dos projetos com múltiplos contribuidores (como o kernel -Linux, ou o próprio git) tem unificações frequentes, e 'gitk' faz um -trabalho melhor de visualizar sua história. Por exemplo, - -------------------------------------- -$ gitk --since="2 weeks ago" drivers/ -------------------------------------- - -permite a você navegar em quaisquer commits desde as últimas duas semanas -de commits que modificaram arquivos sob o diretório "drivers". (Nota: -você pode ajustar as fontes do gitk segurando a tecla control enquanto -pressiona "-" ou "+".) - -Finalmente, a maioria dos comandos que recebem nomes de arquivo permitirão -também, opcionalmente, preceder qualquer nome de arquivo por um -commit, para especificar uma versão particular do arquivo: - -------------------------------------- -$ git diff v2.5:Makefile HEAD:Makefile.in -------------------------------------- - -Você pode usar 'git-show' para ver tal arquivo: - -------------------------------------- -$ git show v2.5:Makefile -------------------------------------- - -Próximos passos ----------- - -Este tutorial deve ser o bastante para operar controle de revisão -distribuído básico para seus projetos. No entanto, para entender -plenamente a profundidade e o poder do git você precisa entender duas -idéias simples nas quais ele se baseia: - - * A base de objetos é um sistema bem elegante usado para armazenar a - história de seu projeto--arquivos, diretórios, e commits. - - * O arquivo de índice é um cache do estado de uma árvore de diretório, - usado para criar commits, restaurar diretórios de trabalho, e - armazenar as várias árvores envolvidas em uma unificação. - -A parte dois deste tutorial explica a base de objetos, o arquivo de -índice, e algumas outras coisinhas que você vai precisar pra usar o -máximo do git. Você pode encontrá-la em linkgit:gittutorial-2[7]. - -Se você não quiser continuar com o tutorial agora nesse momento, algumas -outras digressões que podem ser interessantes neste ponto são: - - * linkgit:git-format-patch[1], linkgit:git-am[1]: Estes convertem - séries de commits em patches para email, e vice-versa, úteis para - projetos como o kernel Linux que dependem fortemente de patches - enviados por email. - - * linkgit:git-bisect[1]: Quando há uma regressão em seu projeto, uma - forma de rastrear um bug é procurando pela história para encontrar o - commit culpado. Git bisect pode ajudar a executar uma busca binária - por esse commit. Ele é inteligente o bastante para executar uma - busca próxima da ótima mesmo no caso de uma história complexa - não-linear com muitos ramos unificados. - - * link:everyday.html[GIT diariamente com 20 e tantos comandos] - - * linkgit:gitcvs-migration[7]: Git para usuários de CVS. - -VEJA TAMBÉM --------- -linkgit:gittutorial-2[7], -linkgit:gitcvs-migration[7], -linkgit:gitcore-tutorial[7], -linkgit:gitglossary[7], -linkgit:git-help[1], -link:everyday.html[git diariamente], -link:user-manual.html[O Manual do Usuário git] - -GIT ---- -Parte da suite linkgit:git[1]. 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-allocation-growing.txt b/Documentation/technical/api-allocation-growing.txt index 43dbe09f73..542946b1ba 100644 --- a/Documentation/technical/api-allocation-growing.txt +++ b/Documentation/technical/api-allocation-growing.txt @@ -5,7 +5,9 @@ Dynamically growing an array using realloc() is error prone and boring. Define your array with: -* a pointer (`ary`) that points at the array, initialized to `NULL`; +* a pointer (`item`) that points at the array, initialized to `NULL` + (although please name the variable based on its contents, not on its + type); * an integer variable (`alloc`) that keeps track of how big the current allocation is, initialized to `0`; @@ -13,22 +15,22 @@ Define your array with: * another integer variable (`nr`) to keep track of how many elements the array currently has, initialized to `0`. -Then before adding `n`th element to the array, call `ALLOC_GROW(ary, n, +Then before adding `n`th element to the item, call `ALLOC_GROW(item, n, alloc)`. This ensures that the array can hold at least `n` elements by calling `realloc(3)` and adjusting `alloc` variable. ------------ -sometype *ary; +sometype *item; size_t nr; size_t alloc for (i = 0; i < nr; i++) - if (we like ary[i] already) + if (we like item[i] already) return; /* we did not like any existing one, so add one */ -ALLOC_GROW(ary, nr + 1, alloc); -ary[nr++] = value you like; +ALLOC_GROW(item, nr + 1, alloc); +item[nr++] = value you like; ------------ You are responsible for updating the `nr` variable. 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-directory-listing.txt b/Documentation/technical/api-directory-listing.txt index add6f435b5..9d3e3527e4 100644 --- a/Documentation/technical/api-directory-listing.txt +++ b/Documentation/technical/api-directory-listing.txt @@ -9,37 +9,40 @@ Data structure -------------- `struct dir_struct` structure is used to pass directory traversal -options to the library and to record the paths discovered. The notable -options are: +options to the library and to record the paths discovered. A single +`struct dir_struct` is used regardless of whether or not the traversal +recursively descends into subdirectories. + +The notable options are: `exclude_per_dir`:: The name of the file to be read in each directory for excluded files (typically `.gitignore`). -`collect_ignored`:: +`flags`:: - Include paths that are to be excluded in the result. + A bit-field of options: -`show_ignored`:: +`DIR_SHOW_IGNORED`::: The traversal is for finding just ignored files, not unignored files. -`show_other_directories`:: +`DIR_SHOW_OTHER_DIRECTORIES`::: Include a directory that is not tracked. -`hide_empty_directories`:: +`DIR_HIDE_EMPTY_DIRECTORIES`::: Do not include a directory that is not tracked and is empty. -`no_gitlinks`:: +`DIR_NO_GITLINKS`::: If set, recurse into a directory that looks like a git directory. Otherwise it is shown as a directory. -The result of the enumeration is left in these fields:: +The result of the enumeration is left in these fields: `entries[]`:: @@ -64,11 +67,13 @@ marked. If you to exclude files, make sure you have loaded index first. * Prepare `struct dir_struct dir` and clear it with `memset(&dir, 0, sizeof(dir))`. -* Call `add_exclude()` to add single exclude pattern, - `add_excludes_from_file()` to add patterns from a file - (e.g. `.git/info/exclude`), and/or set `dir.exclude_per_dir`. A - short-hand function `setup_standard_excludes()` can be used to set up - the standard set of exclude settings. +* To add single exclude pattern, call `add_exclude_list()` and then + `add_exclude()`. + +* To add patterns from a file (e.g. `.git/info/exclude`), call + `add_excludes_from_file()` , and/or set `dir.exclude_per_dir`. A + short-hand function `setup_standard_excludes()` can be used to set + up the standard set of exclude settings. * Set options described in the Data Structure section above. @@ -76,4 +81,6 @@ marked. If you to exclude files, make sure you have loaded index first. * Use `dir.entries[]`. +* Call `clear_directory()` when none of the contained elements are no longer in use. + (JC) diff --git a/Documentation/technical/api-history-graph.txt b/Documentation/technical/api-history-graph.txt index d6fc90ac7e..18142b6d29 100644 --- a/Documentation/technical/api-history-graph.txt +++ b/Documentation/technical/api-history-graph.txt @@ -33,11 +33,11 @@ The following utility functions are wrappers around `graph_next_line()` and They can all be called with a NULL graph argument, in which case no graph output will be printed. -* `graph_show_commit()` calls `graph_next_line()` until it returns non-zero. - This prints all graph lines up to, and including, the line containing this - commit. Output is printed to stdout. The last line printed does not contain - a terminating newline. This should not be called if the commit line has - already been printed, or it will loop forever. +* `graph_show_commit()` calls `graph_next_line()` and + `graph_is_commit_finished()` until one of them return non-zero. This prints + all graph lines up to, and including, the line containing this commit. + Output is printed to stdout. The last line printed does not contain a + terminating newline. * `graph_show_oneline()` calls `graph_next_line()` and prints the result to stdout. The line printed does not contain a terminating newline. diff --git a/Documentation/technical/api-index-skel.txt b/Documentation/technical/api-index-skel.txt index af7cc2e395..730cfacf78 100644 --- a/Documentation/technical/api-index-skel.txt +++ b/Documentation/technical/api-index-skel.txt @@ -11,5 +11,3 @@ documents them. //////////////////////////////////////////////////////////////// // table of contents end //////////////////////////////////////////////////////////////// - -2007-11-24 diff --git a/Documentation/technical/api-run-command.txt b/Documentation/technical/api-run-command.txt index f18b4f4817..5d7d7f2d32 100644 --- a/Documentation/technical/api-run-command.txt +++ b/Documentation/technical/api-run-command.txt @@ -55,10 +55,8 @@ The functions above do the following: non-zero. . If the program terminated due to a signal, then the return value is the - signal number - 128, ie. it is negative and so indicates an unusual - condition; a diagnostic is printed. This return value can be passed to - exit(2), which will report the same code to the parent process that a - POSIX shell's $? would report for a program that died from the signal. + signal number + 128, ie. the same value that a POSIX shell's $? would + report. A diagnostic is printed. `start_async`:: 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..20be348834 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,13 +76,11 @@ Functions to be deleted. Preserve the order of the items that are retained. -`string_list_longest_prefix`:: +`string_list_remove_empty_items`:: - Return the longest string within a string_list that is a - prefix (in the sense of prefixcmp()) of the specified string, - or NULL if no such prefix exists. This function does not - require the string_list to be sorted (it does a linear - search). + 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. `print_string_list`:: diff --git a/Documentation/technical/index-format.txt b/Documentation/technical/index-format.txt index 9d25b30178..7324154838 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. @@ -161,8 +161,9 @@ GIT index format this span of index as a tree. An entry can be in an invalidated state and is represented by having - -1 in the entry_count field. In this case, there is no object name - and the next entry starts immediately after the newline. + a negative number in the entry_count field. In this case, there is no + object name and the next entry starts immediately after the newline. + When writing an invalid entry, -1 should always be used as entry_count. The entries are written out in the top-down, depth-first order. The first entry represents the root level of the repository, followed by the 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 d66e54f12d..e9f7abca91 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.8.0.1 +DEF_VER=v1.8.1.GIT LF=' ' diff --git a/Makefile b/Makefile index 6b73c14aa0..11e85a6396 100644 --- a/Makefile +++ b/Makefile @@ -74,10 +74,14 @@ all:: # Define NO_D_TYPE_IN_DIRENT if your platform defines DT_UNKNOWN but lacks # d_type in struct dirent (Cygwin 1.5, fixed in Cygwin 1.7). # +# Define HAVE_STRINGS_H if you have strings.h and need it for strcasecmp. +# # Define NO_STRCASESTR if you don't have strcasestr. # # Define NO_MEMMEM if you don't have memmem. # +# Define NO_GETPAGESIZE if you don't have getpagesize. +# # Define NO_STRLCPY if you don't have strlcpy. # # Define NO_STRTOUMAX if you don't have both strtoimax and strtoumax in the @@ -165,6 +169,10 @@ all:: # Define NO_POLL if you do not have or don't want to use poll(). # This also implies NO_SYS_POLL_H. # +# Define NEEDS_SYS_PARAM_H if you need to include sys/param.h to compile, +# *PLEASE* REPORT to git@vger.kernel.org if your platform needs this; +# we want to know more about the issue. +# # Define NO_PTHREADS if you do not have or do not want to use Pthreads. # # Define NO_PREAD if you have a problem with pread() system call (e.g. @@ -233,11 +241,16 @@ all:: # apostrophes to be ASCII so that cut&pasting examples to the shell # will work. # +# Define PERL_PATH to the path of your Perl binary (usually /usr/bin/perl). +# # Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's # MakeMaker (e.g. using ActiveState under Cygwin). # # Define NO_PERL if you do not want Perl scripts or libraries at all. # +# Define PYTHON_PATH to the path of your Python binary (often /usr/bin/python +# but /usr/bin/python2.7 on some platforms). +# # Define NO_PYTHON if you do not want Python scripts or libraries at all. # # Define NO_TCLTK if you do not want Tcl/Tk GUI. @@ -273,6 +286,10 @@ all:: # # Define NO_REGEX if you have no or inferior regex support in your C library. # +# Define CYGWIN_V15_WIN32API if you are using Cygwin v1.7.x but are not +# using the current w32api packages. The recommended approach, however, +# is to update your installation if compilation errors occur. +# # Define HAVE_DEV_TTY if your system can open /dev/tty to interact with the # user. # @@ -326,19 +343,6 @@ GIT-VERSION-FILE: FORCE @$(SHELL_PATH) ./GIT-VERSION-GEN -include GIT-VERSION-FILE -uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') -uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not') -uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not') -uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not') -uname_P := $(shell sh -c 'uname -p 2>/dev/null || echo not') -uname_V := $(shell sh -c 'uname -v 2>/dev/null || echo not') - -ifdef MSVC - # avoid the MingW and Cygwin configuration sections - uname_S := Windows - uname_O := Windows -endif - # CFLAGS and LDFLAGS are for the users to override from the command line. CFLAGS = -g -O2 -Wall @@ -374,7 +378,7 @@ htmldir = share/doc/git-doc ETC_GITCONFIG = $(sysconfdir)/gitconfig ETC_GITATTRIBUTES = $(sysconfdir)/gitattributes lib = lib -# DESTDIR= +# DESTDIR = pathsep = : export prefix bindir sharedir sysconfdir gitwebdir localedir @@ -470,7 +474,7 @@ SCRIPT_PERL += git-relink.perl SCRIPT_PERL += git-send-email.perl SCRIPT_PERL += git-svn.perl -SCRIPT_PYTHON += git-remote-testgit.py +SCRIPT_PYTHON += git-remote-testpy.py SCRIPT_PYTHON += git-p4.py SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \ @@ -495,6 +499,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 = @@ -523,6 +528,7 @@ TEST_PROGRAMS_NEED_X += test-sigchain TEST_PROGRAMS_NEED_X += test-string-list TEST_PROGRAMS_NEED_X += test-subprocess TEST_PROGRAMS_NEED_X += test-svn-fe +TEST_PROGRAMS_NEED_X += test-wildmatch TEST_PROGRAMS = $(patsubst %,%$X,$(TEST_PROGRAMS_NEED_X)) @@ -574,9 +580,9 @@ endif export PERL_PATH export PYTHON_PATH -LIB_FILE=libgit.a -XDIFF_LIB=xdiff/lib.a -VCSSVN_LIB=vcs-svn/lib.a +LIB_FILE = libgit.a +XDIFF_LIB = xdiff/lib.a +VCSSVN_LIB = vcs-svn/lib.a LIB_H += xdiff/xinclude.h LIB_H += xdiff/xmacros.h @@ -648,7 +654,7 @@ LIB_H += list-objects.h LIB_H += ll-merge.h LIB_H += log-tree.h LIB_H += mailmap.h -LIB_H += merge-file.h +LIB_H += merge-blobs.h LIB_H += merge-recursive.h LIB_H += mergesort.h LIB_H += notes-cache.h @@ -660,6 +666,7 @@ LIB_H += pack-revindex.h LIB_H += pack.h LIB_H += parse-options.h LIB_H += patch-ids.h +LIB_H += pathspec.h LIB_H += pkt-line.h LIB_H += progress.h LIB_H += prompt.h @@ -695,6 +702,7 @@ LIB_H += userdiff.h LIB_H += utf8.h LIB_H += varint.h LIB_H += walker.h +LIB_H += wildmatch.h LIB_H += wt-status.h LIB_H += xdiff-interface.h LIB_H += xdiff/xdiff.h @@ -745,6 +753,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,7 +771,8 @@ LIB_OBJS += lockfile.o LIB_OBJS += log-tree.o LIB_OBJS += mailmap.o LIB_OBJS += match-trees.o -LIB_OBJS += merge-file.o +LIB_OBJS += merge.o +LIB_OBJS += merge-blobs.o LIB_OBJS += merge-recursive.o LIB_OBJS += mergesort.o LIB_OBJS += name-hash.o @@ -780,6 +790,7 @@ LIB_OBJS += parse-options-cb.o LIB_OBJS += patch-delta.o LIB_OBJS += patch-ids.o LIB_OBJS += path.o +LIB_OBJS += pathspec.o LIB_OBJS += pkt-line.o LIB_OBJS += preload-index.o LIB_OBJS += pretty.o @@ -796,6 +807,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 @@ -826,6 +838,7 @@ LIB_OBJS += utf8.o LIB_OBJS += varint.o LIB_OBJS += version.o LIB_OBJS += walker.o +LIB_OBJS += wildmatch.o LIB_OBJS += wrapper.o LIB_OBJS += write_or_die.o LIB_OBJS += ws.o @@ -843,6 +856,7 @@ BUILTIN_OBJS += builtin/branch.o BUILTIN_OBJS += builtin/bundle.o BUILTIN_OBJS += builtin/cat-file.o BUILTIN_OBJS += builtin/check-attr.o +BUILTIN_OBJS += builtin/check-ignore.o BUILTIN_OBJS += builtin/check-ref-format.o BUILTIN_OBJS += builtin/checkout-index.o BUILTIN_OBJS += builtin/checkout.o @@ -933,513 +947,7 @@ EXTLIBS = GIT_USER_AGENT = git/$(GIT_VERSION) -# -# Platform specific tweaks -# - -# We choose to avoid "if .. else if .. else .. endif endif" -# because maintaining the nesting to match is a pain. If -# we had "elif" things would have been much nicer... - -ifeq ($(uname_M),x86_64) - XDL_FAST_HASH = YesPlease -endif -ifeq ($(uname_S),OSF1) - # Need this for u_short definitions et al - BASIC_CFLAGS += -D_OSF_SOURCE - SOCKLEN_T = int - NO_STRTOULL = YesPlease - NO_NSEC = YesPlease -endif -ifeq ($(uname_S),Linux) - NO_STRLCPY = YesPlease - NO_MKSTEMPS = YesPlease - HAVE_PATHS_H = YesPlease - LIBC_CONTAINS_LIBINTL = YesPlease - HAVE_DEV_TTY = YesPlease -endif -ifeq ($(uname_S),GNU/kFreeBSD) - NO_STRLCPY = YesPlease - NO_MKSTEMPS = YesPlease - HAVE_PATHS_H = YesPlease - DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease - LIBC_CONTAINS_LIBINTL = YesPlease -endif -ifeq ($(uname_S),UnixWare) - CC = cc - NEEDS_SOCKET = YesPlease - NEEDS_NSL = YesPlease - NEEDS_SSL_WITH_CRYPTO = YesPlease - NEEDS_LIBICONV = YesPlease - SHELL_PATH = /usr/local/bin/bash - NO_IPV6 = YesPlease - NO_HSTRERROR = YesPlease - NO_MKSTEMPS = YesPlease - BASIC_CFLAGS += -Kthread - BASIC_CFLAGS += -I/usr/local/include - BASIC_LDFLAGS += -L/usr/local/lib - INSTALL = ginstall - TAR = gtar - NO_STRCASESTR = YesPlease - NO_MEMMEM = YesPlease -endif -ifeq ($(uname_S),SCO_SV) - ifeq ($(uname_R),3.2) - CFLAGS = -O2 - endif - ifeq ($(uname_R),5) - CC = cc - BASIC_CFLAGS += -Kthread - endif - NEEDS_SOCKET = YesPlease - NEEDS_NSL = YesPlease - NEEDS_SSL_WITH_CRYPTO = YesPlease - NEEDS_LIBICONV = YesPlease - SHELL_PATH = /usr/bin/bash - NO_IPV6 = YesPlease - NO_HSTRERROR = YesPlease - NO_MKSTEMPS = YesPlease - BASIC_CFLAGS += -I/usr/local/include - BASIC_LDFLAGS += -L/usr/local/lib - NO_STRCASESTR = YesPlease - NO_MEMMEM = YesPlease - INSTALL = ginstall - TAR = gtar -endif -ifeq ($(uname_S),Darwin) - NEEDS_CRYPTO_WITH_SSL = YesPlease - NEEDS_SSL_WITH_CRYPTO = YesPlease - NEEDS_LIBICONV = YesPlease - ifeq ($(shell expr "$(uname_R)" : '[15678]\.'),2) - OLD_ICONV = UnfortunatelyYes - endif - ifeq ($(shell expr "$(uname_R)" : '[15]\.'),2) - NO_STRLCPY = YesPlease - endif - NO_MEMMEM = YesPlease - USE_ST_TIMESPEC = YesPlease - HAVE_DEV_TTY = YesPlease - COMPAT_OBJS += compat/precompose_utf8.o - BASIC_CFLAGS += -DPRECOMPOSE_UNICODE -endif -ifeq ($(uname_S),SunOS) - NEEDS_SOCKET = YesPlease - NEEDS_NSL = YesPlease - SHELL_PATH = /bin/bash - SANE_TOOL_PATH = /usr/xpg6/bin:/usr/xpg4/bin - NO_STRCASESTR = YesPlease - NO_MEMMEM = YesPlease - NO_MKDTEMP = YesPlease - NO_MKSTEMPS = YesPlease - NO_REGEX = YesPlease - NO_FNMATCH_CASEFOLD = YesPlease - NO_MSGFMT_EXTENDED_OPTIONS = YesPlease - HAVE_DEV_TTY = YesPlease - ifeq ($(uname_R),5.6) - SOCKLEN_T = int - NO_HSTRERROR = YesPlease - NO_IPV6 = YesPlease - NO_SOCKADDR_STORAGE = YesPlease - NO_UNSETENV = YesPlease - NO_SETENV = YesPlease - NO_STRLCPY = YesPlease - NO_STRTOUMAX = YesPlease - GIT_TEST_CMP = cmp - endif - ifeq ($(uname_R),5.7) - NEEDS_RESOLV = YesPlease - NO_IPV6 = YesPlease - NO_SOCKADDR_STORAGE = YesPlease - NO_UNSETENV = YesPlease - NO_SETENV = YesPlease - NO_STRLCPY = YesPlease - NO_STRTOUMAX = YesPlease - GIT_TEST_CMP = cmp - endif - ifeq ($(uname_R),5.8) - NO_UNSETENV = YesPlease - NO_SETENV = YesPlease - NO_STRTOUMAX = YesPlease - GIT_TEST_CMP = cmp - endif - ifeq ($(uname_R),5.9) - NO_UNSETENV = YesPlease - NO_SETENV = YesPlease - NO_STRTOUMAX = YesPlease - GIT_TEST_CMP = cmp - endif - INSTALL = /usr/ucb/install - TAR = gtar - BASIC_CFLAGS += -D__EXTENSIONS__ -D__sun__ -DHAVE_ALLOCA_H -endif -ifeq ($(uname_O),Cygwin) - ifeq ($(shell expr "$(uname_R)" : '1\.[1-6]\.'),4) - NO_D_TYPE_IN_DIRENT = YesPlease - NO_D_INO_IN_DIRENT = YesPlease - NO_STRCASESTR = YesPlease - NO_MEMMEM = YesPlease - NO_MKSTEMPS = YesPlease - NO_SYMLINK_HEAD = YesPlease - NO_IPV6 = YesPlease - OLD_ICONV = UnfortunatelyYes - endif - NO_THREAD_SAFE_PREAD = YesPlease - NEEDS_LIBICONV = YesPlease - NO_FAST_WORKING_DIRECTORY = UnfortunatelyYes - NO_TRUSTABLE_FILEMODE = UnfortunatelyYes - NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease - # There are conflicting reports about this. - # On some boxes NO_MMAP is needed, and not so elsewhere. - # Try commenting this out if you suspect MMAP is more efficient - NO_MMAP = YesPlease - X = .exe - COMPAT_OBJS += compat/cygwin.o - UNRELIABLE_FSTAT = UnfortunatelyYes - SPARSE_FLAGS = -isystem /usr/include/w32api -Wno-one-bit-signed-bitfield -endif -ifeq ($(uname_S),FreeBSD) - NEEDS_LIBICONV = YesPlease - OLD_ICONV = YesPlease - NO_MEMMEM = YesPlease - BASIC_CFLAGS += -I/usr/local/include - BASIC_LDFLAGS += -L/usr/local/lib - DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease - USE_ST_TIMESPEC = YesPlease - ifeq ($(shell expr "$(uname_R)" : '4\.'),2) - PTHREAD_LIBS = -pthread - NO_UINTMAX_T = YesPlease - NO_STRTOUMAX = YesPlease - endif - PYTHON_PATH = /usr/local/bin/python - HAVE_PATHS_H = YesPlease -endif -ifeq ($(uname_S),OpenBSD) - NO_STRCASESTR = YesPlease - NO_MEMMEM = YesPlease - USE_ST_TIMESPEC = YesPlease - NEEDS_LIBICONV = YesPlease - BASIC_CFLAGS += -I/usr/local/include - BASIC_LDFLAGS += -L/usr/local/lib - HAVE_PATHS_H = YesPlease -endif -ifeq ($(uname_S),NetBSD) - ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2) - NEEDS_LIBICONV = YesPlease - endif - BASIC_CFLAGS += -I/usr/pkg/include - BASIC_LDFLAGS += -L/usr/pkg/lib $(CC_LD_DYNPATH)/usr/pkg/lib - USE_ST_TIMESPEC = YesPlease - NO_MKSTEMPS = YesPlease - HAVE_PATHS_H = YesPlease -endif -ifeq ($(uname_S),AIX) - DEFAULT_PAGER = more - NO_STRCASESTR=YesPlease - NO_MEMMEM = YesPlease - NO_MKDTEMP = YesPlease - NO_MKSTEMPS = YesPlease - NO_STRLCPY = YesPlease - NO_NSEC = YesPlease - FREAD_READS_DIRECTORIES = UnfortunatelyYes - INTERNAL_QSORT = UnfortunatelyYes - NEEDS_LIBICONV=YesPlease - BASIC_CFLAGS += -D_LARGE_FILES - ifeq ($(shell expr "$(uname_V)" : '[1234]'),1) - NO_PTHREADS = YesPlease - else - PTHREAD_LIBS = -lpthread - endif - ifeq ($(shell expr "$(uname_V).$(uname_R)" : '5\.1'),3) - INLINE='' - endif - GIT_TEST_CMP = cmp -endif -ifeq ($(uname_S),GNU) - # GNU/Hurd - NO_STRLCPY=YesPlease - NO_MKSTEMPS = YesPlease - HAVE_PATHS_H = YesPlease - LIBC_CONTAINS_LIBINTL = YesPlease -endif -ifeq ($(uname_S),IRIX) - NO_SETENV = YesPlease - NO_UNSETENV = YesPlease - NO_STRCASESTR = YesPlease - NO_MEMMEM = YesPlease - NO_MKSTEMPS = YesPlease - NO_MKDTEMP = YesPlease - # When compiled with the MIPSpro 7.4.4m compiler, and without pthreads - # (i.e. NO_PTHREADS is set), and _with_ MMAP (i.e. NO_MMAP is not set), - # git dies with a segmentation fault when trying to access the first - # entry of a reflog. The conservative choice is made to always set - # NO_MMAP. If you suspect that your compiler is not affected by this - # issue, comment out the NO_MMAP statement. - NO_MMAP = YesPlease - NO_REGEX = YesPlease - NO_FNMATCH_CASEFOLD = YesPlease - SNPRINTF_RETURNS_BOGUS = YesPlease - SHELL_PATH = /usr/gnu/bin/bash - NEEDS_LIBGEN = YesPlease -endif -ifeq ($(uname_S),IRIX64) - NO_SETENV=YesPlease - NO_UNSETENV = YesPlease - NO_STRCASESTR=YesPlease - NO_MEMMEM = YesPlease - NO_MKSTEMPS = YesPlease - NO_MKDTEMP = YesPlease - # When compiled with the MIPSpro 7.4.4m compiler, and without pthreads - # (i.e. NO_PTHREADS is set), and _with_ MMAP (i.e. NO_MMAP is not set), - # git dies with a segmentation fault when trying to access the first - # entry of a reflog. The conservative choice is made to always set - # NO_MMAP. If you suspect that your compiler is not affected by this - # issue, comment out the NO_MMAP statement. - NO_MMAP = YesPlease - NO_REGEX = YesPlease - NO_FNMATCH_CASEFOLD = YesPlease - SNPRINTF_RETURNS_BOGUS = YesPlease - SHELL_PATH=/usr/gnu/bin/bash - NEEDS_LIBGEN = YesPlease -endif -ifeq ($(uname_S),HP-UX) - INLINE = __inline - NO_IPV6=YesPlease - NO_SETENV=YesPlease - NO_STRCASESTR=YesPlease - NO_MEMMEM = YesPlease - NO_MKSTEMPS = YesPlease - NO_STRLCPY = YesPlease - NO_MKDTEMP = YesPlease - NO_UNSETENV = YesPlease - NO_HSTRERROR = YesPlease - NO_SYS_SELECT_H = YesPlease - NO_FNMATCH_CASEFOLD = YesPlease - SNPRINTF_RETURNS_BOGUS = YesPlease - NO_NSEC = YesPlease - ifeq ($(uname_R),B.11.00) - NO_INET_NTOP = YesPlease - NO_INET_PTON = YesPlease - endif - ifeq ($(uname_R),B.10.20) - # Override HP-UX 11.x setting: - INLINE = - SOCKLEN_T = size_t - NO_PREAD = YesPlease - NO_INET_NTOP = YesPlease - NO_INET_PTON = YesPlease - endif - GIT_TEST_CMP = cmp -endif -ifeq ($(uname_S),Windows) - GIT_VERSION := $(GIT_VERSION).MSVC - pathsep = ; - NO_PREAD = YesPlease - NEEDS_CRYPTO_WITH_SSL = YesPlease - NO_LIBGEN_H = YesPlease - NO_POLL = YesPlease - NO_SYMLINK_HEAD = YesPlease - NO_IPV6 = YesPlease - NO_UNIX_SOCKETS = YesPlease - NO_SETENV = YesPlease - NO_UNSETENV = YesPlease - NO_STRCASESTR = YesPlease - NO_STRLCPY = YesPlease - NO_STRTOK_R = YesPlease - NO_FNMATCH = YesPlease - NO_MEMMEM = YesPlease - # NEEDS_LIBICONV = YesPlease - NO_ICONV = YesPlease - NO_STRTOUMAX = YesPlease - NO_STRTOULL = YesPlease - NO_MKDTEMP = YesPlease - NO_MKSTEMPS = YesPlease - SNPRINTF_RETURNS_BOGUS = YesPlease - NO_SVN_TESTS = YesPlease - NO_PERL_MAKEMAKER = YesPlease - RUNTIME_PREFIX = YesPlease - NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease - NO_NSEC = YesPlease - USE_WIN32_MMAP = YesPlease - # USE_NED_ALLOCATOR = YesPlease - UNRELIABLE_FSTAT = UnfortunatelyYes - OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo - NO_REGEX = YesPlease - NO_CURL = YesPlease - NO_PYTHON = YesPlease - BLK_SHA1 = YesPlease - NO_POSIX_GOODIES = UnfortunatelyYes - NATIVE_CRLF = YesPlease - DEFAULT_HELP_FORMAT = html - - CC = compat/vcbuild/scripts/clink.pl - AR = compat/vcbuild/scripts/lib.pl - CFLAGS = - BASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE - COMPAT_OBJS = compat/msvc.o compat/winansi.o \ - compat/win32/pthread.o compat/win32/syslog.o \ - compat/win32/dirent.o - COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\" - BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE -NODEFAULTLIB:MSVCRT.lib - EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib - PTHREAD_LIBS = - lib = -ifndef DEBUG - BASIC_CFLAGS += -GL -Os -MT - BASIC_LDFLAGS += -LTCG - AR += -LTCG -else - BASIC_CFLAGS += -Zi -MTd -endif - X = .exe -endif -ifeq ($(uname_S),Interix) - NO_INITGROUPS = YesPlease - NO_IPV6 = YesPlease - NO_MEMMEM = YesPlease - NO_MKDTEMP = YesPlease - NO_STRTOUMAX = YesPlease - NO_NSEC = YesPlease - NO_MKSTEMPS = YesPlease - ifeq ($(uname_R),3.5) - NO_INET_NTOP = YesPlease - NO_INET_PTON = YesPlease - NO_SOCKADDR_STORAGE = YesPlease - NO_FNMATCH_CASEFOLD = YesPlease - endif - ifeq ($(uname_R),5.2) - NO_INET_NTOP = YesPlease - NO_INET_PTON = YesPlease - NO_SOCKADDR_STORAGE = YesPlease - NO_FNMATCH_CASEFOLD = YesPlease - endif -endif -ifeq ($(uname_S),Minix) - NO_IPV6 = YesPlease - NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease - NO_NSEC = YesPlease - NEEDS_LIBGEN = - NEEDS_CRYPTO_WITH_SSL = YesPlease - NEEDS_IDN_WITH_CURL = YesPlease - NEEDS_SSL_WITH_CURL = YesPlease - NEEDS_RESOLV = - NO_HSTRERROR = YesPlease - NO_MMAP = YesPlease - NO_CURL = - NO_EXPAT = -endif -ifeq ($(uname_S),NONSTOP_KERNEL) - # Needs some C99 features, "inline" is just one of them. - # INLINE='' would just replace one set of warnings with another and - # still not compile in c89 mode, due to non-const array initializations. - CC = cc -c99 - # Disable all optimization, seems to result in bad code, with -O or -O2 - # or even -O1 (default), /usr/local/libexec/git-core/git-pack-objects - # abends on "git push". Needs more investigation. - CFLAGS = -g -O0 - # We'd want it to be here. - prefix = /usr/local - # Our's are in ${prefix}/bin (perl might also be in /usr/bin/perl). - PERL_PATH = ${prefix}/bin/perl - PYTHON_PATH = ${prefix}/bin/python - - # As detected by './configure'. - # Missdetected, hence commented out, see below. - #NO_CURL = YesPlease - # Added manually, see above. - NEEDS_SSL_WITH_CURL = YesPlease - HAVE_LIBCHARSET_H = YesPlease - NEEDS_LIBICONV = YesPlease - NEEDS_LIBINTL_BEFORE_LIBICONV = YesPlease - NO_SYS_SELECT_H = UnfortunatelyYes - NO_D_TYPE_IN_DIRENT = YesPlease - NO_HSTRERROR = YesPlease - NO_STRCASESTR = YesPlease - NO_FNMATCH_CASEFOLD = YesPlease - NO_MEMMEM = YesPlease - NO_STRLCPY = YesPlease - NO_SETENV = YesPlease - NO_UNSETENV = YesPlease - NO_MKDTEMP = YesPlease - NO_MKSTEMPS = YesPlease - # Currently libiconv-1.9.1. - OLD_ICONV = UnfortunatelyYes - NO_REGEX = YesPlease - NO_PTHREADS = UnfortunatelyYes - - # Not detected (nor checked for) by './configure'. - # We don't have SA_RESTART on NonStop, unfortunalety. - COMPAT_CFLAGS += -DSA_RESTART=0 - # Apparently needed in compat/fnmatch/fnmatch.c. - COMPAT_CFLAGS += -DHAVE_STRING_H=1 - NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease - NO_NSEC = YesPlease - NO_PREAD = YesPlease - NO_MMAP = YesPlease - NO_POLL = YesPlease - NO_INTPTR_T = UnfortunatelyYes - # Bug report 10-120822-4477 submitted to HP NonStop development. - MKDIR_WO_TRAILING_SLASH = YesPlease - # RFE 10-120912-4693 submitted to HP NonStop development. - NO_SETITIMER = UnfortunatelyYes -endif -ifneq (,$(findstring MINGW,$(uname_S))) - pathsep = ; - NO_PREAD = YesPlease - NEEDS_CRYPTO_WITH_SSL = YesPlease - NO_LIBGEN_H = YesPlease - NO_POLL = YesPlease - NO_SYMLINK_HEAD = YesPlease - NO_UNIX_SOCKETS = YesPlease - NO_SETENV = YesPlease - NO_UNSETENV = YesPlease - NO_STRCASESTR = YesPlease - NO_STRLCPY = YesPlease - NO_STRTOK_R = YesPlease - NO_FNMATCH = YesPlease - NO_MEMMEM = YesPlease - NEEDS_LIBICONV = YesPlease - OLD_ICONV = YesPlease - NO_STRTOUMAX = YesPlease - NO_MKDTEMP = YesPlease - NO_MKSTEMPS = YesPlease - NO_SVN_TESTS = YesPlease - NO_PERL_MAKEMAKER = YesPlease - RUNTIME_PREFIX = YesPlease - NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease - NO_NSEC = YesPlease - USE_WIN32_MMAP = YesPlease - USE_NED_ALLOCATOR = YesPlease - UNRELIABLE_FSTAT = UnfortunatelyYes - OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo - NO_REGEX = YesPlease - NO_PYTHON = YesPlease - BLK_SHA1 = YesPlease - ETAGS_TARGET = ETAGS - NO_INET_PTON = YesPlease - NO_INET_NTOP = YesPlease - NO_POSIX_GOODIES = UnfortunatelyYes - COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/win32 - COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\" - COMPAT_OBJS += compat/mingw.o compat/winansi.o \ - compat/win32/pthread.o compat/win32/syslog.o \ - compat/win32/dirent.o - EXTLIBS += -lws2_32 - PTHREAD_LIBS = - X = .exe - SPARSE_FLAGS = -Wno-one-bit-signed-bitfield -ifneq (,$(wildcard ../THIS_IS_MSYSGIT)) - htmldir=doc/git/html/ - prefix = - INSTALL = /bin/install - EXTLIBS += /mingw/lib/libz.a - NO_R_TO_GCC_LINKER = YesPlease - INTERNAL_QSORT = YesPlease - HAVE_LIBCHARSET_H = YesPlease -else - NO_CURL = YesPlease -endif -endif - +include config.mak.uname -include config.mak.autogen -include config.mak @@ -1550,7 +1058,7 @@ else CURL_LIBCURL = -lcurl endif ifdef NEEDS_SSL_WITH_CURL - CURL_LIBCURL += -lssl + CURL_LIBCURL += -lssl ifdef NEEDS_CRYPTO_WITH_SSL CURL_LIBCURL += -lcrypto endif @@ -1646,6 +1154,9 @@ endif ifdef NO_D_INO_IN_DIRENT BASIC_CFLAGS += -DNO_D_INO_IN_DIRENT endif +ifdef NO_GECOS_IN_PWENT + BASIC_CFLAGS += -DNO_GECOS_IN_PWENT +endif ifdef NO_ST_BLOCKS_IN_STRUCT_STAT BASIC_CFLAGS += -DNO_ST_BLOCKS_IN_STRUCT_STAT endif @@ -1739,6 +1250,9 @@ endif ifdef NO_SYS_POLL_H BASIC_CFLAGS += -DNO_SYS_POLL_H endif +ifdef NEEDS_SYS_PARAM_H + BASIC_CFLAGS += -DNEEDS_SYS_PARAM_H +endif ifdef NO_INTTYPES_H BASIC_CFLAGS += -DNO_INTTYPES_H endif @@ -1759,7 +1273,7 @@ ifdef OBJECT_CREATION_USES_RENAMES endif ifdef NO_STRUCT_ITIMERVAL COMPAT_CFLAGS += -DNO_STRUCT_ITIMERVAL - NO_SETITIMER=YesPlease + NO_SETITIMER = YesPlease endif ifdef NO_SETITIMER COMPAT_CFLAGS += -DNO_SETITIMER @@ -1850,6 +1364,9 @@ ifdef NO_MEMMEM COMPAT_CFLAGS += -DNO_MEMMEM COMPAT_OBJS += compat/memmem.o endif +ifdef NO_GETPAGESIZE + COMPAT_CFLAGS += -DNO_GETPAGESIZE +endif ifdef INTERNAL_QSORT COMPAT_CFLAGS += -DINTERNAL_QSORT COMPAT_OBJS += compat/qsort.o @@ -1875,6 +1392,10 @@ ifdef HAVE_LIBCHARSET_H EXTLIBS += $(CHARSET_LIB) endif +ifdef HAVE_STRINGS_H + BASIC_CFLAGS += -DHAVE_STRINGS_H +endif + ifdef HAVE_DEV_TTY BASIC_CFLAGS += -DHAVE_DEV_TTY endif @@ -1889,6 +1410,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 @@ -1908,15 +1432,15 @@ ifneq (,$(XDL_FAST_HASH)) endif ifeq ($(TCLTK_PATH),) -NO_TCLTK=NoThanks +NO_TCLTK = NoThanks endif ifeq ($(PERL_PATH),) -NO_PERL=NoThanks +NO_PERL = NoThanks endif ifeq ($(PYTHON_PATH),) -NO_PYTHON=NoThanks +NO_PYTHON = NoThanks endif QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir @@ -1963,13 +1487,13 @@ PROFILE_DIR := $(CURDIR) ifeq ("$(PROFILE)","GEN") CFLAGS += -fprofile-generate=$(PROFILE_DIR) -DNO_NORETURN=1 EXTLIBS += -lgcov - export CCACHE_DISABLE=t - V=1 + export CCACHE_DISABLE = t + V = 1 else ifneq ("$(PROFILE)","") CFLAGS += -fprofile-use=$(PROFILE_DIR) -fprofile-correction -DNO_NORETURN=1 - export CCACHE_DISABLE=t - V=1 + export CCACHE_DISABLE = t + V = 1 endif endif @@ -2171,7 +1695,7 @@ endef GIT-SCRIPT-DEFINES: FORCE @FLAGS='$(SCRIPT_DEFINES)'; \ if test x"$$FLAGS" != x"`cat $@ 2>/dev/null`" ; then \ - echo 1>&2 " * new script parameters"; \ + echo >&2 " * new script parameters"; \ echo "$$FLAGS" >$@; \ fi @@ -2233,7 +1757,7 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)) git-instaweb: % : unimplemented.sh endif # NO_PERL ifndef NO_PYTHON -$(patsubst %.py,%,$(SCRIPT_PYTHON)): GIT-CFLAGS GIT-PREFIX +$(patsubst %.py,%,$(SCRIPT_PYTHON)): GIT-CFLAGS GIT-PREFIX GIT-PYTHON-VARS $(patsubst %.py,%,$(SCRIPT_PYTHON)): % : %.py $(QUIET_GEN)$(RM) $@ $@+ && \ INSTLIBDIR=`MAKEFLAGS= $(MAKE) -C git_remote_helpers -s \ @@ -2263,8 +1787,14 @@ configure: configure.ac GIT-VERSION-FILE $(RM) $<+ ifdef AUTOCONFIGURED -config.status: configure - $(QUIET_GEN)if test -f config.status; then \ +# We avoid depending on 'configure' here, because it gets rebuilt +# every time GIT-VERSION-FILE is modified, only to update the embedded +# version number string, which config.status does not care about. We +# do want to recheck when the platform/environment detection logic +# changes, hence this depends on configure.ac. +config.status: configure.ac + $(QUIET_GEN)$(MAKE) configure && \ + if test -f config.status; then \ ./config.status --recheck; \ else \ ./configure; \ @@ -2449,6 +1979,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 || \ @@ -2548,7 +2082,7 @@ TRACK_PREFIX = $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ):\ GIT-PREFIX: FORCE @FLAGS='$(TRACK_PREFIX)'; \ if test x"$$FLAGS" != x"`cat GIT-PREFIX 2>/dev/null`" ; then \ - echo 1>&2 " * new prefix flags"; \ + echo >&2 " * new prefix flags"; \ echo "$$FLAGS" >GIT-PREFIX; \ fi @@ -2557,7 +2091,7 @@ TRACK_CFLAGS = $(CC):$(subst ','\'',$(ALL_CFLAGS)):$(USE_GETTEXT_SCHEME) GIT-CFLAGS: FORCE @FLAGS='$(TRACK_CFLAGS)'; \ if test x"$$FLAGS" != x"`cat GIT-CFLAGS 2>/dev/null`" ; then \ - echo 1>&2 " * new build flags"; \ + echo >&2 " * new build flags"; \ echo "$$FLAGS" >GIT-CFLAGS; \ fi @@ -2566,7 +2100,7 @@ TRACK_LDFLAGS = $(subst ','\'',$(ALL_LDFLAGS)) GIT-LDFLAGS: FORCE @FLAGS='$(TRACK_LDFLAGS)'; \ if test x"$$FLAGS" != x"`cat GIT-LDFLAGS 2>/dev/null`" ; then \ - echo 1>&2 " * new link flags"; \ + echo >&2 " * new link flags"; \ echo "$$FLAGS" >GIT-LDFLAGS; \ fi @@ -2608,14 +2142,14 @@ ifdef GIT_PERF_MAKE_OPTS @echo GIT_PERF_MAKE_OPTS=\''$(subst ','\'',$(subst ','\'',$(GIT_PERF_MAKE_OPTS)))'\' >>$@ endif -### Detect Tck/Tk interpreter path changes -ifndef NO_TCLTK -TRACK_VARS = $(subst ','\'',-DTCLTK_PATH='$(TCLTK_PATH_SQ)') +### Detect Python interpreter path changes +ifndef NO_PYTHON +TRACK_PYTHON = $(subst ','\'',-DPYTHON_PATH='$(PYTHON_PATH_SQ)') -GIT-GUI-VARS: FORCE - @VARS='$(TRACK_VARS)'; \ +GIT-PYTHON-VARS: FORCE + @VARS='$(TRACK_PYTHON)'; \ if test x"$$VARS" != x"`cat $@ 2>/dev/null`" ; then \ - echo 1>&2 " * new Tcl/Tk interpreter location"; \ + echo >&2 " * new Python interpreter location"; \ echo "$$VARS" >$@; \ fi endif @@ -2814,7 +2348,7 @@ git.spec: git.spec.in GIT-VERSION-FILE sed -e 's/@@VERSION@@/$(GIT_VERSION)/g' < $< > $@+ mv $@+ $@ -GIT_TARNAME=git-$(GIT_VERSION) +GIT_TARNAME = git-$(GIT_VERSION) dist: git.spec git-archive$(X) configure ./git-archive --format=tar \ --prefix=$(GIT_TARNAME)/ HEAD^{tree} > $(GIT_TARNAME).tar @@ -2894,8 +2428,8 @@ ifndef NO_TCLTK $(MAKE) -C gitk-git clean $(MAKE) -C git-gui clean endif - $(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-LDFLAGS GIT-GUI-VARS GIT-BUILD-OPTIONS - $(RM) GIT-USER-AGENT GIT-PREFIX GIT-SCRIPT-DEFINES + $(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-LDFLAGS GIT-BUILD-OPTIONS + $(RM) GIT-USER-AGENT GIT-PREFIX GIT-SCRIPT-DEFINES GIT-PYTHON-VARS .PHONY: all install profile-clean clean strip .PHONY: shell_compatibility_test please_set_SHELL_PATH_to_a_more_modern_shell diff --git a/README b/README index d2690ec8dc..49713ea08e 100644 --- a/README +++ b/README @@ -19,9 +19,10 @@ Git is a fast, scalable, distributed revision control system with an unusually rich command set that provides both high-level operations and full access to internals. -Git is an Open Source project covered by the GNU General Public License. -It was originally written by Linus Torvalds with help of a group of -hackers around the net. It is currently maintained by Junio C Hamano. +Git is an Open Source project covered by the GNU General Public +License version 2 (some parts of it are under different licenses, +compatible with the GPLv2). It was originally written by Linus +Torvalds with help of a group of hackers around the net. Please read the file INSTALL for installation instructions. diff --git a/RelNotes b/RelNotes index ca734fb888..bdce3136ea 120000 --- a/RelNotes +++ b/RelNotes @@ -1 +1 @@ -Documentation/RelNotes/1.8.0.1.txt \ No newline at end of file +Documentation/RelNotes/1.8.2.txt \ No newline at end of file diff --git a/abspath.c b/abspath.c index 05f2d79348..40cdc46219 100644 --- a/abspath.c +++ b/abspath.c @@ -15,16 +15,34 @@ int is_directory(const char *path) #define MAXDEPTH 5 /* - * Use this to get the real path, i.e. resolve links. If you want an - * absolute path but don't mind links, use absolute_path. + * Return the real path (i.e., absolute path, with symlinks resolved + * and extra slashes removed) equivalent to the specified path. (If + * you want an absolute path but don't mind links, use + * absolute_path().) The return value is a pointer to a static + * buffer. + * + * The input and all intermediate paths must be shorter than MAX_PATH. + * The directory part of path (i.e., everything up to the last + * dir_sep) must denote a valid, existing directory, but the last + * component need not exist. If die_on_error is set, then die with an + * informative error message if there is a problem. Otherwise, return + * NULL on errors (without generating any output). * * If path is our buffer, then return path, as it's already what the * user wants. */ -const char *real_path(const char *path) +static const char *real_path_internal(const char *path, int die_on_error) { static char bufs[2][PATH_MAX + 1], *buf = bufs[0], *next_buf = bufs[1]; + char *retval = NULL; + + /* + * If we have to temporarily chdir(), store the original CWD + * here so that we can chdir() back to it at the end of the + * function: + */ char cwd[1024] = ""; + int buf_index = 1; int depth = MAXDEPTH; @@ -35,11 +53,19 @@ const char *real_path(const char *path) if (path == buf || path == next_buf) return path; - if (!*path) - die("The empty string is not a valid path"); + if (!*path) { + if (die_on_error) + die("The empty string is not a valid path"); + else + goto error_out; + } - if (strlcpy(buf, path, PATH_MAX) >= PATH_MAX) - die ("Too long path: %.*s", 60, path); + if (strlcpy(buf, path, PATH_MAX) >= PATH_MAX) { + if (die_on_error) + die("Too long path: %.*s", 60, path); + else + goto error_out; + } while (depth--) { if (!is_directory(buf)) { @@ -54,20 +80,36 @@ const char *real_path(const char *path) } if (*buf) { - if (!*cwd && !getcwd(cwd, sizeof(cwd))) - die_errno ("Could not get current working directory"); + if (!*cwd && !getcwd(cwd, sizeof(cwd))) { + if (die_on_error) + die_errno("Could not get current working directory"); + else + goto error_out; + } - if (chdir(buf)) - die_errno ("Could not switch to '%s'", buf); + if (chdir(buf)) { + if (die_on_error) + die_errno("Could not switch to '%s'", buf); + else + goto error_out; + } + } + if (!getcwd(buf, PATH_MAX)) { + if (die_on_error) + die_errno("Could not get current working directory"); + else + goto error_out; } - if (!getcwd(buf, PATH_MAX)) - die_errno ("Could not get current working directory"); if (last_elem) { size_t len = strlen(buf); - if (len + strlen(last_elem) + 2 > PATH_MAX) - die ("Too long path name: '%s/%s'", - buf, last_elem); + if (len + strlen(last_elem) + 2 > PATH_MAX) { + if (die_on_error) + die("Too long path name: '%s/%s'", + buf, last_elem); + else + goto error_out; + } if (len && !is_dir_sep(buf[len-1])) buf[len++] = '/'; strcpy(buf + len, last_elem); @@ -77,10 +119,18 @@ const char *real_path(const char *path) if (!lstat(buf, &st) && S_ISLNK(st.st_mode)) { ssize_t len = readlink(buf, next_buf, PATH_MAX); - if (len < 0) - die_errno ("Invalid symlink '%s'", buf); - if (PATH_MAX <= len) - die("symbolic link too long: %s", buf); + if (len < 0) { + if (die_on_error) + die_errno("Invalid symlink '%s'", buf); + else + goto error_out; + } + if (PATH_MAX <= len) { + if (die_on_error) + die("symbolic link too long: %s", buf); + else + goto error_out; + } next_buf[len] = '\0'; buf = next_buf; buf_index = 1 - buf_index; @@ -89,10 +139,23 @@ const char *real_path(const char *path) break; } + retval = buf; +error_out: + free(last_elem); if (*cwd && chdir(cwd)) die_errno ("Could not change back to '%s'", cwd); - return buf; + return retval; +} + +const char *real_path(const char *path) +{ + return real_path_internal(path, 1); +} + +const char *real_path_if_valid(const char *path) +{ + return real_path_internal(path, 0); } static const char *get_pwd_cwd(void) diff --git a/advice.c b/advice.c index edfbd4a6fb..d287927280 100644 --- a/advice.c +++ b/advice.c @@ -1,9 +1,10 @@ #include "cache.h" -int advice_push_nonfastforward = 1; +int advice_push_update_rejected = 1; int advice_push_non_ff_current = 1; int advice_push_non_ff_default = 1; int advice_push_non_ff_matching = 1; +int advice_push_already_exists = 1; int advice_status_hints = 1; int advice_commit_before_merge = 1; int advice_resolve_conflict = 1; @@ -14,15 +15,19 @@ static struct { const char *name; int *preference; } advice_config[] = { - { "pushnonfastforward", &advice_push_nonfastforward }, + { "pushupdaterejected", &advice_push_update_rejected }, { "pushnonffcurrent", &advice_push_non_ff_current }, { "pushnonffdefault", &advice_push_non_ff_default }, { "pushnonffmatching", &advice_push_non_ff_matching }, + { "pushalreadyexists", &advice_push_already_exists }, { "statushints", &advice_status_hints }, { "commitbeforemerge", &advice_commit_before_merge }, { "resolveconflict", &advice_resolve_conflict }, { "implicitidentity", &advice_implicit_identity }, { "detachedhead", &advice_detached_head }, + + /* make this an alias for backward compatibility */ + { "pushnonfastforward", &advice_push_update_rejected } }; void advise(const char *advice, ...) diff --git a/advice.h b/advice.h index f3cdbbf29e..8bf63563a5 100644 --- a/advice.h +++ b/advice.h @@ -3,10 +3,11 @@ #include "git-compat-util.h" -extern int advice_push_nonfastforward; +extern int advice_push_update_rejected; extern int advice_push_non_ff_current; extern int advice_push_non_ff_default; extern int advice_push_non_ff_matching; +extern int advice_push_already_exists; extern int advice_status_hints; extern int advice_commit_before_merge; extern int advice_resolve_conflict; diff --git a/archive-tar.c b/archive-tar.c index 0ba3f25cf5..d1cce46e33 100644 --- a/archive-tar.c +++ b/archive-tar.c @@ -153,6 +153,8 @@ static unsigned int ustar_header_chksum(const struct ustar_header *header) static size_t get_path_prefix(const char *path, size_t pathlen, size_t maxlen) { size_t i = pathlen; + if (i > 1 && path[i - 1] == '/') + i--; if (i > maxlen) i = maxlen; do { diff --git a/archive-zip.c b/archive-zip.c index 55f66b4060..d3aef532b7 100644 --- a/archive-zip.c +++ b/archive-zip.c @@ -240,7 +240,7 @@ static int write_zip_entry(struct archiver_args *args, (mode & 0111) ? ((mode) << 16) : 0; if (S_ISREG(mode) && args->compression_level != 0 && size > 0) method = 8; - compressed_size = size; + compressed_size = (method == 0) ? size : 0; if (S_ISREG(mode) && type == OBJ_BLOB && !args->convert && size > big_file_threshold) { @@ -313,10 +313,7 @@ static int write_zip_entry(struct archiver_args *args, copy_le16(header.compression_method, method); copy_le16(header.mtime, zip_time); copy_le16(header.mdate, zip_date); - if (flags & ZIP_STREAM) - set_zip_header_data_desc(&header, 0, 0, 0); - else - set_zip_header_data_desc(&header, size, compressed_size, crc); + set_zip_header_data_desc(&header, size, compressed_size, crc); copy_le16(header.filename_length, pathlen); copy_le16(header.extra_length, ZIP_EXTRA_MTIME_SIZE); write_or_die(1, &header, ZIP_LOCAL_HEADER_SIZE); diff --git a/archive.c b/archive.c index 466640479e..93e00bb4ae 100644 --- a/archive.c +++ b/archive.c @@ -120,6 +120,8 @@ static int write_archive_entry(const unsigned char *sha1, const char *base, strbuf_add(&path, args->base, args->baselen); strbuf_add(&path, base, baselen); strbuf_addstr(&path, filename); + if (S_ISDIR(mode) || S_ISGITLINK(mode)) + strbuf_addch(&path, '/'); path_without_prefix = path.buf + args->baselen; setup_archive_check(check); @@ -130,7 +132,6 @@ static int write_archive_entry(const unsigned char *sha1, const char *base, } if (S_ISDIR(mode) || S_ISGITLINK(mode)) { - strbuf_addch(&path, '/'); if (args->verbose) fprintf(stderr, "%.*s\n", (int)path.len, path.buf); err = write_entry(args, sha1, path.buf, path.len, mode); 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..4657cc233c 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; @@ -270,13 +284,14 @@ static struct match_attr *parse_attr_line(const char *line, const char *src, * (reading the file from top to bottom), .gitattribute of the root * directory (again, reading the file from top to bottom) down to the * current directory, and then scan the list backwards to find the first match. - * This is exactly the same as what excluded() does in dir.c to deal with + * This is exactly the same as what is_excluded() does in dir.c to deal with * .gitignore */ 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); @@ -548,18 +564,12 @@ static void bootstrap_attr_stack(void) attr_stack = elem; } -static void prepare_attr_stack(const char *path) +static void prepare_attr_stack(const char *path, int dirlen) { struct attr_stack *elem, *info; - int dirlen, len; + int len; const char *cp; - cp = strrchr(path, '/'); - if (!cp) - dirlen = 0; - else - dirlen = cp - path; - /* * At the bottom of the attribute stack is the built-in * set of attribute definitions, followed by the contents @@ -628,7 +638,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 +655,26 @@ 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); - } - /* - * 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)) + const char *pattern = pat->pattern; + int prefix = pat->nowildcardlen; + + if ((pat->flags & EXC_FLAG_MUSTBEDIR) && + ((!pathlen) || (pathname[pathlen-1] != '/'))) return 0; - if (baselen != 0) - baselen++; - return fnmatch_icase(pattern, pathname + baselen, FNM_PATHNAME) == 0; + + if (pat->flags & EXC_FLAG_NODIR) { + return match_basename(basename, + pathlen - (basename - pathname), + pattern, prefix, + pat->patternlen, pat->flags); + } + return match_pathname(pathname, pathlen, + base, baselen, + pattern, prefix, pat->patternlen, pat->flags); } static int macroexpand_one(int attr_nr, int rem); @@ -683,7 +691,7 @@ static int fill_one(const char *what, struct match_attr *a, int rem) if (*n == ATTR__UNKNOWN) { debug_set(what, - a->is_macro ? a->u.attr->name : a->u.pattern, + a->is_macro ? a->u.attr->name : a->u.pat.pattern, attr, v); *n = v; rem--; @@ -693,7 +701,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 +711,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; @@ -740,16 +749,29 @@ static int macroexpand_one(int attr_nr, int rem) static void collect_all_attrs(const char *path) { struct attr_stack *stk; - int i, pathlen, rem; + int i, pathlen, rem, dirlen; + const char *basename, *cp, *last_slash = NULL; + + for (cp = path; *cp; cp++) { + if (*cp == '/' && cp[1]) + last_slash = cp; + } + pathlen = cp - path; + if (last_slash) { + basename = last_slash + 1; + dirlen = last_slash - path; + } else { + basename = path; + dirlen = 0; + } - prepare_attr_stack(path); + prepare_attr_stack(path, dirlen); for (i = 0; i < attr_nr; i++) check_all_attr[i].value = ATTR__UNKNOWN; - 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..faef559136 100644 --- a/builtin.h +++ b/builtin.h @@ -15,7 +15,8 @@ extern const char git_more_info_string[]; extern void prune_packed_objects(int); struct fmt_merge_msg_opts { - unsigned add_title:1; + unsigned add_title:1, + credit_people:1; int shortlog_len; }; @@ -37,10 +38,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); @@ -55,6 +52,7 @@ extern int cmd_cat_file(int argc, const char **argv, const char *prefix); extern int cmd_checkout(int argc, const char **argv, const char *prefix); extern int cmd_checkout_index(int argc, const char **argv, const char *prefix); extern int cmd_check_attr(int argc, const char **argv, const char *prefix); +extern int cmd_check_ignore(int argc, const char **argv, const char *prefix); extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix); extern int cmd_cherry(int argc, const char **argv, const char *prefix); extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix); diff --git a/builtin/add.c b/builtin/add.c index e664100c71..7cb6cca56d 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -6,6 +6,7 @@ #include "cache.h" #include "builtin.h" #include "dir.h" +#include "pathspec.h" #include "exec_cmd.h" #include "cache-tree.h" #include "run-command.h" @@ -97,39 +98,6 @@ int add_files_to_cache(const char *prefix, const char **pathspec, int flags) return !!data.add_errors; } -static void fill_pathspec_matches(const char **pathspec, char *seen, int specs) -{ - int num_unmatched = 0, i; - - /* - * Since we are walking the index as if we were walking the directory, - * we have to mark the matched pathspec as seen; otherwise we will - * mistakenly think that the user gave a pathspec that did not match - * anything. - */ - for (i = 0; i < specs; i++) - if (!seen[i]) - num_unmatched++; - if (!num_unmatched) - return; - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen); - } -} - -static char *find_used_pathspec(const char **pathspec) -{ - char *seen; - int i; - - for (i = 0; pathspec[i]; i++) - ; /* just counting */ - seen = xcalloc(i, 1); - fill_pathspec_matches(pathspec, seen, i); - return seen; -} - static char *prune_directory(struct dir_struct *dir, const char **pathspec, int prefix) { char *seen; @@ -149,10 +117,14 @@ static char *prune_directory(struct dir_struct *dir, const char **pathspec, int *dst++ = entry; } dir->nr = dst - dir->entries; - fill_pathspec_matches(pathspec, seen, specs); + add_pathspec_matches_against_index(pathspec, seen, specs); return seen; } +/* + * Checks the index to see whether any path in pathspec refers to + * something inside a submodule. If so, dies with an error message. + */ static void treat_gitlinks(const char **pathspec) { int i; @@ -160,24 +132,8 @@ static void treat_gitlinks(const char **pathspec) if (!pathspec || !*pathspec) return; - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - if (S_ISGITLINK(ce->ce_mode)) { - int len = ce_namelen(ce), j; - for (j = 0; pathspec[j]; j++) { - int len2 = strlen(pathspec[j]); - if (len2 <= len || pathspec[j][len] != '/' || - memcmp(ce->name, pathspec[j], len)) - continue; - if (len2 == len + 1) - /* strip trailing slash */ - pathspec[j] = xstrndup(ce->name, len); - else - die (_("Path '%s' is in submodule '%.*s'"), - pathspec[j], len, ce->name); - } - } - } + for (i = 0; pathspec[i]; i++) + pathspec[i] = check_path_for_gitlink(pathspec[i]); } static void refresh(int verbose, const char **pathspec) @@ -197,17 +153,19 @@ static void refresh(int verbose, const char **pathspec) free(seen); } -static const char **validate_pathspec(int argc, const char **argv, const char *prefix) +/* + * Normalizes argv relative to prefix, via get_pathspec(), and then + * runs die_if_path_beyond_symlink() on each path in the normalized + * list. + */ +static const char **validate_pathspec(const char **argv, const char *prefix) { const char **pathspec = get_pathspec(prefix, argv); if (pathspec) { const char **p; for (p = pathspec; *p; p++) { - if (has_symlink_leading_path(*p, strlen(*p))) { - int len = prefix ? strlen(prefix) : 0; - die(_("'%s' is beyond a symbolic link"), *p + len); - } + die_if_path_beyond_symlink(*p, prefix); } } @@ -248,7 +206,7 @@ int interactive_add(int argc, const char **argv, const char *prefix, int patch) const char **pathspec = NULL; if (argc) { - pathspec = validate_pathspec(argc, argv, prefix); + pathspec = validate_pathspec(argv, prefix); if (!pathspec) return -1; } @@ -415,7 +373,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n")); return 0; } - pathspec = validate_pathspec(argc, argv, prefix); + pathspec = validate_pathspec(argv, prefix); if (read_cache() < 0) die(_("index file corrupt")); @@ -448,13 +406,13 @@ int cmd_add(int argc, const char **argv, const char *prefix) path_exclude_check_init(&check, &dir); if (!seen) - seen = find_used_pathspec(pathspec); + seen = find_pathspecs_matching_against_index(pathspec); for (i = 0; pathspec[i]; i++) { if (!seen[i] && pathspec[i][0] && !file_exists(pathspec[i])) { if (ignore_missing) { int dtype = DT_UNKNOWN; - if (path_excluded(&check, pathspec[i], -1, &dtype)) + if (is_path_excluded(&check, pathspec[i], -1, &dtype)) dir_add_ignored(&dir, pathspec[i], strlen(pathspec[i])); } else die(_("pathspec '%s' did not match any files"), diff --git a/builtin/apply.c b/builtin/apply.c index 156b3ce3b7..6c11e8bc73 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -2095,7 +2095,7 @@ static void update_pre_post_images(struct image *preimage, char *buf, size_t len, size_t postlen) { - int i, ctx; + int i, ctx, reduced; char *new, *old, *fixed; struct image fixed_preimage; @@ -2105,8 +2105,10 @@ static void update_pre_post_images(struct image *preimage, * free "oldlines". */ prepare_image(&fixed_preimage, buf, len, 1); - assert(fixed_preimage.nr == preimage->nr); - for (i = 0; i < preimage->nr; i++) + assert(postlen + ? fixed_preimage.nr == preimage->nr + : fixed_preimage.nr <= preimage->nr); + for (i = 0; i < fixed_preimage.nr; i++) fixed_preimage.line[i].flag = preimage->line[i].flag; free(preimage->line_allocated); *preimage = fixed_preimage; @@ -2126,7 +2128,8 @@ static void update_pre_post_images(struct image *preimage, else new = old; fixed = preimage->buf; - for (i = ctx = 0; i < postimage->nr; i++) { + + for (i = reduced = ctx = 0; i < postimage->nr; i++) { size_t len = postimage->line[i].len; if (!(postimage->line[i].flag & LINE_COMMON)) { /* an added line -- no counterparts in preimage */ @@ -2145,8 +2148,15 @@ static void update_pre_post_images(struct image *preimage, fixed += preimage->line[ctx].len; ctx++; } - if (preimage->nr <= ctx) - die(_("oops")); + + /* + * preimage is expected to run out, if the caller + * fixed addition of trailing blank lines. + */ + if (preimage->nr <= ctx) { + reduced++; + continue; + } /* and copy it in, while fixing the line length */ len = preimage->line[ctx].len; @@ -2159,6 +2169,7 @@ static void update_pre_post_images(struct image *preimage, /* Fix the length of the whole thing */ postimage->len = new - postimage->buf; + postimage->nr -= reduced; } static int match_fragment(struct image *img, diff --git a/builtin/blame.c b/builtin/blame.c index c27ef21c23..b431ba3209 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -42,6 +42,7 @@ static int blank_boundary; static int incremental; static int xdl_opts; static int abbrev = -1; +static int no_whole_file_rename; static enum date_mode blame_date_mode = DATE_ISO8601; static size_t blame_date_width; @@ -1226,7 +1227,7 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) * The first pass looks for unrenamed path to optimize for * common cases, then we look for renames in the second pass. */ - for (pass = 0; pass < 2; pass++) { + for (pass = 0; pass < 2 - no_whole_file_rename; pass++) { struct origin *(*find)(struct scoreboard *, struct commit *, struct origin *); find = pass ? find_rename : find_origin; @@ -1321,30 +1322,31 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) * Information on commits, used for output. */ struct commit_info { - const char *author; - const char *author_mail; + struct strbuf author; + struct strbuf author_mail; unsigned long author_time; - const char *author_tz; + struct strbuf author_tz; /* filled only when asked for details */ - const char *committer; - const char *committer_mail; + struct strbuf committer; + struct strbuf committer_mail; unsigned long committer_time; - const char *committer_tz; + struct strbuf committer_tz; - const char *summary; + struct strbuf summary; }; /* * Parse author/committer line in the commit object buffer */ static void get_ac_line(const char *inbuf, const char *what, - int person_len, char *person, - int mail_len, char *mail, - unsigned long *time, const char **tz) + struct strbuf *name, struct strbuf *mail, + unsigned long *time, struct strbuf *tz) { - int len, tzlen, maillen; - char *tmp, *endp, *timepos, *mailpos; + struct ident_split ident; + size_t len, maillen, namelen; + char *tmp, *endp; + const char *namebuf, *mailbuf; tmp = strstr(inbuf, what); if (!tmp) @@ -1355,69 +1357,61 @@ static void get_ac_line(const char *inbuf, const char *what, len = strlen(tmp); else len = endp - tmp; - if (person_len <= len) { + + if (split_ident_line(&ident, tmp, len)) { error_out: /* Ugh */ - *tz = "(unknown)"; - strcpy(person, *tz); - strcpy(mail, *tz); + tmp = "(unknown)"; + strbuf_addstr(name, tmp); + strbuf_addstr(mail, tmp); + strbuf_addstr(tz, tmp); *time = 0; return; } - memcpy(person, tmp, len); - tmp = person; - tmp += len; - *tmp = 0; - while (person < tmp && *tmp != ' ') - tmp--; - if (tmp <= person) - goto error_out; - *tz = tmp+1; - tzlen = (person+len)-(tmp+1); + namelen = ident.name_end - ident.name_begin; + namebuf = ident.name_begin; - *tmp = 0; - while (person < tmp && *tmp != ' ') - tmp--; - if (tmp <= person) - goto error_out; - *time = strtoul(tmp, NULL, 10); - timepos = tmp; + maillen = ident.mail_end - ident.mail_begin; + mailbuf = ident.mail_begin; - *tmp = 0; - while (person < tmp && !(*tmp == ' ' && tmp[1] == '<')) - tmp--; - if (tmp <= person) - return; - mailpos = tmp + 1; - *tmp = 0; - maillen = timepos - tmp; - memcpy(mail, mailpos, maillen); + *time = strtoul(ident.date_begin, NULL, 10); - if (!mailmap.nr) - return; - - /* - * mailmap expansion may make the name longer. - * make room by pushing stuff down. - */ - tmp = person + person_len - (tzlen + 1); - memmove(tmp, *tz, tzlen); - tmp[tzlen] = 0; - *tz = tmp; + len = ident.tz_end - ident.tz_begin; + strbuf_add(tz, ident.tz_begin, len); /* * Now, convert both name and e-mail using mailmap */ - if (map_user(&mailmap, mail+1, mail_len-1, person, tmp-person-1)) { - /* Add a trailing '>' to email, since map_user returns plain emails - Note: It already has '<', since we replace from mail+1 */ - mailpos = memchr(mail, '\0', mail_len); - if (mailpos && mailpos-mail < mail_len - 1) { - *mailpos = '>'; - *(mailpos+1) = '\0'; - } - } + map_user(&mailmap, &mailbuf, &maillen, + &namebuf, &namelen); + + strbuf_addf(mail, "<%.*s>", (int)maillen, mailbuf); + strbuf_add(name, namebuf, namelen); +} + +static void commit_info_init(struct commit_info *ci) +{ + + strbuf_init(&ci->author, 0); + strbuf_init(&ci->author_mail, 0); + strbuf_init(&ci->author_tz, 0); + strbuf_init(&ci->committer, 0); + strbuf_init(&ci->committer_mail, 0); + strbuf_init(&ci->committer_tz, 0); + strbuf_init(&ci->summary, 0); +} + +static void commit_info_destroy(struct commit_info *ci) +{ + + strbuf_release(&ci->author); + strbuf_release(&ci->author_mail); + strbuf_release(&ci->author_tz); + strbuf_release(&ci->committer); + strbuf_release(&ci->committer_mail); + strbuf_release(&ci->committer_tz); + strbuf_release(&ci->summary); } static void get_commit_info(struct commit *commit, @@ -1425,13 +1419,10 @@ 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]; - static char committer_name[1024]; - static char committer_mail[1024]; - static char summary_buf[1024]; + + commit_info_init(ret); /* * We've operated without save_commit_buffer, so @@ -1446,13 +1437,11 @@ 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; get_ac_line(message, "\nauthor ", - sizeof(author_name), author_name, - sizeof(author_mail), author_mail, + &ret->author, &ret->author_mail, &ret->author_time, &ret->author_tz); if (!detailed) { @@ -1460,21 +1449,16 @@ static void get_commit_info(struct commit *commit, return; } - ret->committer = committer_name; - ret->committer_mail = committer_mail; get_ac_line(message, "\ncommitter ", - sizeof(committer_name), committer_name, - sizeof(committer_mail), committer_mail, + &ret->committer, &ret->committer_mail, &ret->committer_time, &ret->committer_tz); - ret->summary = summary_buf; len = find_commit_subject(message, &subject); - if (len && len < sizeof(summary_buf)) { - memcpy(summary_buf, subject, len); - summary_buf[len] = 0; - } else { - sprintf(summary_buf, "(%s)", sha1_to_hex(commit->object.sha1)); - } + if (len) + strbuf_add(&ret->summary, subject, len); + else + strbuf_addf(&ret->summary, "(%s)", sha1_to_hex(commit->object.sha1)); + free(reencoded); } @@ -1503,15 +1487,15 @@ static int emit_one_suspect_detail(struct origin *suspect, int repeat) suspect->commit->object.flags |= METAINFO_SHOWN; get_commit_info(suspect->commit, &ci, 1); - printf("author %s\n", ci.author); - printf("author-mail %s\n", ci.author_mail); + printf("author %s\n", ci.author.buf); + printf("author-mail %s\n", ci.author_mail.buf); printf("author-time %lu\n", ci.author_time); - printf("author-tz %s\n", ci.author_tz); - printf("committer %s\n", ci.committer); - printf("committer-mail %s\n", ci.committer_mail); + printf("author-tz %s\n", ci.author_tz.buf); + printf("committer %s\n", ci.committer.buf); + printf("committer-mail %s\n", ci.committer_mail.buf); printf("committer-time %lu\n", ci.committer_time); - printf("committer-tz %s\n", ci.committer_tz); - printf("summary %s\n", ci.summary); + printf("committer-tz %s\n", ci.committer_tz.buf); + printf("summary %s\n", ci.summary.buf); if (suspect->commit->object.flags & UNINTERESTING) printf("boundary\n"); if (suspect->previous) { @@ -1519,6 +1503,9 @@ static int emit_one_suspect_detail(struct origin *suspect, int repeat) printf("previous %s ", sha1_to_hex(prev->commit->object.sha1)); write_name_quoted(prev->path, stdout, '\n'); } + + commit_info_destroy(&ci); + return 1; } @@ -1705,11 +1692,11 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) if (opt & OUTPUT_ANNOTATE_COMPAT) { const char *name; if (opt & OUTPUT_SHOW_EMAIL) - name = ci.author_mail; + name = ci.author_mail.buf; else - name = ci.author; + name = ci.author.buf; printf("\t(%10s\t%10s\t%d)", name, - format_time(ci.author_time, ci.author_tz, + format_time(ci.author_time, ci.author_tz.buf, show_raw_time), ent->lno + 1 + cnt); } else { @@ -1728,14 +1715,14 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) const char *name; int pad; if (opt & OUTPUT_SHOW_EMAIL) - name = ci.author_mail; + name = ci.author_mail.buf; else - name = ci.author; + name = ci.author.buf; pad = longest_author - utf8_strwidth(name); printf(" (%s%*s %10s", name, pad, "", format_time(ci.author_time, - ci.author_tz, + ci.author_tz.buf, show_raw_time)); } printf(" %*d) ", @@ -1750,6 +1737,8 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt) if (sb->final_buf_size && cp[-1] != '\n') putchar('\n'); + + commit_info_destroy(&ci); } static void output(struct scoreboard *sb, int option) @@ -1874,9 +1863,9 @@ static void find_alignment(struct scoreboard *sb, int *option) suspect->commit->object.flags |= METAINFO_SHOWN; get_commit_info(suspect->commit, &ci, 1); if (*option & OUTPUT_SHOW_EMAIL) - num = utf8_strwidth(ci.author_mail); + num = utf8_strwidth(ci.author_mail.buf); else - num = utf8_strwidth(ci.author); + num = utf8_strwidth(ci.author.buf); if (longest_author < num) longest_author = num; } @@ -1888,6 +1877,8 @@ static void find_alignment(struct scoreboard *sb, int *option) longest_dst_lines = num; if (largest_score < ent_score(sb, e)) largest_score = ent_score(sb, e); + + commit_info_destroy(&ci); } max_orig_digits = decimal_width(longest_src_lines); max_digits = decimal_width(longest_dst_lines); @@ -2402,6 +2393,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) init_revisions(&revs, NULL); revs.date_mode = blame_date_mode; DIFF_OPT_SET(&revs.diffopt, ALLOW_TEXTCONV); + DIFF_OPT_SET(&revs.diffopt, FOLLOW_RENAMES); save_commit_buffer = 0; dashdash_pos = 0; @@ -2425,6 +2417,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix) parse_revision_opt(&revs, &ctx, options, blame_opt_usage); } parse_done: + no_whole_file_rename = !DIFF_OPT_TST(&revs.diffopt, FOLLOW_RENAMES); + DIFF_OPT_CLR(&revs.diffopt, FOLLOW_RENAMES); argc = parse_options_end(&ctx); if (0 < abbrev) diff --git a/builtin/branch.c b/builtin/branch.c index 1ec9c02612..873f624d1c 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -725,7 +725,7 @@ static int edit_branch_description(const char *branch_name) stripspace(&buf, 1); strbuf_addf(&name, "branch.%s.description", branch_name); - status = git_config_set(name.buf, buf.buf); + status = git_config_set(name.buf, buf.len ? buf.buf : NULL); strbuf_release(&name); strbuf_release(&buf); diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c new file mode 100644 index 0000000000..709535ce09 --- /dev/null +++ b/builtin/check-ignore.c @@ -0,0 +1,173 @@ +#include "builtin.h" +#include "cache.h" +#include "dir.h" +#include "quote.h" +#include "pathspec.h" +#include "parse-options.h" + +static int quiet, verbose, stdin_paths; +static const char * const check_ignore_usage[] = { +"git check-ignore [options] pathname...", +"git check-ignore [options] --stdin < ", +NULL +}; + +static int null_term_line; + +static const struct option check_ignore_options[] = { + OPT__QUIET(&quiet, N_("suppress progress reporting")), + OPT__VERBOSE(&verbose, N_("be verbose")), + OPT_GROUP(""), + OPT_BOOLEAN(0, "stdin", &stdin_paths, + N_("read file names from stdin")), + OPT_BOOLEAN('z', NULL, &null_term_line, + N_("input paths are terminated by a null character")), + OPT_END() +}; + +static void output_exclude(const char *path, struct exclude *exclude) +{ + char *bang = exclude->flags & EXC_FLAG_NEGATIVE ? "!" : ""; + char *slash = exclude->flags & EXC_FLAG_MUSTBEDIR ? "/" : ""; + if (!null_term_line) { + if (!verbose) { + write_name_quoted(path, stdout, '\n'); + } else { + quote_c_style(exclude->el->src, NULL, stdout, 0); + printf(":%d:%s%s%s\t", + exclude->srcpos, + bang, exclude->pattern, slash); + quote_c_style(path, NULL, stdout, 0); + fputc('\n', stdout); + } + } else { + if (!verbose) { + printf("%s%c", path, '\0'); + } else { + printf("%s%c%d%c%s%s%s%c%s%c", + exclude->el->src, '\0', + exclude->srcpos, '\0', + bang, exclude->pattern, slash, '\0', + path, '\0'); + } + } +} + +static int check_ignore(const char *prefix, const char **pathspec) +{ + struct dir_struct dir; + const char *path, *full_path; + char *seen; + int num_ignored = 0, dtype = DT_UNKNOWN, i; + struct path_exclude_check check; + struct exclude *exclude; + + /* read_cache() is only necessary so we can watch out for submodules. */ + if (read_cache() < 0) + die(_("index file corrupt")); + + memset(&dir, 0, sizeof(dir)); + dir.flags |= DIR_COLLECT_IGNORED; + setup_standard_excludes(&dir); + + if (!pathspec || !*pathspec) { + if (!quiet) + fprintf(stderr, "no pathspec given.\n"); + return 0; + } + + path_exclude_check_init(&check, &dir); + /* + * look for pathspecs matching entries in the index, since these + * should not be ignored, in order to be consistent with + * 'git status', 'git add' etc. + */ + seen = find_pathspecs_matching_against_index(pathspec); + for (i = 0; pathspec[i]; i++) { + path = pathspec[i]; + full_path = prefix_path(prefix, prefix + ? strlen(prefix) : 0, path); + full_path = check_path_for_gitlink(full_path); + die_if_path_beyond_symlink(full_path, prefix); + if (!seen[i] && path[0]) { + exclude = last_exclude_matching_path(&check, full_path, + -1, &dtype); + if (exclude) { + if (!quiet) + output_exclude(path, exclude); + num_ignored++; + } + } + } + free(seen); + clear_directory(&dir); + path_exclude_check_clear(&check); + + return num_ignored; +} + +static int check_ignore_stdin_paths(const char *prefix) +{ + struct strbuf buf, nbuf; + char **pathspec = NULL; + size_t nr = 0, alloc = 0; + int line_termination = null_term_line ? 0 : '\n'; + int num_ignored; + + strbuf_init(&buf, 0); + strbuf_init(&nbuf, 0); + while (strbuf_getline(&buf, stdin, line_termination) != EOF) { + if (line_termination && buf.buf[0] == '"') { + strbuf_reset(&nbuf); + if (unquote_c_style(&nbuf, buf.buf, NULL)) + die("line is badly quoted"); + strbuf_swap(&buf, &nbuf); + } + ALLOC_GROW(pathspec, nr + 1, alloc); + pathspec[nr] = xcalloc(strlen(buf.buf) + 1, sizeof(*buf.buf)); + strcpy(pathspec[nr++], buf.buf); + } + ALLOC_GROW(pathspec, nr + 1, alloc); + pathspec[nr] = NULL; + num_ignored = check_ignore(prefix, (const char **)pathspec); + maybe_flush_or_die(stdout, "attribute to stdout"); + strbuf_release(&buf); + strbuf_release(&nbuf); + free(pathspec); + return num_ignored; +} + +int cmd_check_ignore(int argc, const char **argv, const char *prefix) +{ + int num_ignored; + + git_config(git_default_config, NULL); + + argc = parse_options(argc, argv, prefix, check_ignore_options, + check_ignore_usage, 0); + + if (stdin_paths) { + if (argc > 0) + die(_("cannot specify pathnames with --stdin")); + } else { + if (null_term_line) + die(_("-z only makes sense with --stdin")); + if (argc == 0) + die(_("no path specified")); + } + if (quiet) { + if (argc > 1) + die(_("--quiet is only valid with a single pathname")); + if (verbose) + die(_("cannot have both --quiet and --verbose")); + } + + if (stdin_paths) { + num_ignored = check_ignore_stdin_paths(prefix); + } else { + num_ignored = check_ignore(prefix, argv); + maybe_flush_or_die(stdout, "ignore to stdout"); + } + + return !num_ignored; +} diff --git a/builtin/clean.c b/builtin/clean.c index 69c1cda906..04e396b17a 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -10,6 +10,7 @@ #include "cache.h" #include "dir.h" #include "parse-options.h" +#include "refs.h" #include "string-list.h" #include "quote.h" @@ -20,6 +21,12 @@ static const char *const builtin_clean_usage[] = { NULL }; +static const char *msg_remove = N_("Removing %s\n"); +static const char *msg_would_remove = N_("Would remove %s\n"); +static const char *msg_skip_git_dir = N_("Skipping repository %s\n"); +static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n"); +static const char *msg_warn_remove_failed = N_("failed to remove %s"); + static int git_clean_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "clean.requireforce")) @@ -34,22 +41,124 @@ static int exclude_cb(const struct option *opt, const char *arg, int unset) return 0; } +static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, + int dry_run, int quiet, int *dir_gone) +{ + DIR *dir; + struct strbuf quoted = STRBUF_INIT; + struct dirent *e; + int res = 0, ret = 0, gone = 1, original_len = path->len, len, i; + unsigned char submodule_head[20]; + struct string_list dels = STRING_LIST_INIT_DUP; + + *dir_gone = 1; + + if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) && + !resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) { + if (!quiet) { + quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + printf(dry_run ? _(msg_would_skip_git_dir) : _(msg_skip_git_dir), + quoted.buf); + } + + *dir_gone = 0; + return 0; + } + + dir = opendir(path->buf); + if (!dir) { + /* an empty dir could be removed even if it is unreadble */ + res = dry_run ? 0 : rmdir(path->buf); + if (res) { + quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + warning(_(msg_warn_remove_failed), quoted.buf); + *dir_gone = 0; + } + return res; + } + + if (path->buf[original_len - 1] != '/') + strbuf_addch(path, '/'); + + len = path->len; + while ((e = readdir(dir)) != NULL) { + struct stat st; + if (is_dot_or_dotdot(e->d_name)) + continue; + + strbuf_setlen(path, len); + strbuf_addstr(path, e->d_name); + if (lstat(path->buf, &st)) + ; /* fall thru */ + else if (S_ISDIR(st.st_mode)) { + if (remove_dirs(path, prefix, force_flag, dry_run, quiet, &gone)) + ret = 1; + if (gone) { + quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + string_list_append(&dels, quoted.buf); + } else + *dir_gone = 0; + continue; + } else { + res = dry_run ? 0 : unlink(path->buf); + if (!res) { + quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + string_list_append(&dels, quoted.buf); + } else { + quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + warning(_(msg_warn_remove_failed), quoted.buf); + *dir_gone = 0; + ret = 1; + } + continue; + } + + /* path too long, stat fails, or non-directory still exists */ + *dir_gone = 0; + ret = 1; + break; + } + closedir(dir); + + strbuf_setlen(path, original_len); + + if (*dir_gone) { + res = dry_run ? 0 : rmdir(path->buf); + if (!res) + *dir_gone = 1; + else { + quote_path_relative(path->buf, strlen(path->buf), "ed, prefix); + warning(_(msg_warn_remove_failed), quoted.buf); + *dir_gone = 0; + ret = 1; + } + } + + if (!*dir_gone && !quiet) { + for (i = 0; i < dels.nr; i++) + printf(dry_run ? _(msg_would_remove) : _(msg_remove), dels.items[i].string); + } + string_list_clear(&dels, 0); + return ret; +} + int cmd_clean(int argc, const char **argv, const char *prefix) { - int i; - int show_only = 0, remove_directories = 0, quiet = 0, ignored = 0; - int ignored_only = 0, config_set = 0, errors = 0; + int i, res; + int dry_run = 0, remove_directories = 0, quiet = 0, ignored = 0; + int ignored_only = 0, config_set = 0, errors = 0, gone = 1; int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT; struct strbuf directory = STRBUF_INIT; struct dir_struct dir; static const char **pathspec; struct strbuf buf = STRBUF_INIT; struct string_list exclude_list = STRING_LIST_INIT_NODUP; + struct exclude_list *el; const char *qname; char *seen = NULL; struct option options[] = { OPT__QUIET(&quiet, N_("do not print names of files removed")), - OPT__DRY_RUN(&show_only, N_("dry run")), + OPT__DRY_RUN(&dry_run, N_("dry run")), OPT__FORCE(&force, N_("force")), OPT_BOOLEAN('d', NULL, &remove_directories, N_("remove whole directories")), @@ -77,7 +186,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) if (ignored && ignored_only) die(_("-x and -X cannot be used together")); - if (!show_only && !force) { + if (!dry_run && !force) { if (config_set) die(_("clean.requireForce set to true and neither -n nor -f given; " "refusing to clean")); @@ -97,9 +206,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix) if (!ignored) setup_standard_excludes(&dir); + el = add_exclude_list(&dir, EXC_CMDL, "--exclude option"); for (i = 0; i < exclude_list.nr; i++) - add_exclude(exclude_list.items[i].string, "", 0, - &dir.exclude_list[EXC_CMDL]); + add_exclude(exclude_list.items[i].string, "", 0, el, -(i+1)); pathspec = get_pathspec(prefix, argv); @@ -149,38 +258,26 @@ int cmd_clean(int argc, const char **argv, const char *prefix) if (S_ISDIR(st.st_mode)) { strbuf_addstr(&directory, ent->name); - qname = quote_path_relative(directory.buf, directory.len, &buf, prefix); - if (show_only && (remove_directories || - (matches == MATCHED_EXACTLY))) { - printf(_("Would remove %s\n"), qname); - } else if (remove_directories || - (matches == MATCHED_EXACTLY)) { - if (!quiet) - printf(_("Removing %s\n"), qname); - if (remove_dir_recursively(&directory, - rm_flags) != 0) { - warning(_("failed to remove %s"), qname); + if (remove_directories || (matches == MATCHED_EXACTLY)) { + if (remove_dirs(&directory, prefix, rm_flags, dry_run, quiet, &gone)) errors++; + if (gone && !quiet) { + qname = quote_path_relative(directory.buf, directory.len, &buf, prefix); + printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname); } - } else if (show_only) { - printf(_("Would not remove %s\n"), qname); - } else { - printf(_("Not removing %s\n"), qname); } strbuf_reset(&directory); } else { if (pathspec && !matches) continue; - qname = quote_path_relative(ent->name, -1, &buf, prefix); - if (show_only) { - printf(_("Would remove %s\n"), qname); - continue; - } else if (!quiet) { - printf(_("Removing %s\n"), qname); - } - if (unlink(ent->name) != 0) { - warning(_("failed to remove %s"), qname); + res = dry_run ? 0 : unlink(ent->name); + if (res) { + qname = quote_path_relative(ent->name, -1, &buf, prefix); + warning(_(msg_warn_remove_failed), qname); errors++; + } else if (!quiet) { + qname = quote_path_relative(ent->name, -1, &buf, prefix); + printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname); } } } diff --git a/builtin/clone.c b/builtin/clone.c index ec2f75b4f3..36ec99db3f 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -704,6 +704,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (option_origin) die(_("--bare and --origin %s options are incompatible."), option_origin); + if (real_git_dir) + die(_("--bare and --separate-git-dir are incompatible.")); option_no_checkout = 1; } @@ -771,8 +773,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix) die(_("could not create leading directories of '%s'"), git_dir); set_git_dir_init(git_dir, real_git_dir, 0); - if (real_git_dir) + if (real_git_dir) { git_dir = real_git_dir; + junk_git_dir = real_git_dir; + } if (0 <= option_verbosity) { if (option_bare) diff --git a/builtin/commit.c b/builtin/commit.c index a17a5df449..38b9a9cc0d 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -103,7 +103,7 @@ static enum { CLEANUP_NONE, CLEANUP_ALL } cleanup_mode; -static char *cleanup_arg; +static const char *cleanup_arg; static enum commit_whence whence; static int use_editor = 1, include_status = 1; @@ -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) { @@ -1306,6 +1320,8 @@ static int git_commit_config(const char *k, const char *v, void *cb) include_status = git_config_bool(k, v); return 0; } + if (!strcmp(k, "commit.cleanup")) + return git_config_string(&cleanup_arg, k, v); status = git_gpg_config(k, v, NULL); if (status) @@ -1313,8 +1329,6 @@ static int git_commit_config(const char *k, const char *v, void *cb) return git_status_config(k, v, s); } -static const char post_rewrite_hook[] = "hooks/post-rewrite"; - static int run_rewrite_hook(const unsigned char *oldsha1, const unsigned char *newsha1) { @@ -1325,10 +1339,10 @@ static int run_rewrite_hook(const unsigned char *oldsha1, int code; size_t n; - if (access(git_path(post_rewrite_hook), X_OK) < 0) + argv[0] = find_hook("post-rewrite"); + if (!argv[0]) return 0; - argv[0] = git_path(post_rewrite_hook); argv[1] = "amend"; argv[2] = NULL; @@ -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 505bbc7ddd..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) @@ -139,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 @@ -162,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; @@ -196,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; } @@ -216,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/fast-export.c b/builtin/fast-export.c index 12220ad8da..77dffd1ce3 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -474,18 +474,21 @@ static void handle_tag(const char *name, struct tag *tag) (int)message_size, (int)message_size, message ? message : ""); } -static void get_tags_and_duplicates(struct object_array *pending, +static void get_tags_and_duplicates(struct rev_cmdline_info *info, struct string_list *extra_refs) { struct tag *tag; int i; - for (i = 0; i < pending->nr; i++) { - struct object_array_entry *e = pending->objects + i; + for (i = 0; i < info->nr; i++) { + struct rev_cmdline_entry *e = info->rev + i; unsigned char sha1[20]; - struct commit *commit = commit; + struct commit *commit; char *full_name; + if (e->flags & UNINTERESTING) + continue; + if (dwim_ref(e->name, strlen(e->name), sha1, &full_name) != 1) continue; @@ -523,10 +526,14 @@ static void get_tags_and_duplicates(struct object_array *pending, typename(e->item->type)); continue; } - if (commit->util) - /* more than one name for the same object */ + + /* + * This ref will not be updated through a commit, lets make + * sure it gets properly updated eventually. + */ + if (commit->util || commit->object.flags & SHOWN) string_list_append(extra_refs, full_name)->util = commit; - else + if (!commit->util) commit->util = full_name; } } @@ -614,6 +621,10 @@ static void import_marks(char *input_file) if (object->flags & SHOWN) error("Object %s already has a mark", sha1_to_hex(sha1)); + if (object->type != OBJ_COMMIT) + /* only commits */ + continue; + mark_object(object, mark); if (last_idnum < mark) last_idnum = mark; @@ -677,7 +688,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) if (import_filename && revs.prune_data.nr) full_tree = 1; - get_tags_and_duplicates(&revs.pending, &extra_refs); + get_tags_and_duplicates(&revs.cmdline, &extra_refs); if (prepare_revision_walk(&revs)) die("revision walk setup failed"); 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/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index e2e27b2c40..d9af43c257 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -232,8 +232,9 @@ static void record_person(int which, struct string_list *people, { char *name_buf, *name, *name_end; struct string_list_item *elem; - const char *field = (which == 'a') ? "\nauthor " : "\ncommitter "; + const char *field; + field = (which == 'a') ? "\nauthor " : "\ncommitter "; name = strstr(commit->buffer, field); if (!name) return; @@ -323,7 +324,8 @@ static void add_people_info(struct strbuf *out, static void shortlog(const char *name, struct origin_data *origin_data, struct commit *head, - struct rev_info *rev, int limit, + struct rev_info *rev, + struct fmt_merge_msg_opts *opts, struct strbuf *out) { int i, count = 0; @@ -335,6 +337,7 @@ static void shortlog(const char *name, int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED; struct strbuf sb = STRBUF_INIT; const unsigned char *sha1 = origin_data->sha1; + int limit = opts->shortlog_len; branch = deref_tag(parse_object(sha1), sha1_to_hex(sha1), 40); if (!branch || branch->type != OBJ_COMMIT) @@ -351,13 +354,15 @@ static void shortlog(const char *name, if (commit->parents && commit->parents->next) { /* do not list a merge but count committer */ - record_person('c', &committers, commit); + if (opts->credit_people) + record_person('c', &committers, commit); continue; } - if (!count) + if (!count && opts->credit_people) /* the 'tip' committer */ record_person('c', &committers, commit); - record_person('a', &authors, commit); + if (opts->credit_people) + record_person('a', &authors, commit); count++; if (subjects.nr > limit) continue; @@ -372,7 +377,8 @@ static void shortlog(const char *name, string_list_append(&subjects, strbuf_detach(&sb, NULL)); } - add_people_info(out, &authors, &committers); + if (opts->credit_people) + add_people_info(out, &authors, &committers); if (count > limit) strbuf_addf(out, "\n* %s: (%d commits)\n", name, count); else @@ -635,7 +641,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out, for (i = 0; i < origins.nr; i++) shortlog(origins.items[i].string, origins.items[i].util, - head, &rev, opts->shortlog_len, out); + head, &rev, opts, out); } strbuf_complete_line(out); @@ -690,6 +696,7 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) memset(&opts, 0, sizeof(opts)); opts.add_title = !message; + opts.credit_people = 1; opts.shortlog_len = shortlog_len; ret = fmt_merge_msg(&input, &output, &opts); diff --git a/builtin/log.c b/builtin/log.c index 09cf43e6d4..8f0b2e84fe 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -22,6 +22,7 @@ #include "branch.h" #include "streaming.h" #include "version.h" +#include "mailmap.h" /* Set a default date-time format for git log ("log.date" config variable) */ static const char *default_date_mode = NULL; @@ -30,6 +31,7 @@ static int default_abbrev_commit; static int default_show_root = 1; static int decoration_style; static int decoration_given; +static int use_mailmap_config; static const char *fmt_patch_subject_prefix = "PATCH"; static const char *fmt_pretty; @@ -94,16 +96,18 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, struct rev_info *rev, struct setup_revision_opt *opt) { struct userformat_want w; - int quiet = 0, source = 0; + int quiet = 0, source = 0, mailmap = 0; const struct option builtin_log_options[] = { OPT_BOOLEAN(0, "quiet", &quiet, N_("suppress diff output")), OPT_BOOLEAN(0, "source", &source, N_("show source")), + OPT_BOOLEAN(0, "use-mailmap", &mailmap, N_("Use mail map file")), { OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"), PARSE_OPT_OPTARG, decorate_callback}, OPT_END() }; + mailmap = use_mailmap_config; argc = parse_options(argc, argv, prefix, builtin_log_options, builtin_log_usage, PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | @@ -136,6 +140,11 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, if (source) rev->show_source = 1; + if (mailmap) { + rev->mailmap = xcalloc(1, sizeof(struct string_list)); + read_mailmap(rev->mailmap, NULL); + } + if (rev->pretty_given && rev->commit_format == CMIT_FMT_RAW) { /* * "log --pretty=raw" is special; ignore UI oriented @@ -351,7 +360,13 @@ 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 (!strcmp(var, "log.mailmap")) { + use_mailmap_config = git_config_bool(var, value); + return 0; + } + if (grep_config(var, value, cb) < 0) + return -1; return git_diff_ui_config(var, value, cb); } @@ -360,6 +375,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 +466,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 +547,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 +570,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); @@ -673,7 +692,7 @@ static int reopen_stdout(struct commit *commit, const char *subject, struct rev_info *rev, int quiet) { struct strbuf filename = STRBUF_INIT; - int suffix_len = strlen(fmt_patch_suffix) + 1; + int suffix_len = strlen(rev->patch_suffix) + 1; if (output_directory) { strbuf_addstr(&filename, output_directory); @@ -684,7 +703,12 @@ static int reopen_stdout(struct commit *commit, const char *subject, strbuf_addch(&filename, '/'); } - get_patch_filename(commit, subject, rev->nr, fmt_patch_suffix, &filename); + if (rev->numbered_files) + strbuf_addf(&filename, "%d", rev->nr); + else if (commit) + fmt_output_commit(&filename, commit, rev); + else + fmt_output_subject(&filename, subject, rev); if (!quiet) fprintf(realstdout, "%s\n", filename.buf + outdir_offset); @@ -768,7 +792,6 @@ static void add_branch_description(struct strbuf *buf, const char *branch_name) } static void make_cover_letter(struct rev_info *rev, int use_stdout, - int numbered, int numbered_files, struct commit *origin, int nr, struct commit **list, struct commit *head, const char *branch_name, @@ -791,7 +814,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, committer = git_committer_info(0); if (!use_stdout && - reopen_stdout(NULL, numbered_files ? NULL : "cover-letter", rev, quiet)) + reopen_stdout(NULL, rev->numbered_files ? NULL : "cover-letter", rev, quiet)) return; log_write_email_headers(rev, head, &pp.subject, &pp.after_subject, @@ -1011,8 +1034,9 @@ static char *find_branch_name(struct rev_info *rev) { int i, positive = -1; unsigned char branch_sha1[20]; - struct strbuf buf = STRBUF_INIT; - const char *branch; + const unsigned char *tip_sha1; + const char *ref; + char *full_ref, *branch = NULL; for (i = 0; i < rev->cmdline.nr; i++) { if (rev->cmdline.rev[i].flags & UNINTERESTING) @@ -1022,18 +1046,27 @@ static char *find_branch_name(struct rev_info *rev) else return NULL; } - if (positive < 0) + if (0 <= positive) { + ref = rev->cmdline.rev[positive].name; + tip_sha1 = rev->cmdline.rev[positive].item->sha1; + } else if (!rev->cmdline.nr && rev->pending.nr == 1 && + !strcmp(rev->pending.objects[0].name, "HEAD")) { + /* + * No actual ref from command line, but "HEAD" from + * rev->def was added in setup_revisions() + * e.g. format-patch --cover-letter -12 + */ + ref = "HEAD"; + tip_sha1 = rev->pending.objects[0].item->sha1; + } else { return NULL; - strbuf_addf(&buf, "refs/heads/%s", rev->cmdline.rev[positive].name); - branch = resolve_ref_unsafe(buf.buf, branch_sha1, 1, NULL); - if (!branch || - prefixcmp(branch, "refs/heads/") || - hashcmp(rev->cmdline.rev[positive].item->sha1, branch_sha1)) - branch = NULL; - strbuf_release(&buf); - if (branch) - return xstrdup(rev->cmdline.rev[positive].name); - return NULL; + } + if (dwim_ref(ref, strlen(ref), branch_sha1, &full_ref) && + !prefixcmp(full_ref, "refs/heads/") && + !hashcmp(tip_sha1, branch_sha1)) + branch = xstrdup(full_ref + strlen("refs/heads/")); + free(full_ref); + return branch; } int cmd_format_patch(int argc, const char **argv, const char *prefix) @@ -1045,7 +1078,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) int nr = 0, total, i; int use_stdout = 0; int start_number = -1; - int numbered_files = 0; /* _just_ numbers */ + int just_numbers = 0; int ignore_if_in_upstream = 0; int cover_letter = 0; int boundary_count = 0; @@ -1057,6 +1090,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) struct strbuf buf = STRBUF_INIT; int use_patch_format = 0; int quiet = 0; + int reroll_count = -1; char *branch_name = NULL; const struct option builtin_format_patch_options[] = { { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL, @@ -1070,12 +1104,14 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("print patches to standard out")), OPT_BOOLEAN(0, "cover-letter", &cover_letter, N_("generate a cover letter")), - OPT_BOOLEAN(0, "numbered-files", &numbered_files, + OPT_BOOLEAN(0, "numbered-files", &just_numbers, N_("use simple number sequence for output file names")), OPT_STRING(0, "suffix", &fmt_patch_suffix, N_("sfx"), N_("use instead of '.patch'")), OPT_INTEGER(0, "start-number", &start_number, N_("start numbering patches at instead of 1")), + OPT_INTEGER('v', "reroll-count", &reroll_count, + N_("mark the series as Nth re-roll")), { OPTION_CALLBACK, 0, "subject-prefix", &rev, N_("prefix"), N_("Use [] instead of [PATCH]"), PARSE_OPT_NONEG, subject_prefix_callback }, @@ -1121,6 +1157,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; @@ -1148,6 +1185,14 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH); + if (0 < reroll_count) { + struct strbuf sprefix = STRBUF_INIT; + strbuf_addf(&sprefix, "%s v%d", + rev.subject_prefix, reroll_count); + rev.reroll_count = reroll_count; + rev.subject_prefix = strbuf_detach(&sprefix, NULL); + } + if (do_signoff) { const char *committer; const char *endpos; @@ -1338,12 +1383,12 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) const char *msgid = clean_message_id(in_reply_to); string_list_append(rev.ref_message_ids, msgid); } - rev.numbered_files = numbered_files; + rev.numbered_files = just_numbers; rev.patch_suffix = fmt_patch_suffix; if (cover_letter) { if (thread) gen_message_id(&rev, "cover"); - make_cover_letter(&rev, use_stdout, numbered, numbered_files, + make_cover_letter(&rev, use_stdout, origin, nr, list, head, branch_name, quiet); total++; start_number--; @@ -1390,7 +1435,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) } if (!use_stdout && - reopen_stdout(numbered_files ? NULL : commit, NULL, &rev, quiet)) + reopen_stdout(rev.numbered_files ? NULL : commit, NULL, &rev, quiet)) die(_("Failed to create output files")); shown = log_tree_commit(&rev, commit); free(commit->buffer); diff --git a/builtin/ls-files.c b/builtin/ls-files.c index b5434af0c8..175e6e3e72 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -35,6 +35,7 @@ static int error_unmatch; static char *ps_matched; static const char *with_tree; static int exc_given; +static int exclude_args; static const char *tag_cached = ""; static const char *tag_unmerged = ""; @@ -203,7 +204,7 @@ static void show_ru_info(void) static int ce_excluded(struct path_exclude_check *check, struct cache_entry *ce) { int dtype = ce_to_dtype(ce); - return path_excluded(check, ce->name, ce_namelen(ce), &dtype); + return is_path_excluded(check, ce->name, ce_namelen(ce), &dtype); } static void show_files(struct dir_struct *dir) @@ -337,7 +338,7 @@ void overlay_tree_on_cache(const char *tree_name, const char *prefix) matchbuf[0] = prefix; matchbuf[1] = NULL; init_pathspec(&pathspec, matchbuf); - pathspec.items[0].use_wildcard = 0; + pathspec.items[0].nowildcard_len = pathspec.items[0].len; } else init_pathspec(&pathspec, NULL); if (read_tree(tree, 1, &pathspec)) @@ -420,10 +421,10 @@ static int option_parse_z(const struct option *opt, static int option_parse_exclude(const struct option *opt, const char *arg, int unset) { - struct exclude_list *list = opt->value; + struct string_list *exclude_list = opt->value; exc_given = 1; - add_exclude(arg, "", 0, list); + string_list_append(exclude_list, arg); return 0; } @@ -452,9 +453,11 @@ static int option_parse_exclude_standard(const struct option *opt, int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) { - int require_work_tree = 0, show_tag = 0; + int require_work_tree = 0, show_tag = 0, i; const char *max_prefix; struct dir_struct dir; + struct exclude_list *el; + struct string_list exclude_list = STRING_LIST_INIT_NODUP; struct option builtin_ls_files_options[] = { { OPTION_CALLBACK, 'z', NULL, NULL, NULL, N_("paths are separated with NUL character"), @@ -488,7 +491,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) N_("show unmerged files in the output")), OPT_BOOLEAN(0, "resolve-undo", &show_resolve_undo, N_("show resolve-undo information")), - { OPTION_CALLBACK, 'x', "exclude", &dir.exclude_list[EXC_CMDL], N_("pattern"), + { OPTION_CALLBACK, 'x', "exclude", &exclude_list, N_("pattern"), N_("skip files matching pattern"), 0, option_parse_exclude }, { OPTION_CALLBACK, 'X', "exclude-from", &dir, N_("file"), @@ -525,6 +528,10 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) argc = parse_options(argc, argv, prefix, builtin_ls_files_options, ls_files_usage, 0); + el = add_exclude_list(&dir, EXC_CMDL, "--exclude option"); + for (i = 0; i < exclude_list.nr; i++) { + add_exclude(exclude_list.items[i].string, "", 0, el, --exclude_args); + } if (show_tag || show_valid_bit) { tag_cached = "H "; tag_unmerged = "M "; diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 235c17cc01..fb76e38d84 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -168,7 +168,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) init_pathspec(&pathspec, get_pathspec(prefix, argv + 1)); for (i = 0; i < pathspec.nr; i++) - pathspec.items[i].use_wildcard = 0; + pathspec.items[i].nowildcard_len = pathspec.items[i].len; pathspec.has_wildcard = 0; tree = parse_tree_indirect(sha1); if (!tree) 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-index.c b/builtin/merge-index.c index 2338832587..be5e514324 100644 --- a/builtin/merge-index.c +++ b/builtin/merge-index.c @@ -42,7 +42,7 @@ static int merge_entry(int pos, const char *path) return found; } -static void merge_file(const char *path) +static void merge_one_path(const char *path) { int pos = cache_name_pos(path, strlen(path)); @@ -102,7 +102,7 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix) } die("git merge-index: unknown option %s", arg); } - merge_file(arg); + merge_one_path(arg); } if (err && !quiet) die("merge program failed"); diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index 897a563bc6..e0d0b7d28b 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -3,17 +3,15 @@ #include "xdiff-interface.h" #include "blob.h" #include "exec_cmd.h" -#include "merge-file.h" +#include "merge-blobs.h" static const char merge_tree_usage[] = "git merge-tree "; -static int resolve_directories = 1; struct merge_list { struct merge_list *next; struct merge_list *link; /* other stages for this object */ - unsigned int stage : 2, - flags : 30; + unsigned int stage : 2; unsigned int mode; const char *path; struct blob *blob; @@ -27,7 +25,7 @@ static void add_merge_entry(struct merge_list *entry) merge_result_end = &entry->next; } -static void merge_trees(struct tree_desc t[3], const char *base); +static void merge_trees_recursive(struct tree_desc t[3], const char *base, int df_conflict); static const char *explanation(struct merge_list *entry) { @@ -76,7 +74,7 @@ static void *result(struct merge_list *entry, unsigned long *size) their = NULL; if (entry) their = entry->blob; - return merge_file(path, base, our, their, size); + return merge_blobs(path, base, our, their, size); } static void *origin(struct merge_list *entry, unsigned long *size) @@ -174,17 +172,17 @@ static char *traverse_path(const struct traverse_info *info, const struct name_e return make_traverse_path(path, info, n); } -static void resolve(const struct traverse_info *info, struct name_entry *branch1, struct name_entry *result) +static void resolve(const struct traverse_info *info, struct name_entry *ours, struct name_entry *result) { struct merge_list *orig, *final; const char *path; - /* If it's already branch1, don't bother showing it */ - if (!branch1) + /* If it's already ours, don't bother showing it */ + if (!ours) return; path = traverse_path(info, result); - orig = create_entry(2, branch1->mode, branch1->sha1, path); + orig = create_entry(2, ours->mode, ours->sha1, path); final = create_entry(0, result->mode, result->sha1, path); final->link = orig; @@ -192,34 +190,35 @@ static void resolve(const struct traverse_info *info, struct name_entry *branch1 add_merge_entry(final); } -static int unresolved_directory(const struct traverse_info *info, struct name_entry n[3]) +static void unresolved_directory(const struct traverse_info *info, struct name_entry n[3], + int df_conflict) { char *newbase; struct name_entry *p; struct tree_desc t[3]; void *buf0, *buf1, *buf2; - if (!resolve_directories) - return 0; - p = n; - if (!p->mode) { - p++; - if (!p->mode) - p++; + for (p = n; p < n + 3; p++) { + if (p->mode && S_ISDIR(p->mode)) + break; } - if (!S_ISDIR(p->mode)) - return 0; + if (n + 3 <= p) + return; /* there is no tree here */ + newbase = traverse_path(info, p); - buf0 = fill_tree_descriptor(t+0, n[0].sha1); - buf1 = fill_tree_descriptor(t+1, n[1].sha1); - buf2 = fill_tree_descriptor(t+2, n[2].sha1); - merge_trees(t, newbase); + +#define ENTRY_SHA1(e) (((e)->mode && S_ISDIR((e)->mode)) ? (e)->sha1 : NULL) + buf0 = fill_tree_descriptor(t+0, ENTRY_SHA1(n + 0)); + buf1 = fill_tree_descriptor(t+1, ENTRY_SHA1(n + 1)); + buf2 = fill_tree_descriptor(t+2, ENTRY_SHA1(n + 2)); +#undef ENTRY_SHA1 + + merge_trees_recursive(t, newbase, df_conflict); free(buf0); free(buf1); free(buf2); free(newbase); - return 1; } @@ -242,18 +241,26 @@ static struct merge_list *link_entry(unsigned stage, const struct traverse_info static void unresolved(const struct traverse_info *info, struct name_entry n[3]) { struct merge_list *entry = NULL; + int i; + unsigned dirmask = 0, mask = 0; + + for (i = 0; i < 3; i++) { + mask |= (1 << 1); + if (n[i].mode && S_ISDIR(n[i].mode)) + dirmask |= (1 << i); + } - if (unresolved_directory(info, n)) + unresolved_directory(info, n, dirmask && (dirmask != mask)); + + if (dirmask == mask) return; - /* - * Do them in reverse order so that the resulting link - * list has the stages in order - link_entry adds new - * links at the front. - */ - entry = link_entry(3, info, n + 2, entry); - entry = link_entry(2, info, n + 1, entry); - entry = link_entry(1, info, n + 0, entry); + if (n[2].mode && !S_ISDIR(n[2].mode)) + entry = link_entry(3, info, n + 2, entry); + if (n[1].mode && !S_ISDIR(n[1].mode)) + entry = link_entry(2, info, n + 1, entry); + if (n[0].mode && !S_ISDIR(n[0].mode)) + entry = link_entry(1, info, n + 0, entry); add_merge_entry(entry); } @@ -292,20 +299,29 @@ static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, s /* Same in both? */ if (same_entry(entry+1, entry+2)) { if (entry[0].sha1) { + /* Modified identically */ resolve(info, NULL, entry+1); return mask; } + /* "Both added the same" is left unresolved */ } if (same_entry(entry+0, entry+1)) { if (entry[2].sha1 && !S_ISDIR(entry[2].mode)) { + /* We did not touch, they modified -- take theirs */ resolve(info, entry+1, entry+2); return mask; } + /* + * If we did not touch a directory but they made it + * into a file, we fall through and unresolved() + * recurses down. Likewise for the opposite case. + */ } if (same_entry(entry+0, entry+2)) { if (entry[1].sha1 && !S_ISDIR(entry[1].mode)) { + /* We modified, they did not touch -- take ours */ resolve(info, NULL, entry+1); return mask; } @@ -315,15 +331,21 @@ static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, s return mask; } -static void merge_trees(struct tree_desc t[3], const char *base) +static void merge_trees_recursive(struct tree_desc t[3], const char *base, int df_conflict) { struct traverse_info info; setup_traverse_info(&info, base); + info.data = &df_conflict; info.fn = threeway_callback; traverse_trees(3, t, &info); } +static void merge_trees(struct tree_desc t[3], const char *base) +{ + merge_trees_recursive(t, base, 0); +} + static void *get_tree_descriptor(struct tree_desc *desc, const char *rev) { unsigned char sha1[20]; diff --git a/builtin/merge.c b/builtin/merge.c index 0ec8f0d449..9307e9c726 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) { @@ -903,8 +800,9 @@ static void prepare_to_commit(struct commit_list *remoteheads) if (0 < option_edit) strbuf_add_lines(&msg, "# ", comment, strlen(comment)); write_merge_msg(&msg); - run_hook(get_index_file(), "prepare-commit-msg", - git_path("MERGE_MSG"), "merge", NULL, NULL); + if (run_hook(get_index_file(), "prepare-commit-msg", + git_path("MERGE_MSG"), "merge", NULL, NULL)) + abort_commit(remoteheads, NULL); if (0 < option_edit) { if (launch_editor(git_path("MERGE_MSG"), NULL, NULL)) abort_commit(remoteheads, NULL); @@ -1324,6 +1222,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) memset(&opts, 0, sizeof(opts)); opts.add_title = !have_message; opts.shortlog_len = shortlog_len; + opts.credit_people = (0 < option_edit); fmt_merge_msg(&merge_names, &merge_msg, &opts); if (merge_msg.len) @@ -1424,7 +1323,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/pack-redundant.c b/builtin/pack-redundant.c index f5c6afc5dd..649c3aaa93 100644 --- a/builtin/pack-redundant.c +++ b/builtin/pack-redundant.c @@ -301,14 +301,14 @@ static void pll_free(struct pll *l) */ static struct pll * get_permutations(struct pack_list *list, int n) { - struct pll *subset, *ret = NULL, *new_pll = NULL, *pll; + struct pll *subset, *ret = NULL, *new_pll = NULL; if (list == NULL || pack_list_size(list) < n || n == 0) return NULL; if (n == 1) { while (list) { - new_pll = xmalloc(sizeof(pll)); + new_pll = xmalloc(sizeof(*new_pll)); new_pll->pl = NULL; pack_list_insert(&new_pll->pl, list); new_pll->next = ret; @@ -321,7 +321,7 @@ static struct pll * get_permutations(struct pack_list *list, int n) while (list->next) { subset = get_permutations(list->next, n - 1); while (subset) { - new_pll = xmalloc(sizeof(pll)); + new_pll = xmalloc(sizeof(*new_pll)); new_pll->pl = subset->pl; pack_list_insert(&new_pll->pl, list); new_pll->next = ret; diff --git a/builtin/push.c b/builtin/push.c index db9ba30b08..b158028be8 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -220,31 +220,42 @@ static const char message_advice_checkout_pull_push[] = "(e.g. 'git pull') before pushing again.\n" "See the 'Note about fast-forwards' in 'git push --help' for details."); +static const char message_advice_ref_already_exists[] = + N_("Updates were rejected because the destination reference already exists\n" + "in the remote."); + static void advise_pull_before_push(void) { - if (!advice_push_non_ff_current || !advice_push_nonfastforward) + if (!advice_push_non_ff_current || !advice_push_update_rejected) return; advise(_(message_advice_pull_before_push)); } static void advise_use_upstream(void) { - if (!advice_push_non_ff_default || !advice_push_nonfastforward) + if (!advice_push_non_ff_default || !advice_push_update_rejected) return; advise(_(message_advice_use_upstream)); } static void advise_checkout_pull_push(void) { - if (!advice_push_non_ff_matching || !advice_push_nonfastforward) + if (!advice_push_non_ff_matching || !advice_push_update_rejected) return; advise(_(message_advice_checkout_pull_push)); } +static void advise_ref_already_exists(void) +{ + if (!advice_push_already_exists || !advice_push_update_rejected) + return; + advise(_(message_advice_ref_already_exists)); +} + static int push_with_options(struct transport *transport, int flags) { int err; - int nonfastforward; + unsigned int reject_reasons; transport_set_verbosity(transport, verbosity, progress); @@ -257,7 +268,7 @@ static int push_with_options(struct transport *transport, int flags) if (verbosity > 0) fprintf(stderr, _("Pushing to %s\n"), transport->url); err = transport_push(transport, refspec_nr, refspec, flags, - &nonfastforward); + &reject_reasons); if (err != 0) error(_("failed to push some refs to '%s'"), transport->url); @@ -265,18 +276,15 @@ static int push_with_options(struct transport *transport, int flags) if (!err) return 0; - switch (nonfastforward) { - default: - break; - case NON_FF_HEAD: + if (reject_reasons & REJECT_NON_FF_HEAD) { advise_pull_before_push(); - break; - case NON_FF_OTHER: + } else if (reject_reasons & REJECT_NON_FF_OTHER) { if (default_matching_used) advise_use_upstream(); else advise_checkout_pull_push(); - break; + } else if (reject_reasons & REJECT_ALREADY_EXISTS) { + advise_ref_already_exists(); } return 1; @@ -399,6 +407,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "progress", &progress, N_("force progress reporting")), OPT_BIT(0, "prune", &flags, N_("prune locally removed refs"), TRANSPORT_PUSH_PRUNE), + OPT_BIT(0, "no-verify", &flags, N_("bypass pre-push hook"), TRANSPORT_PUSH_NO_HOOK), OPT_END() }; diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index ff781febca..e8878de45c 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -182,9 +182,6 @@ struct command { char ref_name[FLEX_ARRAY]; /* more */ }; -static const char pre_receive_hook[] = "hooks/pre-receive"; -static const char post_receive_hook[] = "hooks/post-receive"; - static void rp_error(const char *err, ...) __attribute__((format (printf, 1, 2))); static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2))); @@ -242,10 +239,10 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_sta const char *argv[2]; int code; - if (access(hook_name, X_OK) < 0) + argv[0] = find_hook(hook_name); + if (!argv[0]) return 0; - argv[0] = hook_name; argv[1] = NULL; memset(&proc, 0, sizeof(proc)); @@ -331,15 +328,14 @@ static int run_receive_hook(struct command *commands, const char *hook_name, static int run_update_hook(struct command *cmd) { - static const char update_hook[] = "hooks/update"; const char *argv[5]; struct child_process proc; int code; - if (access(update_hook, X_OK) < 0) + argv[0] = find_hook("update"); + if (!argv[0]) return 0; - argv[0] = update_hook; argv[1] = cmd->ref_name; argv[2] = sha1_to_hex(cmd->old_sha1); argv[3] = sha1_to_hex(cmd->new_sha1); @@ -532,24 +528,25 @@ static const char *update(struct command *cmd) } } -static char update_post_hook[] = "hooks/post-update"; - static void run_update_post_hook(struct command *commands) { struct command *cmd; int argc; const char **argv; struct child_process proc; + char *hook; + hook = find_hook("post-update"); for (argc = 0, cmd = commands; cmd; cmd = cmd->next) { if (cmd->error_string || cmd->did_not_exist) continue; argc++; } - if (!argc || access(update_post_hook, X_OK) < 0) + if (!argc || !hook) return; + argv = xmalloc(sizeof(*argv) * (2 + argc)); - argv[0] = update_post_hook; + argv[0] = hook; for (argc = 1, cmd = commands; cmd; cmd = cmd->next) { char *p; @@ -704,7 +701,7 @@ static void execute_commands(struct command *commands, const char *unpacker_erro 0, &cmd)) set_connectivity_errors(commands); - if (run_receive_hook(commands, pre_receive_hook, 0)) { + if (run_receive_hook(commands, "pre-receive", 0)) { for (cmd = commands; cmd; cmd = cmd->next) { if (!cmd->error_string) cmd->error_string = "pre-receive hook declined"; @@ -994,7 +991,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) unlink_or_warn(pack_lockfile); if (report_status) report(commands, unpack_status); - run_receive_hook(commands, post_receive_hook, 1); + run_receive_hook(commands, "post-receive", 1); run_update_post_hook(commands); if (auto_gc) { const char *argv_gc_auto[] = { 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/reset.c b/builtin/reset.c index 915cc9f86f..6032131a90 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -23,8 +23,8 @@ static const char * const git_reset_usage[] = { N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] []"), - N_("git reset [-q] [--] ..."), - N_("git reset --patch [] [--] [...]"), + N_("git reset [-q] [--] ..."), + N_("git reset --patch [] [--] [...]"), NULL }; @@ -38,14 +38,12 @@ static inline int is_merge(void) return !access(git_path("MERGE_HEAD"), F_OK); } -static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet) +static int reset_index(const unsigned char *sha1, int reset_type, int quiet) { int nr = 1; - int newfd; struct tree_desc desc[2]; struct tree *tree; struct unpack_trees_options opts; - struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); memset(&opts, 0, sizeof(opts)); opts.head_idx = 1; @@ -67,8 +65,6 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet opts.reset = 1; } - newfd = hold_locked_index(lock, 1); - read_cache_unmerged(); if (reset_type == KEEP) { @@ -91,10 +87,6 @@ static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet prime_cache_tree(&active_cache_tree, tree); } - if (write_cache(newfd, active_cache, active_nr) || - commit_locked_index(lock)) - return error(_("Could not write new index file.")); - return 0; } @@ -117,36 +109,10 @@ static void print_new_head_line(struct commit *commit) printf("\n"); } -static int update_index_refresh(int fd, struct lock_file *index_lock, int flags) -{ - int result; - - if (!index_lock) { - index_lock = xcalloc(1, sizeof(struct lock_file)); - fd = hold_locked_index(index_lock, 1); - } - - if (read_cache() < 0) - return error(_("Could not read index")); - - result = refresh_index(&the_index, (flags), NULL, NULL, - _("Unstaged changes after reset:")) ? 1 : 0; - if (write_cache(fd, active_cache, active_nr) || - commit_locked_index(index_lock)) - return error ("Could not refresh index"); - return result; -} - static void update_index_from_diff(struct diff_queue_struct *q, struct diff_options *opt, void *data) { int i; - int *discard_flag = data; - - /* do_diff_cache() mangled the index */ - discard_cache(); - *discard_flag = 1; - read_cache(); for (i = 0; i < q->nr; i++) { struct diff_filespec *one = q->queue[i]->one; @@ -164,32 +130,15 @@ static void update_index_from_diff(struct diff_queue_struct *q, } } -static int interactive_reset(const char *revision, const char **argv, - const char *prefix) +static int read_from_tree(const char **pathspec, unsigned char *tree_sha1) { - const char **pathspec = NULL; - - if (*argv) - pathspec = get_pathspec(prefix, argv); - - return run_add_interactive(revision, "--patch=reset", pathspec); -} - -static int read_from_tree(const char *prefix, const char **argv, - unsigned char *tree_sha1, int refresh_flags) -{ - struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); - int index_fd, index_was_discarded = 0; struct diff_options opt; memset(&opt, 0, sizeof(opt)); - diff_tree_setup_paths(get_pathspec(prefix, (const char **)argv), &opt); + diff_tree_setup_paths(pathspec, &opt); opt.output_format = DIFF_FORMAT_CALLBACK; opt.format_callback = update_index_from_diff; - opt.format_callback_data = &index_was_discarded; - index_fd = hold_locked_index(lock, 1); - index_was_discarded = 0; read_cache(); if (do_diff_cache(tree_sha1, &opt)) return 1; @@ -197,10 +146,7 @@ static int read_from_tree(const char *prefix, const char **argv, diff_flush(&opt); diff_tree_release_paths(&opt); - if (!index_was_discarded) - /* The index is still clobbered from do_diff_cache() */ - discard_cache(); - return update_index_refresh(index_fd, lock, refresh_flags); + return 0; } static void set_reflog_message(struct strbuf *sb, const char *action, @@ -225,15 +171,79 @@ static void die_if_unmerged_cache(int reset_type) } -int cmd_reset(int argc, const char **argv, const char *prefix) +static const char **parse_args(const char **argv, const char *prefix, const char **rev_ret) { - int i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0; - int patch_mode = 0; const char *rev = "HEAD"; - unsigned char sha1[20], *orig = NULL, sha1_orig[20], - *old_orig = NULL, sha1_old_orig[20]; - struct commit *commit; + unsigned char unused[20]; + /* + * Possible arguments are: + * + * git reset [-opts] [] + * git reset [-opts] [...] + * git reset [-opts] -- [...] + * git reset [-opts] -- [...] + * git reset [-opts] ... + * + * At this point, argv points immediately after [-opts]. + */ + + if (argv[0]) { + if (!strcmp(argv[0], "--")) { + argv++; /* reset to HEAD, possibly with paths */ + } else if (argv[1] && !strcmp(argv[1], "--")) { + rev = argv[0]; + argv += 2; + } + /* + * Otherwise, argv[0] could be either or and + * has to be unambiguous. If there is a single argument, it + * can not be a tree + */ + else if ((!argv[1] && !get_sha1_committish(argv[0], unused)) || + (argv[1] && !get_sha1_treeish(argv[0], unused))) { + /* + * Ok, argv[0] looks like a commit/tree; it should not + * be a filename. + */ + verify_non_filename(prefix, argv[0]); + rev = *argv++; + } else { + /* Otherwise we treat this as a filename */ + verify_filename(prefix, argv[0], 1); + } + } + *rev_ret = rev; + return argv[0] ? get_pathspec(prefix, argv) : NULL; +} + +static int update_refs(const char *rev, const unsigned char *sha1) +{ + int update_ref_status; struct strbuf msg = STRBUF_INIT; + unsigned char *orig = NULL, sha1_orig[20], + *old_orig = NULL, sha1_old_orig[20]; + + if (!get_sha1("ORIG_HEAD", sha1_old_orig)) + old_orig = sha1_old_orig; + if (!get_sha1("HEAD", sha1_orig)) { + orig = sha1_orig; + set_reflog_message(&msg, "updating ORIG_HEAD", NULL); + update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR); + } else if (old_orig) + delete_ref("ORIG_HEAD", old_orig, 0); + set_reflog_message(&msg, "updating HEAD", rev); + update_ref_status = update_ref(msg.buf, "HEAD", sha1, orig, 0, MSG_ON_ERR); + strbuf_release(&msg); + return update_ref_status; +} + +int cmd_reset(int argc, const char **argv, const char *prefix) +{ + int reset_type = NONE, update_ref_status = 0, quiet = 0; + int patch_mode = 0, unborn; + const char *rev; + unsigned char sha1[20]; + const char **pathspec = NULL; const struct option options[] = { OPT__QUIET(&quiet, N_("be quiet, only report errors")), OPT_SET_INT(0, "mixed", &reset_type, @@ -253,73 +263,45 @@ int cmd_reset(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, git_reset_usage, PARSE_OPT_KEEP_DASHDASH); - - /* - * Possible arguments are: - * - * git reset [-opts] ... - * git reset [-opts] -- ... - * git reset [-opts] -- ... - * git reset [-opts] ... - * - * At this point, argv[i] points immediately after [-opts]. - */ - - if (i < argc) { - if (!strcmp(argv[i], "--")) { - i++; /* reset to HEAD, possibly with paths */ - } else if (i + 1 < argc && !strcmp(argv[i+1], "--")) { - rev = argv[i]; - i += 2; - } - /* - * Otherwise, argv[i] could be either or and - * has to be unambiguous. - */ - else if (!get_sha1_committish(argv[i], sha1)) { - /* - * Ok, argv[i] looks like a rev; it should not - * be a filename. - */ - verify_non_filename(prefix, argv[i]); - rev = argv[i++]; - } else { - /* Otherwise we treat this as a filename */ - verify_filename(prefix, argv[i], 1); - } + pathspec = parse_args(argv, prefix, &rev); + + unborn = !strcmp(rev, "HEAD") && get_sha1("HEAD", sha1); + if (unborn) { + /* reset on unborn branch: treat as reset to empty tree */ + hashcpy(sha1, EMPTY_TREE_SHA1_BIN); + } else if (!pathspec) { + struct commit *commit; + if (get_sha1_committish(rev, sha1)) + die(_("Failed to resolve '%s' as a valid revision."), rev); + commit = lookup_commit_reference(sha1); + if (!commit) + die(_("Could not parse object '%s'."), rev); + hashcpy(sha1, commit->object.sha1); + } else { + struct tree *tree; + if (get_sha1_treeish(rev, sha1)) + die(_("Failed to resolve '%s' as a valid tree."), rev); + tree = parse_tree_indirect(sha1); + if (!tree) + die(_("Could not parse object '%s'."), rev); + hashcpy(sha1, tree->object.sha1); } - if (get_sha1_committish(rev, sha1)) - die(_("Failed to resolve '%s' as a valid ref."), rev); - - /* - * NOTE: As "git reset $treeish -- $path" should be usable on - * any tree-ish, this is not strictly correct. We are not - * moving the HEAD to any commit; we are merely resetting the - * entries in the index to that of a treeish. - */ - commit = lookup_commit_reference(sha1); - if (!commit) - die(_("Could not parse object '%s'."), rev); - hashcpy(sha1, commit->object.sha1); - if (patch_mode) { if (reset_type != NONE) die(_("--patch is incompatible with --{hard,mixed,soft}")); - return interactive_reset(rev, argv + i, prefix); + return run_add_interactive(sha1_to_hex(sha1), "--patch=reset", pathspec); } /* git reset tree [--] paths... can be used to * load chosen paths from the tree into the index without * affecting the working tree nor HEAD. */ - if (i < argc) { + if (pathspec) { if (reset_type == MIXED) warning(_("--mixed with paths is deprecated; use 'git reset -- ' instead.")); else if (reset_type != NONE) die(_("Cannot do %s reset with paths."), _(reset_type_names[reset_type])); - return read_from_tree(prefix, argv + i, sha1, - quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN); } if (reset_type == NONE) reset_type = MIXED; /* by default */ @@ -334,49 +316,44 @@ int cmd_reset(int argc, const char **argv, const char *prefix) /* Soft reset does not touch the index file nor the working tree * at all, but requires them in a good order. Other resets reset * the index file to the tree object we are switching to. */ - if (reset_type == SOFT) + if (reset_type == SOFT || reset_type == KEEP) die_if_unmerged_cache(reset_type); - else { - int err; - if (reset_type == KEEP) - die_if_unmerged_cache(reset_type); - err = reset_index_file(sha1, reset_type, quiet); - if (reset_type == KEEP) - err = err || reset_index_file(sha1, MIXED, quiet); - if (err) - die(_("Could not reset index file to revision '%s'."), rev); - } - /* Any resets update HEAD to the head being switched to, - * saving the previous head in ORIG_HEAD before. */ - if (!get_sha1("ORIG_HEAD", sha1_old_orig)) - old_orig = sha1_old_orig; - if (!get_sha1("HEAD", sha1_orig)) { - orig = sha1_orig; - set_reflog_message(&msg, "updating ORIG_HEAD", NULL); - update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR); - } - else if (old_orig) - delete_ref("ORIG_HEAD", old_orig, 0); - set_reflog_message(&msg, "updating HEAD", rev); - update_ref_status = update_ref(msg.buf, "HEAD", sha1, orig, 0, MSG_ON_ERR); + if (reset_type != SOFT) { + struct lock_file *lock = xcalloc(1, sizeof(struct lock_file)); + int newfd = hold_locked_index(lock, 1); + if (reset_type == MIXED) { + if (read_from_tree(pathspec, sha1)) + return 1; + } else { + int err = reset_index(sha1, reset_type, quiet); + if (reset_type == KEEP && !err) + err = reset_index(sha1, MIXED, quiet); + if (err) + die(_("Could not reset index file to revision '%s'."), rev); + } - switch (reset_type) { - case HARD: - if (!update_ref_status && !quiet) - print_new_head_line(commit); - break; - case SOFT: /* Nothing else to do. */ - break; - case MIXED: /* Report what has not been updated. */ - update_index_refresh(0, NULL, - quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN); - break; + if (reset_type == MIXED) { /* Report what has not been updated. */ + int flags = quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN; + refresh_index(&the_index, flags, NULL, NULL, + _("Unstaged changes after reset:")); + } + + if (write_cache(newfd, active_cache, active_nr) || + commit_locked_index(lock)) + die(_("Could not write new index file.")); } - remove_branch_state(); + if (!pathspec && !unborn) { + /* Any resets without paths update HEAD to the head being + * switched to, saving the previous head in ORIG_HEAD before. */ + update_ref_status = update_refs(rev, sha1); - strbuf_release(&msg); + if (reset_type == HARD && !update_ref_status && !quiet) + print_new_head_line(lookup_commit_reference(sha1)); + } + if (!pathspec) + remove_branch_state(); return update_ref_status; } 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..f849e0a4a0 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; @@ -202,6 +44,11 @@ static void print_helper_status(struct ref *ref) msg = "non-fast forward"; break; + case REF_STATUS_REJECT_ALREADY_EXISTS: + res = "error"; + msg = "already exists"; + break; + case REF_STATUS_REJECT_NODELETE: case REF_STATUS_REMOTE_REJECT: res = "error"; @@ -227,181 +74,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; @@ -418,7 +90,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) int send_all = 0; const char *receivepack = "git-receive-pack"; int flags; - int nonfastforward = 0; + unsigned int reject_reasons; int progress = -1; argv++; @@ -556,7 +228,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) ret |= finish_connect(conn); if (!helper_status) - transport_print_push_status(dest, remote_refs, args.verbose, 0, &nonfastforward); + transport_print_push_status(dest, remote_refs, args.verbose, 0, &reject_reasons); if (!args.dry_run && remote) { struct ref *ref; diff --git a/builtin/shortlog.c b/builtin/shortlog.c index b316cf37ca..240bff3efa 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -36,52 +36,28 @@ static void insert_one_record(struct shortlog *log, const char *dot3 = log->common_repo_prefix; char *buffer, *p; struct string_list_item *item; - char namebuf[1024]; - char emailbuf[1024]; - size_t len; + const char *mailbuf, *namebuf; + size_t namelen, maillen; const char *eol; - const char *boemail, *eoemail; struct strbuf subject = STRBUF_INIT; + struct strbuf namemailbuf = STRBUF_INIT; + struct ident_split ident; - boemail = strchr(author, '<'); - if (!boemail) - return; - eoemail = strchr(boemail, '>'); - if (!eoemail) + if (split_ident_line(&ident, author, strlen(author))) return; - /* copy author name to namebuf, to support matching on both name and email */ - memcpy(namebuf, author, boemail - author); - len = boemail - author; - while (len > 0 && isspace(namebuf[len-1])) - len--; - namebuf[len] = 0; - - /* copy email name to emailbuf, to allow email replacement as well */ - memcpy(emailbuf, boemail+1, eoemail - boemail); - emailbuf[eoemail - boemail - 1] = 0; - - if (!map_user(&log->mailmap, emailbuf, sizeof(emailbuf), namebuf, sizeof(namebuf))) { - while (author < boemail && isspace(*author)) - author++; - for (len = 0; - len < sizeof(namebuf) - 1 && author + len < boemail; - len++) - namebuf[len] = author[len]; - while (0 < len && isspace(namebuf[len-1])) - len--; - namebuf[len] = '\0'; - } - else - len = strlen(namebuf); + namebuf = ident.name_begin; + mailbuf = ident.mail_begin; + namelen = ident.name_end - ident.name_begin; + maillen = ident.mail_end - ident.mail_begin; - if (log->email) { - size_t room = sizeof(namebuf) - len - 1; - int maillen = strlen(emailbuf); - snprintf(namebuf + len, room, " <%.*s>", maillen, emailbuf); - } + map_user(&log->mailmap, &mailbuf, &maillen, &namebuf, &namelen); + strbuf_add(&namemailbuf, namebuf, namelen); + + if (log->email) + strbuf_addf(&namemailbuf, " <%.*s>", (int)maillen, mailbuf); - item = string_list_insert(&log->list, namebuf); + item = string_list_insert(&log->list, namemailbuf.buf); if (item->util == NULL) item->util = xcalloc(1, sizeof(struct string_list)); @@ -306,9 +282,8 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) static void add_wrapped_shortlog_msg(struct strbuf *sb, const char *s, const struct shortlog *log) { - int col = strbuf_add_wrapped_text(sb, s, log->in1, log->in2, log->wrap); - if (col != log->wrap) - strbuf_addch(sb, '\n'); + strbuf_add_wrapped_text(sb, s, log->in1, log->in2, log->wrap); + strbuf_addch(sb, '\n'); } void shortlog_output(struct shortlog *log) 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-tree.c b/cache-tree.c index 28ed6574a2..37e4d008b5 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -166,12 +166,8 @@ static int verify_cache(struct cache_entry **cache, fprintf(stderr, "...\n"); break; } - if (ce_stage(ce)) - fprintf(stderr, "%s: unmerged (%s)\n", - ce->name, sha1_to_hex(ce->sha1)); - else - fprintf(stderr, "%s: not added yet\n", - ce->name); + fprintf(stderr, "%s: unmerged (%s)\n", + ce->name, sha1_to_hex(ce->sha1)); } } if (funny) @@ -242,13 +238,17 @@ static int update_one(struct cache_tree *it, int entries, const char *base, int baselen, + int *skip_count, int flags) { struct strbuf buffer; int missing_ok = flags & WRITE_TREE_MISSING_OK; int dryrun = flags & WRITE_TREE_DRY_RUN; + int to_invalidate = 0; int i; + *skip_count = 0; + if (0 <= it->entry_count && has_sha1_file(it->sha1)) return it->entry_count; @@ -263,11 +263,12 @@ static int update_one(struct cache_tree *it, /* * Find the subtrees and update them. */ - for (i = 0; i < entries; i++) { + i = 0; + while (i < entries) { struct cache_entry *ce = cache[i]; struct cache_tree_sub *sub; const char *path, *slash; - int pathlen, sublen, subcnt; + int pathlen, sublen, subcnt, subskip; path = ce->name; pathlen = ce_namelen(ce); @@ -275,8 +276,10 @@ static int update_one(struct cache_tree *it, break; /* at the end of this level */ slash = strchr(path + baselen, '/'); - if (!slash) + if (!slash) { + i++; continue; + } /* * a/bbb/c (base = a/, slash = /c) * ==> @@ -290,10 +293,13 @@ static int update_one(struct cache_tree *it, cache + i, entries - i, path, baselen + sublen + 1, + &subskip, flags); if (subcnt < 0) return subcnt; - i += subcnt - 1; + i += subcnt; + sub->count = subcnt; /* to be used in the next loop */ + *skip_count += subskip; sub->used = 1; } @@ -304,7 +310,8 @@ static int update_one(struct cache_tree *it, */ strbuf_init(&buffer, 8192); - for (i = 0; i < entries; i++) { + i = 0; + while (i < entries) { struct cache_entry *ce = cache[i]; struct cache_tree_sub *sub; const char *path, *slash; @@ -324,14 +331,17 @@ static int update_one(struct cache_tree *it, if (!sub) die("cache-tree.c: '%.*s' in '%s' not found", entlen, path + baselen, path); - i += sub->cache_tree->entry_count - 1; + i += sub->count; sha1 = sub->cache_tree->sha1; mode = S_IFDIR; + if (sub->cache_tree->entry_count < 0) + to_invalidate = 1; } else { sha1 = ce->sha1; mode = ce->ce_mode; entlen = pathlen - baselen; + i++; } if (mode != S_IFGITLINK && !missing_ok && !has_sha1_file(sha1)) { strbuf_release(&buffer); @@ -339,8 +349,25 @@ static int update_one(struct cache_tree *it, mode, sha1_to_hex(sha1), entlen+baselen, path); } - if (ce->ce_flags & (CE_REMOVE | CE_INTENT_TO_ADD)) - continue; /* entry being removed or placeholder */ + /* + * CE_REMOVE entries are removed before the index is + * written to disk. Skip them to remain consistent + * with the future on-disk index. + */ + if (ce->ce_flags & CE_REMOVE) { + *skip_count = *skip_count + 1; + continue; + } + + /* + * CE_INTENT_TO_ADD entries exist on on-disk index but + * they are not part of generated trees. Invalidate up + * to root to force cache-tree users to read elsewhere. + */ + if (ce->ce_flags & CE_INTENT_TO_ADD) { + to_invalidate = 1; + continue; + } strbuf_grow(&buffer, entlen + 100); strbuf_addf(&buffer, "%o %.*s%c", mode, entlen, path + baselen, '\0'); @@ -360,7 +387,7 @@ static int update_one(struct cache_tree *it, } strbuf_release(&buffer); - it->entry_count = i; + it->entry_count = to_invalidate ? -1 : i - *skip_count; #if DEBUG fprintf(stderr, "cache-tree update-one (%d ent, %d subtree) %s\n", it->entry_count, it->subtree_nr, @@ -374,11 +401,11 @@ int cache_tree_update(struct cache_tree *it, int entries, int flags) { - int i; + int i, skip; i = verify_cache(cache, entries, flags); if (i) return i; - i = update_one(it, cache, entries, "", 0, flags); + i = update_one(it, cache, entries, "", 0, &skip, flags); if (i < 0) return i; return 0; diff --git a/cache-tree.h b/cache-tree.h index d8cb2e9e39..55d0f59f2b 100644 --- a/cache-tree.h +++ b/cache-tree.h @@ -7,6 +7,7 @@ struct cache_tree; struct cache_tree_sub { struct cache_tree *cache_tree; + int count; /* internally used by update_one() */ int namelen; int used; char name[FLEX_ARRAY]; diff --git a/cache.h b/cache.h index a58df84bd3..1f96f659e4 100644 --- a/cache.h +++ b/cache.h @@ -362,6 +362,7 @@ static inline enum object_type object_type(unsigned int mode) #define GIT_NOTES_DISPLAY_REF_ENVIRONMENT "GIT_NOTES_DISPLAY_REF" #define GIT_NOTES_REWRITE_REF_ENVIRONMENT "GIT_NOTES_REWRITE_REF" #define GIT_NOTES_REWRITE_MODE_ENVIRONMENT "GIT_NOTES_REWRITE_MODE" +#define GIT_LITERAL_PATHSPECS_ENVIRONMENT "GIT_LITERAL_PATHSPECS" /* * Repository-local GIT_* environment variables @@ -473,6 +474,8 @@ extern int index_name_is_other(const struct index_state *, const char *, int); extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int); extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int); +#define PATHSPEC_ONESTAR 1 /* the pathspec pattern sastisfies GFNM_ONESTAR */ + struct pathspec { const char **raw; /* get_pathspec() result, not freed by free_pathspec() */ int nr; @@ -482,7 +485,8 @@ struct pathspec { struct pathspec_item { const char *match; int len; - unsigned int use_wildcard:1; + int nowildcard_len; + int flags; } *items; }; @@ -490,6 +494,8 @@ extern int init_pathspec(struct pathspec *, const char **); extern void free_pathspec(struct pathspec *); extern int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec); +extern int limit_pathspec_to_literal(void); + #define HASH_WRITE_OBJECT 1 #define HASH_FORMAT_CHECK 2 extern int index_fd(unsigned char *sha1, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags); @@ -714,10 +720,11 @@ static inline int is_absolute_path(const char *path) } int is_directory(const char *); const char *real_path(const char *path); +const char *real_path_if_valid(const char *path); const char *absolute_path(const char *path); const char *relative_path(const char *abs, const char *base); int normalize_path_copy(char *dst, const char *src); -int longest_ancestor_length(const char *path, const char *prefix_list); +int longest_ancestor_length(const char *path, struct string_list *prefixes); char *strip_path_suffix(const char *path, const char *suffix); int daemon_avoid_alias(const char *path); int offset_1st_component(const char *path); @@ -999,14 +1006,18 @@ struct ref { unsigned char old_sha1[20]; unsigned char new_sha1[20]; char *symref; - unsigned int force:1, + unsigned int + force:1, + requires_force:1, merge:1, nonfastforward:1, + update:1, deletion:1; enum { REF_STATUS_NONE = 0, REF_STATUS_OK, REF_STATUS_REJECT_NONFASTFORWARD, + REF_STATUS_REJECT_ALREADY_EXISTS, REF_STATUS_REJECT_NODELETE, REF_STATUS_UPTODATE, REF_STATUS_REMOTE_REJECT, @@ -1136,6 +1147,9 @@ extern int check_repository_format_version(const char *var, const char *value, v extern int git_env_bool(const char *, int); extern int git_config_system(void); extern int config_error_nonbool(const char *); +#if defined(__GNUC__) && ! defined(__clang__) +#define config_error_nonbool(s) (config_error_nonbool(s), -1) +#endif extern const char *get_log_output_encoding(void); extern const char *get_commit_output_encoding(void); @@ -1149,15 +1163,13 @@ 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; extern const char *git_mailmap_file; +extern const char *git_mailmap_blob; /* IO helper functions */ extern void maybe_flush_or_die(FILE *, const char *); @@ -1183,6 +1195,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 +1278,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/command-list.txt b/command-list.txt index 7e8cfec29d..bf83303c48 100644 --- a/command-list.txt +++ b/command-list.txt @@ -12,6 +12,7 @@ git-branch mainporcelain common git-bundle mainporcelain git-cat-file plumbinginterrogators git-check-attr purehelpers +git-check-ignore purehelpers git-checkout mainporcelain common git-checkout-index plumbingmanipulators git-check-ref-format purehelpers 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..c16c8a7534 100644 --- a/commit.h +++ b/commit.h @@ -86,9 +86,11 @@ 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; + struct string_list *mailmap; + int color; }; struct userformat_want { @@ -99,8 +101,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 +222,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 0ff1d273a5..5ef0685135 100644 --- a/compat/fnmatch/fnmatch.c +++ b/compat/fnmatch/fnmatch.c @@ -55,7 +55,8 @@ program understand `configure --with-gnu-libc' and omit the object files, it is simpler to just do this in the source for each such file. */ -#if defined _LIBC || !defined __GNU_LIBRARY__ +#if defined NO_FNMATCH || defined NO_FNMATCH_CASEFOLD || \ + defined _LIBC || !defined __GNU_LIBRARY__ # if defined STDC_HEADERS || !defined isascii @@ -135,9 +136,9 @@ extern int errno; # if !defined HAVE___STRCHRNUL && !defined _LIBC static char * -__strchrnul (s, c) - const char *s; - int c; +__strchrnul (const char *s, int c) + + { char *result = strchr (s, c); if (result == NULL) @@ -159,11 +160,11 @@ static int internal_fnmatch __P ((const char *pattern, const char *string, internal_function; static int internal_function -internal_fnmatch (pattern, string, no_leading_period, flags) - const char *pattern; - const char *string; - int no_leading_period; - int flags; +internal_fnmatch (const char *pattern, const char *string, int no_leading_period, int flags) + + + + { register const char *p = pattern, *n = string; register unsigned char c; @@ -481,10 +482,10 @@ internal_fnmatch (pattern, string, no_leading_period, flags) int -fnmatch (pattern, string, flags) - const char *pattern; - const char *string; - int flags; +fnmatch (const char *pattern, const char *string, int flags) + + + { return internal_fnmatch (pattern, string, flags & FNM_PERIOD, flags); } diff --git a/compat/mingw.c b/compat/mingw.c index afc892d6b1..b673625580 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -256,6 +256,8 @@ int mingw_rmdir(const char *pathname) while ((ret = rmdir(pathname)) == -1 && tries < ARRAY_SIZE(delay)) { if (!is_file_in_use_error(GetLastError())) + errno = err_win_to_posix(GetLastError()); + if (errno != EACCES) break; if (!is_dir_empty(pathname)) { errno = ENOTEMPTY; @@ -271,7 +273,7 @@ int mingw_rmdir(const char *pathname) Sleep(delay[tries]); tries++; } - while (ret == -1 && is_file_in_use_error(GetLastError()) && + while (ret == -1 && errno == EACCES && is_file_in_use_error(GetLastError()) && ask_yes_no_if_possible("Deletion of directory '%s' failed. " "Should I try again?", pathname)) ret = rmdir(pathname); @@ -319,6 +321,31 @@ ssize_t mingw_write(int fd, const void *buf, size_t count) return write(fd, buf, min(count, 31 * 1024 * 1024)); } +static BOOL WINAPI ctrl_ignore(DWORD type) +{ + return TRUE; +} + +#undef fgetc +int mingw_fgetc(FILE *stream) +{ + int ch; + if (!isatty(_fileno(stream))) + return fgetc(stream); + + SetConsoleCtrlHandler(ctrl_ignore, TRUE); + while (1) { + ch = fgetc(stream); + if (ch != EOF || GetLastError() != ERROR_OPERATION_ABORTED) + break; + + /* Ctrl+C was pressed, simulate SIGINT and retry */ + mingw_raise(SIGINT); + } + SetConsoleCtrlHandler(ctrl_ignore, FALSE); + return ch; +} + #undef fopen FILE *mingw_fopen (const char *filename, const char *otype) { @@ -335,6 +362,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. @@ -1524,7 +1573,7 @@ static HANDLE timer_event; static HANDLE timer_thread; static int timer_interval; static int one_shot; -static sig_handler_t timer_fn = SIG_DFL; +static sig_handler_t timer_fn = SIG_DFL, sigint_fn = SIG_DFL; /* The timer works like this: * The thread, ticktack(), is a trivial routine that most of the time @@ -1538,10 +1587,7 @@ static sig_handler_t timer_fn = SIG_DFL; static unsigned __stdcall ticktack(void *dummy) { while (WaitForSingleObject(timer_event, timer_interval) == WAIT_TIMEOUT) { - if (timer_fn == SIG_DFL) - die("Alarm"); - if (timer_fn != SIG_IGN) - timer_fn(SIGALRM); + mingw_raise(SIGALRM); if (one_shot) break; } @@ -1632,12 +1678,49 @@ int sigaction(int sig, struct sigaction *in, struct sigaction *out) sig_handler_t mingw_signal(int sig, sig_handler_t handler) { sig_handler_t old = timer_fn; - if (sig != SIGALRM) + + switch (sig) { + case SIGALRM: + timer_fn = handler; + break; + + case SIGINT: + sigint_fn = handler; + break; + + default: return signal(sig, handler); - timer_fn = handler; + } + return old; } +#undef raise +int mingw_raise(int sig) +{ + switch (sig) { + case SIGALRM: + if (timer_fn == SIG_DFL) { + if (isatty(STDERR_FILENO)) + fputs("Alarm clock\n", stderr); + exit(128 + SIGALRM); + } else if (timer_fn != SIG_IGN) + timer_fn(SIGALRM); + return 0; + + case SIGINT: + if (sigint_fn == SIG_DFL) + exit(128 + SIGINT); + else if (sigint_fn != SIG_IGN) + sigint_fn(SIGINT); + return 0; + + default: + return raise(sig); + } +} + + static const char *make_backslash_path(const char *path) { static char buf[PATH_MAX + 1]; @@ -1699,21 +1782,6 @@ int link(const char *oldpath, const char *newpath) return 0; } -char *getpass(const char *prompt) -{ - struct strbuf buf = STRBUF_INIT; - - fputs(prompt, stderr); - for (;;) { - char c = _getch(); - if (c == '\r' || c == '\n') - break; - strbuf_addch(&buf, c); - } - fputs("\n", stderr); - return strbuf_detach(&buf, NULL); -} - pid_t waitpid(pid_t pid, int *status, int options) { HANDLE h = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION, diff --git a/compat/mingw.h b/compat/mingw.h index 61a652138a..685cd2c3d4 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -55,8 +55,6 @@ struct passwd { char *pw_dir; }; -extern char *getpass(const char *prompt); - typedef void (__cdecl *sig_handler_t)(int); struct sigaction { sig_handler_t sa_handler; @@ -179,12 +177,18 @@ int mingw_open (const char *filename, int oflags, ...); ssize_t mingw_write(int fd, const void *buf, size_t count); #define write mingw_write +int mingw_fgetc(FILE *stream); +#define fgetc mingw_fgetc + FILE *mingw_fopen (const char *filename, const char *otype); #define fopen mingw_fopen 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 @@ -287,6 +291,9 @@ static inline unsigned int git_ntohl(unsigned int x) sig_handler_t mingw_signal(int sig, sig_handler_t handler); #define signal mingw_signal +int mingw_raise(int sig); +#define raise mingw_raise + /* * ANSI emulation wrappers */ diff --git a/compat/nedmalloc/nedmalloc.c b/compat/nedmalloc/nedmalloc.c index d9a17a8057..91c4e7f27b 100644 --- a/compat/nedmalloc/nedmalloc.c +++ b/compat/nedmalloc/nedmalloc.c @@ -603,7 +603,10 @@ static NOINLINE mstate FindMSpace(nedpool *p, threadcache *tc, int *lastUsed, si } /* We really want to make sure this goes into memory now but we have to be careful of breaking aliasing rules, so write it twice */ - *((volatile struct malloc_state **) &p->m[end])=p->m[end]=temp; + { + volatile struct malloc_state **_m=(volatile struct malloc_state **) &p->m[end]; + *_m=(p->m[end]=temp); + } ACQUIRE_LOCK(&p->m[end]->mutex); /*printf("Created mspace idx %d\n", end);*/ RELEASE_LOCK(&p->mutex); diff --git a/compat/terminal.c b/compat/terminal.c index bbb038dd01..9b5e3d1bb8 100644 --- a/compat/terminal.c +++ b/compat/terminal.c @@ -3,8 +3,22 @@ #include "sigchain.h" #include "strbuf.h" +#if defined(HAVE_DEV_TTY) || defined(WIN32) + +static void restore_term(void); + +static void restore_term_on_signal(int sig) +{ + restore_term(); + sigchain_pop(sig); + raise(sig); +} + #ifdef HAVE_DEV_TTY +#define INPUT_PATH "/dev/tty" +#define OUTPUT_PATH "/dev/tty" + static int term_fd = -1; static struct termios old_term; @@ -14,58 +28,109 @@ static void restore_term(void) return; tcsetattr(term_fd, TCSAFLUSH, &old_term); + close(term_fd); term_fd = -1; } -static void restore_term_on_signal(int sig) +static int disable_echo(void) { - restore_term(); - sigchain_pop(sig); - raise(sig); + struct termios t; + + term_fd = open("/dev/tty", O_RDWR); + if (tcgetattr(term_fd, &t) < 0) + goto error; + + old_term = t; + sigchain_push_common(restore_term_on_signal); + + t.c_lflag &= ~ECHO; + if (!tcsetattr(term_fd, TCSAFLUSH, &t)) + return 0; + +error: + close(term_fd); + term_fd = -1; + return -1; +} + +#elif defined(WIN32) + +#define INPUT_PATH "CONIN$" +#define OUTPUT_PATH "CONOUT$" +#define FORCE_TEXT "t" + +static HANDLE hconin = INVALID_HANDLE_VALUE; +static DWORD cmode; + +static void restore_term(void) +{ + if (hconin == INVALID_HANDLE_VALUE) + return; + + SetConsoleMode(hconin, cmode); + CloseHandle(hconin); + hconin = INVALID_HANDLE_VALUE; } +static int disable_echo(void) +{ + hconin = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL); + if (hconin == INVALID_HANDLE_VALUE) + return -1; + + GetConsoleMode(hconin, &cmode); + sigchain_push_common(restore_term_on_signal); + if (!SetConsoleMode(hconin, cmode & (~ENABLE_ECHO_INPUT))) { + CloseHandle(hconin); + hconin = INVALID_HANDLE_VALUE; + return -1; + } + + return 0; +} + +#endif + +#ifndef FORCE_TEXT +#define FORCE_TEXT +#endif + char *git_terminal_prompt(const char *prompt, int echo) { static struct strbuf buf = STRBUF_INIT; int r; - FILE *fh; + FILE *input_fh, *output_fh; - fh = fopen("/dev/tty", "w+"); - if (!fh) + input_fh = fopen(INPUT_PATH, "r" FORCE_TEXT); + if (!input_fh) return NULL; - if (!echo) { - struct termios t; - - if (tcgetattr(fileno(fh), &t) < 0) { - fclose(fh); - return NULL; - } - - old_term = t; - term_fd = fileno(fh); - sigchain_push_common(restore_term_on_signal); - - t.c_lflag &= ~ECHO; - if (tcsetattr(fileno(fh), TCSAFLUSH, &t) < 0) { - term_fd = -1; - fclose(fh); - return NULL; - } + output_fh = fopen(OUTPUT_PATH, "w" FORCE_TEXT); + if (!output_fh) { + fclose(input_fh); + return NULL; + } + + if (!echo && disable_echo()) { + fclose(input_fh); + fclose(output_fh); + return NULL; } - fputs(prompt, fh); - fflush(fh); + fputs(prompt, output_fh); + fflush(output_fh); - r = strbuf_getline(&buf, fh, '\n'); + r = strbuf_getline(&buf, input_fh, '\n'); if (!echo) { - fseek(fh, SEEK_CUR, 0); - putc('\n', fh); - fflush(fh); + putc('\n', output_fh); + fflush(output_fh); } restore_term(); - fclose(fh); + fclose(input_fh); + fclose(output_fh); if (r == EOF) return NULL; diff --git a/config.c b/config.c index fff8a43bc0..7b444b68ab 100644 --- a/config.c +++ b/config.c @@ -58,7 +58,7 @@ static int handle_path_include(const char *path, struct config_include_data *inc path = buf.buf; } - if (!access_or_warn(path, R_OK)) { + if (!access_or_die(path, R_OK)) { if (++inc->depth > MAX_INCLUDE_DEPTH) die(include_depth_advice, MAX_INCLUDE_DEPTH, path, cf && cf->name ? cf->name : "the command line"); @@ -839,6 +839,8 @@ static int git_default_mailmap_config(const char *var, const char *value) { if (!strcmp(var, "mailmap.file")) return git_config_string(&git_mailmap_file, var, value); + if (!strcmp(var, "mailmap.blob")) + return git_config_string(&git_mailmap_blob, var, value); /* Add other config variables here and to Documentation/config.txt. */ return 0; @@ -938,23 +940,23 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config) home_config_paths(&user_config, &xdg_config, "config"); - if (git_config_system() && !access_or_warn(git_etc_gitconfig(), R_OK)) { + if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK)) { ret += git_config_from_file(fn, git_etc_gitconfig(), data); found += 1; } - if (xdg_config && !access_or_warn(xdg_config, R_OK)) { + if (xdg_config && !access_or_die(xdg_config, R_OK)) { ret += git_config_from_file(fn, xdg_config, data); found += 1; } - if (user_config && !access_or_warn(user_config, R_OK)) { + if (user_config && !access_or_die(user_config, R_OK)) { ret += git_config_from_file(fn, user_config, data); found += 1; } - if (repo_config && !access_or_warn(repo_config, R_OK)) { + if (repo_config && !access_or_die(repo_config, R_OK)) { ret += git_config_from_file(fn, repo_config, data); found += 1; } @@ -1279,6 +1281,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; } @@ -1659,6 +1662,7 @@ int git_config_rename_section(const char *old_name, const char *new_name) * Call this to report error for your variable that should not * get a boolean value (i.e. "[my] var" means "true"). */ +#undef config_error_nonbool int config_error_nonbool(const char *var) { return error("Missing value for '%s'", var); diff --git a/config.mak.in b/config.mak.in index 69d48382fe..e8a9bb4682 100644 --- a/config.mak.in +++ b/config.mak.in @@ -18,7 +18,7 @@ datarootdir = @datarootdir@ template_dir = @datadir@/git-core/templates sysconfdir = @sysconfdir@ -mandir=@mandir@ +mandir = @mandir@ srcdir = @srcdir@ VPATH = @srcdir@ diff --git a/config.mak.uname b/config.mak.uname new file mode 100644 index 0000000000..bea34f0511 --- /dev/null +++ b/config.mak.uname @@ -0,0 +1,539 @@ +# Platform specific Makefile tweaks based on uname detection + +uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') +uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not') +uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not') +uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not') +uname_P := $(shell sh -c 'uname -p 2>/dev/null || echo not') +uname_V := $(shell sh -c 'uname -v 2>/dev/null || echo not') + +ifdef MSVC + # avoid the MingW and Cygwin configuration sections + uname_S := Windows + uname_O := Windows +endif + +# We choose to avoid "if .. else if .. else .. endif endif" +# because maintaining the nesting to match is a pain. If +# we had "elif" things would have been much nicer... + +ifeq ($(uname_M),x86_64) + XDL_FAST_HASH = YesPlease +endif +ifeq ($(uname_S),OSF1) + # Need this for u_short definitions et al + BASIC_CFLAGS += -D_OSF_SOURCE + SOCKLEN_T = int + NO_STRTOULL = YesPlease + NO_NSEC = YesPlease +endif +ifeq ($(uname_S),Linux) + NO_STRLCPY = YesPlease + NO_MKSTEMPS = YesPlease + HAVE_PATHS_H = YesPlease + LIBC_CONTAINS_LIBINTL = YesPlease + HAVE_DEV_TTY = YesPlease +endif +ifeq ($(uname_S),GNU/kFreeBSD) + NO_STRLCPY = YesPlease + NO_MKSTEMPS = YesPlease + HAVE_PATHS_H = YesPlease + DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease + LIBC_CONTAINS_LIBINTL = YesPlease +endif +ifeq ($(uname_S),UnixWare) + CC = cc + NEEDS_SOCKET = YesPlease + NEEDS_NSL = YesPlease + NEEDS_SSL_WITH_CRYPTO = YesPlease + NEEDS_LIBICONV = YesPlease + SHELL_PATH = /usr/local/bin/bash + NO_IPV6 = YesPlease + NO_HSTRERROR = YesPlease + NO_MKSTEMPS = YesPlease + BASIC_CFLAGS += -Kthread + BASIC_CFLAGS += -I/usr/local/include + BASIC_LDFLAGS += -L/usr/local/lib + INSTALL = ginstall + TAR = gtar + NO_STRCASESTR = YesPlease + NO_MEMMEM = YesPlease +endif +ifeq ($(uname_S),SCO_SV) + ifeq ($(uname_R),3.2) + CFLAGS = -O2 + endif + ifeq ($(uname_R),5) + CC = cc + BASIC_CFLAGS += -Kthread + endif + NEEDS_SOCKET = YesPlease + NEEDS_NSL = YesPlease + NEEDS_SSL_WITH_CRYPTO = YesPlease + NEEDS_LIBICONV = YesPlease + SHELL_PATH = /usr/bin/bash + NO_IPV6 = YesPlease + NO_HSTRERROR = YesPlease + NO_MKSTEMPS = YesPlease + BASIC_CFLAGS += -I/usr/local/include + BASIC_LDFLAGS += -L/usr/local/lib + NO_STRCASESTR = YesPlease + NO_MEMMEM = YesPlease + INSTALL = ginstall + TAR = gtar +endif +ifeq ($(uname_S),Darwin) + NEEDS_CRYPTO_WITH_SSL = YesPlease + NEEDS_SSL_WITH_CRYPTO = YesPlease + NEEDS_LIBICONV = YesPlease + ifeq ($(shell expr "$(uname_R)" : '[15678]\.'),2) + OLD_ICONV = UnfortunatelyYes + endif + ifeq ($(shell expr "$(uname_R)" : '[15]\.'),2) + NO_STRLCPY = YesPlease + endif + NO_MEMMEM = YesPlease + USE_ST_TIMESPEC = YesPlease + HAVE_DEV_TTY = YesPlease + COMPAT_OBJS += compat/precompose_utf8.o + BASIC_CFLAGS += -DPRECOMPOSE_UNICODE +endif +ifeq ($(uname_S),SunOS) + NEEDS_SOCKET = YesPlease + NEEDS_NSL = YesPlease + SHELL_PATH = /bin/bash + SANE_TOOL_PATH = /usr/xpg6/bin:/usr/xpg4/bin + NO_STRCASESTR = YesPlease + NO_MEMMEM = YesPlease + NO_MKDTEMP = YesPlease + NO_MKSTEMPS = YesPlease + NO_REGEX = YesPlease + NO_FNMATCH_CASEFOLD = YesPlease + NO_MSGFMT_EXTENDED_OPTIONS = YesPlease + HAVE_DEV_TTY = YesPlease + ifeq ($(uname_R),5.6) + SOCKLEN_T = int + NO_HSTRERROR = YesPlease + NO_IPV6 = YesPlease + NO_SOCKADDR_STORAGE = YesPlease + NO_UNSETENV = YesPlease + NO_SETENV = YesPlease + NO_STRLCPY = YesPlease + NO_STRTOUMAX = YesPlease + GIT_TEST_CMP = cmp + endif + ifeq ($(uname_R),5.7) + NEEDS_RESOLV = YesPlease + NO_IPV6 = YesPlease + NO_SOCKADDR_STORAGE = YesPlease + NO_UNSETENV = YesPlease + NO_SETENV = YesPlease + NO_STRLCPY = YesPlease + NO_STRTOUMAX = YesPlease + GIT_TEST_CMP = cmp + endif + ifeq ($(uname_R),5.8) + NO_UNSETENV = YesPlease + NO_SETENV = YesPlease + NO_STRTOUMAX = YesPlease + GIT_TEST_CMP = cmp + endif + ifeq ($(uname_R),5.9) + NO_UNSETENV = YesPlease + NO_SETENV = YesPlease + NO_STRTOUMAX = YesPlease + GIT_TEST_CMP = cmp + endif + INSTALL = /usr/ucb/install + TAR = gtar + BASIC_CFLAGS += -D__EXTENSIONS__ -D__sun__ -DHAVE_ALLOCA_H +endif +ifeq ($(uname_O),Cygwin) + ifeq ($(shell expr "$(uname_R)" : '1\.[1-6]\.'),4) + NO_D_TYPE_IN_DIRENT = YesPlease + NO_D_INO_IN_DIRENT = YesPlease + NO_STRCASESTR = YesPlease + NO_MEMMEM = YesPlease + NO_MKSTEMPS = YesPlease + NO_SYMLINK_HEAD = YesPlease + NO_IPV6 = YesPlease + OLD_ICONV = UnfortunatelyYes + CYGWIN_V15_WIN32API = YesPlease + endif + NO_THREAD_SAFE_PREAD = YesPlease + NEEDS_LIBICONV = YesPlease + NO_FAST_WORKING_DIRECTORY = UnfortunatelyYes + NO_TRUSTABLE_FILEMODE = UnfortunatelyYes + NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease + # There are conflicting reports about this. + # On some boxes NO_MMAP is needed, and not so elsewhere. + # Try commenting this out if you suspect MMAP is more efficient + NO_MMAP = YesPlease + X = .exe + COMPAT_OBJS += compat/cygwin.o + UNRELIABLE_FSTAT = UnfortunatelyYes + SPARSE_FLAGS = -isystem /usr/include/w32api -Wno-one-bit-signed-bitfield +endif +ifeq ($(uname_S),FreeBSD) + NEEDS_LIBICONV = YesPlease + OLD_ICONV = YesPlease + NO_MEMMEM = YesPlease + BASIC_CFLAGS += -I/usr/local/include + BASIC_LDFLAGS += -L/usr/local/lib + DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease + USE_ST_TIMESPEC = YesPlease + ifeq ($(shell expr "$(uname_R)" : '4\.'),2) + PTHREAD_LIBS = -pthread + NO_UINTMAX_T = YesPlease + NO_STRTOUMAX = YesPlease + endif + PYTHON_PATH = /usr/local/bin/python + HAVE_PATHS_H = YesPlease +endif +ifeq ($(uname_S),OpenBSD) + NO_STRCASESTR = YesPlease + NO_MEMMEM = YesPlease + USE_ST_TIMESPEC = YesPlease + NEEDS_LIBICONV = YesPlease + BASIC_CFLAGS += -I/usr/local/include + BASIC_LDFLAGS += -L/usr/local/lib + HAVE_PATHS_H = YesPlease +endif +ifeq ($(uname_S),NetBSD) + ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2) + NEEDS_LIBICONV = YesPlease + endif + BASIC_CFLAGS += -I/usr/pkg/include + BASIC_LDFLAGS += -L/usr/pkg/lib $(CC_LD_DYNPATH)/usr/pkg/lib + USE_ST_TIMESPEC = YesPlease + NO_MKSTEMPS = YesPlease + HAVE_PATHS_H = YesPlease +endif +ifeq ($(uname_S),AIX) + DEFAULT_PAGER = more + NO_STRCASESTR = YesPlease + NO_MEMMEM = YesPlease + NO_MKDTEMP = YesPlease + NO_MKSTEMPS = YesPlease + NO_STRLCPY = YesPlease + NO_NSEC = YesPlease + FREAD_READS_DIRECTORIES = UnfortunatelyYes + INTERNAL_QSORT = UnfortunatelyYes + NEEDS_LIBICONV = YesPlease + BASIC_CFLAGS += -D_LARGE_FILES + ifeq ($(shell expr "$(uname_V)" : '[1234]'),1) + NO_PTHREADS = YesPlease + else + PTHREAD_LIBS = -lpthread + endif + ifeq ($(shell expr "$(uname_V).$(uname_R)" : '5\.1'),3) + INLINE = '' + endif + GIT_TEST_CMP = cmp +endif +ifeq ($(uname_S),GNU) + # GNU/Hurd + NO_STRLCPY = YesPlease + NO_MKSTEMPS = YesPlease + HAVE_PATHS_H = YesPlease + LIBC_CONTAINS_LIBINTL = YesPlease +endif +ifeq ($(uname_S),IRIX) + NO_SETENV = YesPlease + NO_UNSETENV = YesPlease + NO_STRCASESTR = YesPlease + NO_MEMMEM = YesPlease + NO_MKSTEMPS = YesPlease + NO_MKDTEMP = YesPlease + # When compiled with the MIPSpro 7.4.4m compiler, and without pthreads + # (i.e. NO_PTHREADS is set), and _with_ MMAP (i.e. NO_MMAP is not set), + # git dies with a segmentation fault when trying to access the first + # entry of a reflog. The conservative choice is made to always set + # NO_MMAP. If you suspect that your compiler is not affected by this + # issue, comment out the NO_MMAP statement. + NO_MMAP = YesPlease + NO_REGEX = YesPlease + NO_FNMATCH_CASEFOLD = YesPlease + SNPRINTF_RETURNS_BOGUS = YesPlease + SHELL_PATH = /usr/gnu/bin/bash + NEEDS_LIBGEN = YesPlease +endif +ifeq ($(uname_S),IRIX64) + NO_SETENV = YesPlease + NO_UNSETENV = YesPlease + NO_STRCASESTR = YesPlease + NO_MEMMEM = YesPlease + NO_MKSTEMPS = YesPlease + NO_MKDTEMP = YesPlease + # When compiled with the MIPSpro 7.4.4m compiler, and without pthreads + # (i.e. NO_PTHREADS is set), and _with_ MMAP (i.e. NO_MMAP is not set), + # git dies with a segmentation fault when trying to access the first + # entry of a reflog. The conservative choice is made to always set + # NO_MMAP. If you suspect that your compiler is not affected by this + # issue, comment out the NO_MMAP statement. + NO_MMAP = YesPlease + NO_REGEX = YesPlease + NO_FNMATCH_CASEFOLD = YesPlease + SNPRINTF_RETURNS_BOGUS = YesPlease + SHELL_PATH = /usr/gnu/bin/bash + NEEDS_LIBGEN = YesPlease +endif +ifeq ($(uname_S),HP-UX) + INLINE = __inline + NO_IPV6 = YesPlease + NO_SETENV = YesPlease + NO_STRCASESTR = YesPlease + NO_MEMMEM = YesPlease + NO_MKSTEMPS = YesPlease + NO_STRLCPY = YesPlease + NO_MKDTEMP = YesPlease + NO_UNSETENV = YesPlease + NO_HSTRERROR = YesPlease + NO_SYS_SELECT_H = YesPlease + NO_FNMATCH_CASEFOLD = YesPlease + SNPRINTF_RETURNS_BOGUS = YesPlease + NO_NSEC = YesPlease + ifeq ($(uname_R),B.11.00) + NO_INET_NTOP = YesPlease + NO_INET_PTON = YesPlease + endif + ifeq ($(uname_R),B.10.20) + # Override HP-UX 11.x setting: + INLINE = + SOCKLEN_T = size_t + NO_PREAD = YesPlease + NO_INET_NTOP = YesPlease + NO_INET_PTON = YesPlease + endif + GIT_TEST_CMP = cmp +endif +ifeq ($(uname_S),Windows) + GIT_VERSION := $(GIT_VERSION).MSVC + pathsep = ; + NO_PREAD = YesPlease + NEEDS_CRYPTO_WITH_SSL = YesPlease + NO_LIBGEN_H = YesPlease + NO_POLL = YesPlease + NO_SYMLINK_HEAD = YesPlease + NO_IPV6 = YesPlease + NO_UNIX_SOCKETS = YesPlease + NO_SETENV = YesPlease + NO_UNSETENV = YesPlease + NO_STRCASESTR = YesPlease + NO_STRLCPY = YesPlease + NO_STRTOK_R = YesPlease + NO_FNMATCH = YesPlease + NO_MEMMEM = YesPlease + # NEEDS_LIBICONV = YesPlease + NO_ICONV = YesPlease + NO_STRTOUMAX = YesPlease + NO_STRTOULL = YesPlease + NO_MKDTEMP = YesPlease + NO_MKSTEMPS = YesPlease + SNPRINTF_RETURNS_BOGUS = YesPlease + NO_SVN_TESTS = YesPlease + NO_PERL_MAKEMAKER = YesPlease + RUNTIME_PREFIX = YesPlease + NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease + NO_NSEC = YesPlease + USE_WIN32_MMAP = YesPlease + # USE_NED_ALLOCATOR = YesPlease + UNRELIABLE_FSTAT = UnfortunatelyYes + OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo + NO_REGEX = YesPlease + NO_CURL = YesPlease + NO_PYTHON = YesPlease + BLK_SHA1 = YesPlease + NO_POSIX_GOODIES = UnfortunatelyYes + NATIVE_CRLF = YesPlease + DEFAULT_HELP_FORMAT = html + + CC = compat/vcbuild/scripts/clink.pl + AR = compat/vcbuild/scripts/lib.pl + CFLAGS = + BASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE + COMPAT_OBJS = compat/msvc.o compat/winansi.o \ + compat/win32/pthread.o compat/win32/syslog.o \ + compat/win32/dirent.o + COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\" + BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE -NODEFAULTLIB:MSVCRT.lib + EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib + PTHREAD_LIBS = + lib = +ifndef DEBUG + BASIC_CFLAGS += -GL -Os -MT + BASIC_LDFLAGS += -LTCG + AR += -LTCG +else + BASIC_CFLAGS += -Zi -MTd +endif + X = .exe +endif +ifeq ($(uname_S),Interix) + NO_INITGROUPS = YesPlease + NO_IPV6 = YesPlease + NO_MEMMEM = YesPlease + NO_MKDTEMP = YesPlease + NO_STRTOUMAX = YesPlease + NO_NSEC = YesPlease + NO_MKSTEMPS = YesPlease + ifeq ($(uname_R),3.5) + NO_INET_NTOP = YesPlease + NO_INET_PTON = YesPlease + NO_SOCKADDR_STORAGE = YesPlease + NO_FNMATCH_CASEFOLD = YesPlease + endif + ifeq ($(uname_R),5.2) + NO_INET_NTOP = YesPlease + NO_INET_PTON = YesPlease + NO_SOCKADDR_STORAGE = YesPlease + NO_FNMATCH_CASEFOLD = YesPlease + endif +endif +ifeq ($(uname_S),Minix) + NO_IPV6 = YesPlease + NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease + NO_NSEC = YesPlease + NEEDS_LIBGEN = + NEEDS_CRYPTO_WITH_SSL = YesPlease + NEEDS_IDN_WITH_CURL = YesPlease + NEEDS_SSL_WITH_CURL = YesPlease + NEEDS_RESOLV = + NO_HSTRERROR = YesPlease + NO_MMAP = YesPlease + NO_CURL = + NO_EXPAT = +endif +ifeq ($(uname_S),NONSTOP_KERNEL) + # Needs some C99 features, "inline" is just one of them. + # INLINE='' would just replace one set of warnings with another and + # still not compile in c89 mode, due to non-const array initializations. + CC = cc -c99 + # Disable all optimization, seems to result in bad code, with -O or -O2 + # or even -O1 (default), /usr/local/libexec/git-core/git-pack-objects + # abends on "git push". Needs more investigation. + CFLAGS = -g -O0 + # We'd want it to be here. + prefix = /usr/local + # Our's are in ${prefix}/bin (perl might also be in /usr/bin/perl). + PERL_PATH = ${prefix}/bin/perl + PYTHON_PATH = ${prefix}/bin/python + + # As detected by './configure'. + # Missdetected, hence commented out, see below. + #NO_CURL = YesPlease + # Added manually, see above. + NEEDS_SSL_WITH_CURL = YesPlease + HAVE_LIBCHARSET_H = YesPlease + HAVE_STRINGS_H = YesPlease + NEEDS_LIBICONV = YesPlease + NEEDS_LIBINTL_BEFORE_LIBICONV = YesPlease + NO_SYS_SELECT_H = UnfortunatelyYes + NO_D_TYPE_IN_DIRENT = YesPlease + NO_HSTRERROR = YesPlease + NO_STRCASESTR = YesPlease + NO_FNMATCH_CASEFOLD = YesPlease + NO_MEMMEM = YesPlease + NO_STRLCPY = YesPlease + NO_SETENV = YesPlease + NO_UNSETENV = YesPlease + NO_MKDTEMP = YesPlease + NO_MKSTEMPS = YesPlease + # Currently libiconv-1.9.1. + OLD_ICONV = UnfortunatelyYes + NO_REGEX = YesPlease + NO_PTHREADS = UnfortunatelyYes + + # Not detected (nor checked for) by './configure'. + # We don't have SA_RESTART on NonStop, unfortunalety. + COMPAT_CFLAGS += -DSA_RESTART=0 + # Apparently needed in compat/fnmatch/fnmatch.c. + COMPAT_CFLAGS += -DHAVE_STRING_H=1 + NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease + NO_NSEC = YesPlease + NO_PREAD = YesPlease + NO_MMAP = YesPlease + NO_POLL = YesPlease + NO_INTPTR_T = UnfortunatelyYes + # Bug report 10-120822-4477 submitted to HP NonStop development. + 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 = ; + NO_PREAD = YesPlease + NEEDS_CRYPTO_WITH_SSL = YesPlease + NO_LIBGEN_H = YesPlease + NO_POLL = YesPlease + NO_SYMLINK_HEAD = YesPlease + NO_UNIX_SOCKETS = YesPlease + NO_SETENV = YesPlease + NO_UNSETENV = YesPlease + NO_STRCASESTR = YesPlease + NO_STRLCPY = YesPlease + NO_STRTOK_R = YesPlease + NO_FNMATCH = YesPlease + NO_MEMMEM = YesPlease + NEEDS_LIBICONV = YesPlease + OLD_ICONV = YesPlease + NO_STRTOUMAX = YesPlease + NO_MKDTEMP = YesPlease + NO_MKSTEMPS = YesPlease + NO_SVN_TESTS = YesPlease + NO_PERL_MAKEMAKER = YesPlease + RUNTIME_PREFIX = YesPlease + NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease + NO_NSEC = YesPlease + USE_WIN32_MMAP = YesPlease + USE_NED_ALLOCATOR = YesPlease + UNRELIABLE_FSTAT = UnfortunatelyYes + OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo + NO_REGEX = YesPlease + NO_PYTHON = YesPlease + BLK_SHA1 = YesPlease + ETAGS_TARGET = ETAGS + NO_INET_PTON = YesPlease + NO_INET_NTOP = YesPlease + NO_POSIX_GOODIES = UnfortunatelyYes + COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/win32 + COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\" + COMPAT_OBJS += compat/mingw.o compat/winansi.o \ + compat/win32/pthread.o compat/win32/syslog.o \ + compat/win32/dirent.o + EXTLIBS += -lws2_32 + PTHREAD_LIBS = + X = .exe + SPARSE_FLAGS = -Wno-one-bit-signed-bitfield +ifneq (,$(wildcard ../THIS_IS_MSYSGIT)) + htmldir = doc/git/html/ + prefix = + INSTALL = /bin/install + EXTLIBS += /mingw/lib/libz.a + NO_R_TO_GCC_LINKER = YesPlease + INTERNAL_QSORT = YesPlease + HAVE_LIBCHARSET_H = YesPlease +else + NO_CURL = YesPlease +endif +endif +ifeq ($(uname_S),QNX) + COMPAT_CFLAGS += -DSA_RESTART=0 + HAVE_STRINGS_H = YesPlease + NEEDS_SOCKET = YesPlease + NO_FNMATCH_CASEFOLD = YesPlease + NO_GETPAGESIZE = YesPlease + NO_ICONV = YesPlease + NO_MEMMEM = YesPlease + NO_MKDTEMP = YesPlease + NO_MKSTEMPS = YesPlease + NO_NSEC = YesPlease + NO_PTHREADS = YesPlease + NO_R_TO_GCC_LINKER = YesPlease + NO_STRCASESTR = YesPlease + NO_STRLCPY = YesPlease +endif diff --git a/configure.ac b/configure.ac index c85888c851..1991258ae0 100644 --- a/configure.ac +++ b/configure.ac @@ -753,6 +753,14 @@ AC_CHECK_MEMBER(struct dirent.d_type, [#include ]) GIT_CONF_SUBST([NO_D_TYPE_IN_DIRENT]) # +# Define NO_GECOS_IN_PWENT if you don't have pw_gecos in struct passwd +# in the C library. +AC_CHECK_MEMBER(struct passwd.pw_gecos, +[NO_GECOS_IN_PWENT=], +[NO_GECOS_IN_PWENT=YesPlease], +[#include ]) +GIT_CONF_SUBST([NO_GECOS_IN_PWENT]) +# # Define NO_SOCKADDR_STORAGE if your platform does not have struct # sockaddr_storage. AC_CHECK_TYPE(struct sockaddr_storage, @@ -872,6 +880,12 @@ AC_CHECK_HEADER([libcharset.h], [HAVE_LIBCHARSET_H=YesPlease], [HAVE_LIBCHARSET_H=]) GIT_CONF_SUBST([HAVE_LIBCHARSET_H]) +# +# Define HAVE_STRINGS_H if you have strings.h +AC_CHECK_HEADER([strings.h], +[HAVE_STRINGS_H=YesPlease], +[HAVE_STRINGS_H=]) +GIT_CONF_SUBST([HAVE_STRINGS_H]) # Define CHARSET_LIB if libiconv does not export the locale_charset symbol # and libcharset does CHARSET_LIB= @@ -1021,10 +1035,20 @@ if test -n "$USER_NOPTHREAD"; then # -D_REENTRANT' or some such. elif test -z "$PTHREAD_CFLAGS"; then threads_found=no - for opt in -mt -pthread -lpthread; do + # Attempt to compile and link some code using pthreads to determine + # required linker flags. The order is somewhat important here: We + # first try it without any extra flags, to catch systems where + # pthreads are part of the C library, then go on testing various other + # flags. We do so to avoid false positives. For example, on Mac OS X + # pthreads are part of the C library; moreover, the compiler allows us + # to add "-mt" to the CFLAGS (although it will do nothing except + # trigger a warning about an unused flag). Hence if we checked for + # "-mt" before "" we would end up picking it. But unfortunately this + # would then trigger compiler warnings on every single file we compile. + 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 +1068,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/ciabot/ciabot.py b/contrib/ciabot/ciabot.py index bd24395d4c..36b5665ff8 100755 --- a/contrib/ciabot/ciabot.py +++ b/contrib/ciabot/ciabot.py @@ -47,7 +47,13 @@ # we default to that. # -import os, sys, commands, socket, urllib +import sys +if sys.hexversion < 0x02000000: + # The limiter is the xml.sax module + sys.stderr.write("ciabot.py: requires Python 2.0.0 or later.\n") + sys.exit(1) + +import os, commands, socket, urllib from xml.sax.saxutils import escape # Changeset URL prefix for your repo: when the commit ID is appended diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 48c3abdf98..7147d64af2 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 @@ -428,7 +397,7 @@ __git_complete_revlist_file () *) pfx="$ref:$pfx" ;; esac - __gitcomp_nl "$(git --git-dir="$(__gitdir)" ls-tree "$ls" \ + __gitcomp_nl "$(git --git-dir="$(__gitdir)" ls-tree "$ls" 2>/dev/null \ | sed '/^100... blob /{ s,^.* ,, s,$, , @@ -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;; @@ -594,6 +563,7 @@ __git_list_porcelain_commands () archimport) : import;; cat-file) : plumbing;; check-attr) : plumbing;; + check-ignore) : plumbing;; check-ref-format) : plumbing;; checkout-index) : plumbing;; commit-tree) : plumbing;; @@ -989,6 +959,8 @@ _git_clone () --upload-pack --template= --depth + --single-branch + --branch " return ;; @@ -1000,6 +972,13 @@ _git_commit () { __git_has_doubledash && return + case "$prev" in + -c|-C) + __gitcomp_nl "$(__git_refs)" "" "${cur}" + return + ;; + esac + case "$cur" in --cleanup=*) __gitcomp "default strip verbatim whitespace @@ -1116,6 +1095,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 +1113,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 +1527,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 +1542,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 +2409,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..3e3889f2b4 --- /dev/null +++ b/contrib/completion/git-completion.tcsh @@ -0,0 +1,116 @@ +#!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: +# +# 0) You need tcsh 6.16.00 or newer. +# 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 +# 3) For completion similar to bash, it is recommended to also +# add the following line to your .tcshrc/.cshrc: +# set autolist=ambiguous +# It will tell tcsh to list the possible completion choices. + +set __git_tcsh_completion_version = `\echo ${tcsh} | \sed 's/\./ /g'` +if ( ${__git_tcsh_completion_version[1]} < 6 || \ + ( ${__git_tcsh_completion_version[1]} == 6 && \ + ${__git_tcsh_completion_version[2]} < 16 ) ) then + echo "git-completion.tcsh: Your version of tcsh is too old, you need version 6.16.00 or newer. Git completion will not work." + exit +endif +unset __git_tcsh_completion_version + +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 git-completion.tcsh +# and source it again. + +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' +if [ \${#COMPREPLY[*]} -eq 0 ]; then + # No completions suggested. In this case, we want tcsh to perform + # standard file completion. However, there does not seem to be way + # to tell tcsh to do that. To help the user, we try to simulate + # file completion directly in this script. + # + # Known issues: + # - Possible completions are shown with their directory prefix. + # - Completions containing shell variables are not handled. + # - Completions with ~ as the first character are not handled. + + # No file completion should be done unless we are completing beyond + # the git sub-command. An improvement on the bash completion :) + if [ \${COMP_CWORD} -gt 1 ]; then + TO_COMPLETE="\${COMP_WORDS[\${COMP_CWORD}]}" + + # We don't support ~ expansion: too tricky. + if [ "\${TO_COMPLETE:0:1}" != "~" ]; then + # Use ls so as to add the '/' at the end of directories. + COMPREPLY=(\`ls -dp \${TO_COMPLETE}* 2> /dev/null\`) + fi + fi +fi + +# tcsh does not automatically remove duplicates, so we do it ourselves +echo "\${COMPREPLY[*]}" | sort | uniq + +# If there is a single completion and it is a directory, we output it +# a second time to trick tcsh into not adding a space after it. +if [ \${#COMPREPLY[*]} -eq 1 ] && [ "\${COMPREPLY[0]: -1}" == "/" ]; then + echo "\${COMPREPLY[*]}" +fi + +EOF + +# Don't need this variable anymore, so don't pollute the users environment +unset __git_tcsh_completion_original_script + +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..9bef0531c5 100644 --- a/contrib/completion/git-prompt.sh +++ b/contrib/completion/git-prompt.sh @@ -10,9 +10,22 @@ # 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) 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. +# 3b) Alternatively, if you are using bash, __git_ps1 can be +# used for PROMPT_COMMAND with two parameters,
 and
+#        , which are strings you would put in $PS1 before
+#        and after the status string generated by the git-prompt
+#        machinery.  e.g.
+#           PROMPT_COMMAND='__git_ps1 "\u@\h:\w" "\\\$ "'
+#        will show username, at-sign, host, colon, cwd, then
+#        various status string, followed by dollar and SP, as
+#        your prompt.
+#        Optionally, you can supply a third argument with a printf
+#        format string to finetune the output of the branch status
 #
 # 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,6 +62,19 @@
 # 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 to see more information about the identity of
+# commits checked out as a detached HEAD, set GIT_PS1_DESCRIBE_STYLE
+# to one of these values:
+#
+#     contains      relative to newer annotated tag (v1.6.3.2~35)
+#     branch        relative to newer tag or branch (master~4)
+#     describe      relative to older annotated tag (v1.6.3.1-13-gdd42c2f)
+#     default       exactly matching tag
+#
+# 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
@@ -195,11 +221,43 @@ __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 or 3 arguments when called from PROMPT_COMMAND (pc)
+# in that case it _sets_ PS1. The arguments are parts of a PS1 string.
+# when two arguments are given, the first is prepended and the second appended
+# to the state string when assigned to PS1.
+# The optional third parameter will be used as printf format string to further
+# customize the output of the git-status string.
+# 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|3)	pcmode=yes
+			ps1pc_start="$1"
+			ps1pc_end="$2"
+			printf_format="${3:-$printf_format}"
+		;;
+		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 +284,7 @@ __git_ps1 ()
 			fi
 
 			b="$(git symbolic-ref HEAD 2>/dev/null)" || {
-
+				detached=yes
 				b="$(
 				case "${GIT_PS1_DESCRIBE_STYLE-}" in
 				(contains)
@@ -285,6 +343,53 @@ __git_ps1 ()
 		fi
 
 		local f="$w$i$s$u"
-		printf -- "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r$p"
+		if [ $pcmode = yes ]; then
+			local gitstring=
+			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 gitstring directly with \[ and \] around colors
+				# is necessary to prevent wrapping issues!
+				gitstring="\[$branch_color\]$branchstring\[$c_clear\]"
+
+				if [ -n "$w$i$s$u$r$p" ]; then
+					gitstring="$gitstring "
+				fi
+				if [ "$w" = "*" ]; then
+					gitstring="$gitstring\[$bad_color\]$w"
+				fi
+				if [ -n "$i" ]; then
+					gitstring="$gitstring\[$ok_color\]$i"
+				fi
+				if [ -n "$s" ]; then
+					gitstring="$gitstring\[$flags_color\]$s"
+				fi
+				if [ -n "$u" ]; then
+					gitstring="$gitstring\[$bad_color\]$u"
+				fi
+				gitstring="$gitstring\[$c_clear\]$r$p"
+			else
+				gitstring="$c${b##refs/heads/}${f:+ $f}$r$p"
+			fi
+			gitstring=$(printf -- "$printf_format" "$gitstring")
+			PS1="$ps1pc_start$gitstring$ps1pc_end"
+		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/fast-import/import-zips.py b/contrib/fast-import/import-zips.py
index 82f5ed3ddc..5cec9b0129 100755
--- a/contrib/fast-import/import-zips.py
+++ b/contrib/fast-import/import-zips.py
@@ -9,10 +9,15 @@
 ##  git log --stat import-zips
 
 from os import popen, path
-from sys import argv, exit
+from sys import argv, exit, hexversion, stderr
 from time import mktime
 from zipfile import ZipFile
 
+if hexversion < 0x01060000:
+        # The limiter is the zipfile module
+        sys.stderr.write("import-zips.py: requires Python 1.6.0 or later.\n")
+        sys.exit(1)
+
 if len(argv) < 2:
 	print 'Usage:', argv[0], '...'
 	exit(1)
diff --git a/contrib/hg-to-git/hg-to-git.py b/contrib/hg-to-git/hg-to-git.py
index 046cb2b268..232625a7b7 100755
--- a/contrib/hg-to-git/hg-to-git.py
+++ b/contrib/hg-to-git/hg-to-git.py
@@ -23,6 +23,11 @@
 import tempfile, pickle, getopt
 import re
 
+if sys.hexversion < 0x02030000:
+   # The behavior of the pickle module changed significantly in 2.3
+   sys.stderr.write("hg-to-git.py: requires Python 2.3 or later.\n")
+   sys.exit(1)
+
 # Maps hg version -> git version
 hgvers = {}
 # List of children for each hg revision
diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email
index b2171a092e..0e5b72d7f1 100755
--- a/contrib/hooks/post-receive-email
+++ b/contrib/hooks/post-receive-email
@@ -237,6 +237,7 @@ generate_email_header()
 	X-Git-Reftype: $refname_type
 	X-Git-Oldrev: $oldrev
 	X-Git-Newrev: $newrev
+	Auto-Submitted: auto-generated
 
 	This is an automated email from the git hooks/post-receive script. It was
 	generated because a ref change was pushed to the repository containing
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/p4import/git-p4import.py b/contrib/p4import/git-p4import.py
index b6e534b65b..593d6a0682 100644
--- a/contrib/p4import/git-p4import.py
+++ b/contrib/p4import/git-p4import.py
@@ -14,6 +14,11 @@
 import time
 import getopt
 
+if sys.hexversion < 0x02020000:
+   # The behavior of the marshal module changed significantly in 2.2
+   sys.stderr.write("git-p4import.py: requires Python 2.2 or later.\n")
+   sys.exit(1)
+
 from signal import signal, \
    SIGPIPE, SIGINT, SIG_DFL, \
    default_int_handler
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-bzr b/contrib/remote-helpers/git-remote-bzr
new file mode 100755
index 0000000000..c5822e4ac9
--- /dev/null
+++ b/contrib/remote-helpers/git-remote-bzr
@@ -0,0 +1,725 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2012 Felipe Contreras
+#
+
+#
+# Just copy to your ~/bin, or anywhere in your $PATH.
+# Then you can clone with:
+# % git clone bzr::/path/to/bzr/repo/or/url
+#
+# For example:
+# % git clone bzr::$HOME/myrepo
+# or
+# % git clone bzr::lp:myrepo
+#
+
+import sys
+
+import bzrlib
+if hasattr(bzrlib, "initialize"):
+    bzrlib.initialize()
+
+import bzrlib.plugin
+bzrlib.plugin.load_plugins()
+
+import bzrlib.generate_ids
+import bzrlib.transport
+
+import sys
+import os
+import json
+import re
+import StringIO
+
+NAME_RE = re.compile('^([^<>]+)')
+AUTHOR_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 gittz(tz):
+    return '%+03d%02d' % (tz / 3600, tz % 3600 / 60)
+
+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] = 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[rev]
+
+    def to_rev(self, mark):
+        return self.rev_marks[mark]
+
+    def next_mark(self):
+        self.last_mark += 1
+        return self.last_mark
+
+    def get_mark(self, rev):
+        self.last_mark += 1
+        self.marks[rev] = self.last_mark
+        return self.last_mark
+
+    def is_marked(self, rev):
+        return self.marks.has_key(rev)
+
+    def new_mark(self, rev, mark):
+        self.marks[rev] = mark
+        self.rev_marks[mark] = rev
+        self.last_mark = mark
+
+    def get_tip(self, branch):
+        return self.tips.get(branch, None)
+
+    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):
+        m = RAW_AUTHOR_RE.match(self.line)
+        if not m:
+            return None
+        _, name, email, date, tz = m.groups()
+        committer = '%s <%s>' % (name, email)
+        tz = int(tz)
+        tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
+        return (committer, int(date), tz)
+
+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 fixup_user(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 '%s <%s>' % (name, mail)
+
+def get_filechanges(cur, prev):
+    modified = {}
+    removed = {}
+
+    changes = cur.changes_from(prev)
+
+    for path, fid, kind in changes.added:
+        modified[path] = fid
+    for path, fid, kind in changes.removed:
+        removed[path] = None
+    for path, fid, kind, mod, _ in changes.modified:
+        modified[path] = fid
+    for oldpath, newpath, fid, kind, mod, _ in changes.renamed:
+        removed[oldpath] = None
+        modified[newpath] = fid
+
+    return modified, removed
+
+def export_files(tree, files):
+    global marks, filenodes
+
+    final = []
+    for path, fid in files.iteritems():
+        kind = tree.kind(fid)
+
+        h = tree.get_file_sha1(fid)
+
+        if kind == 'symlink':
+            d = tree.get_symlink_target(fid)
+            mode = '120000'
+        elif kind == 'file':
+
+            if tree.is_executable(fid):
+                mode = '100755'
+            else:
+                mode = '100644'
+
+            # is the blog already exported?
+            if h in filenodes:
+                mark = filenodes[h]
+                final.append((mode, mark, path))
+                continue
+
+            d = tree.get_file_text(fid)
+        elif kind == 'directory':
+            continue
+        else:
+            die("Unhandled kind '%s' for path '%s'" % (kind, path))
+
+        mark = marks.next_mark()
+        filenodes[h] = mark
+
+        print "blob"
+        print "mark :%u" % mark
+        print "data %d" % len(d)
+        print d
+
+        final.append((mode, mark, path))
+
+    return final
+
+def export_branch(branch, name):
+    global prefix, dirname
+
+    ref = '%s/heads/%s' % (prefix, name)
+    tip = marks.get_tip(name)
+
+    repo = branch.repository
+    repo.lock_read()
+    revs = branch.iter_merge_sorted_revisions(None, tip, 'exclude', 'forward')
+    count = 0
+
+    revs = [revid for revid, _, _, _ in revs if not marks.is_marked(revid)]
+
+    for revid in revs:
+
+        rev = repo.get_revision(revid)
+
+        parents = rev.parent_ids
+        time = rev.timestamp
+        tz = rev.timezone
+        committer = rev.committer.encode('utf-8')
+        committer = "%s %u %s" % (fixup_user(committer), time, gittz(tz))
+        author = committer
+        msg = rev.message.encode('utf-8')
+
+        msg += '\n'
+
+        if len(parents) == 0:
+            parent = bzrlib.revision.NULL_REVISION
+        else:
+            parent = parents[0]
+
+        cur_tree = repo.revision_tree(revid)
+        prev = repo.revision_tree(parent)
+        modified, removed = get_filechanges(cur_tree, prev)
+
+        modified_final = export_files(cur_tree, modified)
+
+        if len(parents) == 0:
+            print 'reset %s' % ref
+
+        print "commit %s" % ref
+        print "mark :%d" % (marks.get_mark(revid))
+        print "author %s" % (author)
+        print "committer %s" % (committer)
+        print "data %d" % (len(msg))
+        print msg
+
+        for i, p in enumerate(parents):
+            try:
+                m = rev_to_mark(p)
+            except KeyError:
+                # ghost?
+                continue
+            if i == 0:
+                print "from :%s" % m
+            else:
+                print "merge :%s" % m
+
+        for f in modified_final:
+            print "M %s :%u %s" % f
+        for f in removed:
+            print "D %s" % (f)
+        print
+
+        count += 1
+        if (count % 100 == 0):
+            print "progress revision %s (%d/%d)" % (revid, count, len(revs))
+            print "#############################################################"
+
+    repo.unlock()
+
+    revid = branch.last_revision()
+
+    # make sure the ref is updated
+    print "reset %s" % ref
+    print "from :%u" % rev_to_mark(revid)
+    print
+
+    marks.set_tip(name, revid)
+
+def export_tag(repo, name):
+    global tags
+    try:
+        print "reset refs/tags/%s" % name
+        print "from :%u" % rev_to_mark(tags[name])
+        print
+    except KeyError:
+        warn("TODO: fetch tag '%s'" % name)
+
+def do_import(parser):
+    global dirname
+
+    branch = 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()
+
+    while parser.check('import'):
+        ref = parser[1]
+        if ref.startswith('refs/heads/'):
+            name = ref[len('refs/heads/'):]
+            export_branch(branch, name)
+        if ref.startswith('refs/tags/'):
+            name = ref[len('refs/tags/'):]
+            export_tag(branch, name)
+        parser.next()
+
+    print 'done'
+
+    sys.stdout.flush()
+
+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()
+
+class CustomTree():
+
+    def __init__(self, repo, revid, parents, files):
+        global files_cache
+
+        self.repo = repo
+        self.revid = revid
+        self.parents = parents
+        self.updates = {}
+
+        def copy_tree(revid):
+            files = files_cache[revid] = {}
+            tree = repo.repository.revision_tree(revid)
+            repo.lock_read()
+            try:
+                for path, entry in tree.iter_entries_by_dir():
+                    files[path] = entry.file_id
+            finally:
+                repo.unlock()
+            return files
+
+        if len(parents) == 0:
+            self.base_id = bzrlib.revision.NULL_REVISION
+            self.base_files = {}
+        else:
+            self.base_id = parents[0]
+            self.base_files = files_cache.get(self.base_id, None)
+            if not self.base_files:
+                self.base_files = copy_tree(self.base_id)
+
+        self.files = files_cache[revid] = self.base_files.copy()
+
+        for path, f in files.iteritems():
+            fid = self.files.get(path, None)
+            if not fid:
+                fid = bzrlib.generate_ids.gen_file_id(path)
+            f['path'] = path
+            self.updates[fid] = f
+
+    def last_revision(self):
+        return self.base_id
+
+    def iter_changes(self):
+        changes = []
+
+        def get_parent(dirname, basename):
+            parent_fid = self.base_files.get(dirname, None)
+            if parent_fid:
+                return parent_fid
+            parent_fid = self.files.get(dirname, None)
+            if parent_fid:
+                return parent_fid
+            if basename == '':
+                return None
+            fid = bzrlib.generate_ids.gen_file_id(path)
+            d = add_entry(fid, dirname, 'directory')
+            return fid
+
+        def add_entry(fid, path, kind, mode = None):
+            dirname, basename = os.path.split(path)
+            parent_fid = get_parent(dirname, basename)
+
+            executable = False
+            if mode == '100755':
+                executable = True
+            elif mode == '120000':
+                kind = 'symlink'
+
+            change = (fid,
+                    (None, path),
+                    True,
+                    (False, True),
+                    (None, parent_fid),
+                    (None, basename),
+                    (None, kind),
+                    (None, executable))
+            self.files[path] = change[0]
+            changes.append(change)
+            return change
+
+        def update_entry(fid, path, kind, mode = None):
+            dirname, basename = os.path.split(path)
+            parent_fid = get_parent(dirname, basename)
+
+            executable = False
+            if mode == '100755':
+                executable = True
+            elif mode == '120000':
+                kind = 'symlink'
+
+            change = (fid,
+                    (path, path),
+                    True,
+                    (True, True),
+                    (None, parent_fid),
+                    (None, basename),
+                    (None, kind),
+                    (None, executable))
+            self.files[path] = change[0]
+            changes.append(change)
+            return change
+
+        def remove_entry(fid, path, kind):
+            dirname, basename = os.path.split(path)
+            parent_fid = get_parent(dirname, basename)
+            change = (fid,
+                    (path, None),
+                    True,
+                    (True, False),
+                    (parent_fid, None),
+                    (None, None),
+                    (None, None),
+                    (None, None))
+            del self.files[path]
+            changes.append(change)
+            return change
+
+        for fid, f in self.updates.iteritems():
+            path = f['path']
+
+            if 'deleted' in f:
+                remove_entry(fid, path, 'file')
+                continue
+
+            if path in self.base_files:
+                update_entry(fid, path, 'file', f['mode'])
+            else:
+                add_entry(fid, path, 'file', f['mode'])
+
+        return changes
+
+    def get_file_with_stat(self, file_id, path=None):
+        return (StringIO.StringIO(self.updates[file_id]['data']), None)
+
+    def get_symlink_target(self, file_id):
+        return self.updates[file_id]['data']
+
+def parse_commit(parser):
+    global marks, blob_marks, bmarks, parsed_refs
+    global mode
+
+    parents = []
+
+    ref = parser[1]
+    parser.next()
+
+    if ref != 'refs/heads/master':
+        die("bzr doesn't support multiple branches; use 'master'")
+
+    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'):
+        parents.append(parser.get_mark())
+        parser.next()
+    while parser.check('merge'):
+        parents.append(parser.get_mark())
+        parser.next()
+
+    files = {}
+
+    for line in parser:
+        if parser.check('M'):
+            t, m, mark_ref, path = line.split(' ', 3)
+            mark = int(mark_ref[1:])
+            f = { 'mode' : 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
+
+    repo = parser.repo
+
+    committer, date, tz = committer
+    parents = [str(mark_to_rev(p)) for p in parents]
+    revid = bzrlib.generate_ids.gen_revision_id(committer, date)
+    props = {}
+    props['branch-nick'] = repo.nick
+
+    mtree = CustomTree(repo, revid, parents, files)
+    changes = mtree.iter_changes()
+
+    repo.lock_write()
+    try:
+        builder = repo.get_commit_builder(parents, None, date, tz, committer, props, revid)
+        try:
+            list(builder.record_iter_changes(mtree, mtree.last_revision(), changes))
+            builder.finish_inventory()
+            builder.commit(data.decode('utf-8', 'replace'))
+        except Exception, e:
+            builder.abort()
+            raise
+    finally:
+        repo.unlock()
+
+    parsed_refs[ref] = revid
+    marks.new_mark(revid, commit_mark)
+
+def parse_reset(parser):
+    global parsed_refs
+
+    ref = parser[1]
+    parser.next()
+
+    if ref != 'refs/heads/master':
+        die("bzr doesn't support multiple branches; use 'master'")
+
+    # ugh
+    if parser.check('commit'):
+        parse_commit(parser)
+        return
+    if not parser.check('from'):
+        return
+    from_mark = parser.get_mark()
+    parser.next()
+
+    parsed_refs[ref] = mark_to_rev(from_mark)
+
+def do_export(parser):
+    global parsed_refs, dirname, 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'):
+            pass
+        elif parser.check('feature'):
+            pass
+        else:
+            die('unhandled export command: %s' % line)
+
+    repo = parser.repo
+
+    for ref, revid in parsed_refs.iteritems():
+        if ref == 'refs/heads/master':
+            repo.generate_revision_history(revid, marks.get_tip('master'))
+            revno, revid = repo.last_revision_info()
+            if peer:
+                if hasattr(peer, "import_last_revision_info_and_tags"):
+                    peer.import_last_revision_info_and_tags(repo, revno, revid)
+                else:
+                    peer.import_last_revision_info(repo.repository, revno, revid)
+                wt = peer.bzrdir.open_workingtree()
+            else:
+                wt = repo.bzrdir.open_workingtree()
+            wt.update()
+        print "ok %s" % ref
+    print
+
+def do_capabilities(parser):
+    global dirname
+
+    print "import"
+    print "export"
+    print "refspec refs/heads/*:%s/heads/*" % 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 do_list(parser):
+    global tags
+    print "? refs/heads/%s" % 'master'
+    for tag, revid in parser.repo.tags.get_tag_dict().items():
+        print "? refs/tags/%s" % tag
+        tags[tag] = revid
+    print "@refs/heads/%s HEAD" % 'master'
+    print
+
+def get_repo(url, alias):
+    global dirname, peer
+
+    origin = bzrlib.bzrdir.BzrDir.open(url)
+    branch = origin.open_branch()
+
+    if not isinstance(origin.transport, bzrlib.transport.local.LocalTransport):
+        clone_path = os.path.join(dirname, 'clone')
+        remote_branch = branch
+        if os.path.exists(clone_path):
+            # pull
+            d = bzrlib.bzrdir.BzrDir.open(clone_path)
+            branch = d.open_branch()
+            result = branch.pull(remote_branch, [], None, False)
+        else:
+            # clone
+            d = origin.sprout(clone_path, None,
+                    hardlink=True, create_tree_if_local=False,
+                    source_branch=remote_branch)
+            branch = d.open_branch()
+            branch.bind(remote_branch)
+
+        peer = remote_branch
+    else:
+        peer = None
+
+    return branch
+
+def main(args):
+    global marks, prefix, dirname
+    global tags, filenodes
+    global blob_marks
+    global parsed_refs
+    global files_cache
+
+    alias = args[1]
+    url = args[2]
+
+    prefix = 'refs/bzr/%s' % alias
+    tags = {}
+    filenodes = {}
+    blob_marks = {}
+    parsed_refs = {}
+    files_cache = {}
+
+    gitdir = os.environ['GIT_DIR']
+    dirname = os.path.join(gitdir, 'bzr', alias)
+
+    if not os.path.exists(dirname):
+        os.makedirs(dirname)
+
+    repo = get_repo(url, alias)
+
+    marks_path = os.path.join(dirname, 'marks-int')
+    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()
+
+    marks.store()
+
+sys.exit(main(sys.argv))
diff --git a/contrib/remote-helpers/git-remote-hg b/contrib/remote-helpers/git-remote-hg
new file mode 100755
index 0000000000..328c2dc76d
--- /dev/null
+++ b/contrib/remote-helpers/git-remote-hg
@@ -0,0 +1,805 @@
+#!/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 bidirectionality.
+#
+
+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 = { '100755': 'x', '120000': '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 fix_path(alias, repo, orig_url):
+    repo_url = util.url(repo.url())
+    url = util.url(orig_url)
+    if str(url) == str(repo_url):
+        return
+    cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % repo_url]
+    subprocess.call(cmd)
+
+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 is_tmp:
+        fix_path(alias, peer or repo, url)
+
+    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-bzr.sh b/contrib/remote-helpers/test-bzr.sh
new file mode 100755
index 0000000000..70aa8a010a
--- /dev/null
+++ b/contrib/remote-helpers/test-bzr.sh
@@ -0,0 +1,143 @@
+#!/bin/sh
+#
+# Copyright (c) 2012 Felipe Contreras
+#
+
+test_description='Test remote-bzr'
+
+. ./test-lib.sh
+
+if ! test_have_prereq PYTHON; then
+	skip_all='skipping remote-bzr tests; python not available'
+	test_done
+fi
+
+if ! "$PYTHON_PATH" -c 'import bzrlib'; then
+	skip_all='skipping remote-bzr tests; bzr not available'
+	test_done
+fi
+
+cmd='
+import bzrlib
+bzrlib.initialize()
+import bzrlib.plugin
+bzrlib.plugin.load_plugins()
+import bzrlib.plugins.fastimport
+'
+
+if ! "$PYTHON_PATH" -c "$cmd"; then
+	echo "consider setting BZR_PLUGIN_PATH=$HOME/.bazaar/plugins" 1>&2
+	skip_all='skipping remote-bzr tests; bzr-fastimport not available'
+	test_done
+fi
+
+check () {
+	(cd $1 &&
+	git log --format='%s' -1 &&
+	git symbolic-ref HEAD) > actual &&
+	(echo $2 &&
+	echo "refs/heads/$3") > expected &&
+	test_cmp expected actual
+}
+
+bzr whoami "A U Thor "
+
+test_expect_success 'cloning' '
+  (bzr init bzrrepo &&
+  cd bzrrepo &&
+  echo one > content &&
+  bzr add content &&
+  bzr commit -m one
+  ) &&
+
+  git clone "bzr::$PWD/bzrrepo" gitrepo &&
+  check gitrepo one master
+'
+
+test_expect_success 'pulling' '
+  (cd bzrrepo &&
+  echo two > content &&
+  bzr commit -m two
+  ) &&
+
+  (cd gitrepo && git pull) &&
+
+  check gitrepo two master
+'
+
+test_expect_success 'pushing' '
+  (cd gitrepo &&
+  echo three > content &&
+  git commit -a -m three &&
+  git push
+  ) &&
+
+  echo three > expected &&
+  cat bzrrepo/content > actual &&
+  test_cmp expected actual
+'
+
+test_expect_success 'roundtrip' '
+  (cd gitrepo &&
+  git pull &&
+  git log --format="%s" -1 origin/master > actual) &&
+  echo three > expected &&
+  test_cmp expected actual &&
+
+  (cd gitrepo && git push && git pull) &&
+
+  (cd bzrrepo &&
+  echo four > content &&
+  bzr commit -m four
+  ) &&
+
+  (cd gitrepo && git pull && git push) &&
+
+  check gitrepo four master &&
+
+  (cd gitrepo &&
+  echo five > content &&
+  git commit -a -m five &&
+  git push && git pull
+  ) &&
+
+  (cd bzrrepo && bzr revert) &&
+
+  echo five > expected &&
+  cat bzrrepo/content > actual &&
+  test_cmp expected actual
+'
+
+cat > expected < executable
+  chmod +x executable &&
+  bzr add executable
+  bzr commit -m exec &&
+  ln -s content link
+  bzr add link
+  bzr commit -m link &&
+  mkdir dir &&
+  bzr add dir &&
+  bzr commit -m dir) &&
+
+  (cd gitrepo &&
+  git pull
+  git ls-tree HEAD > ../actual) &&
+
+  test_cmp expected actual &&
+
+  (cd gitrepo &&
+  git cat-file -p HEAD:link > ../actual) &&
+
+  echo -n content > expected &&
+  test_cmp expected actual
+'
+
+test_done
diff --git a/contrib/remote-helpers/test-hg-bidi.sh b/contrib/remote-helpers/test-hg-bidi.sh
new file mode 100755
index 0000000000..1d61982436
--- /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 bidirectionality 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..7e3967f5b6
--- /dev/null
+++ b/contrib/remote-helpers/test-hg-hg-git.sh
@@ -0,0 +1,534 @@
+#!/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 'executable bit' '
+	mkdir -p tmp && cd tmp &&
+	test_when_finished "cd .. && rm -rf tmp" &&
+
+	(
+	git init -q gitrepo &&
+	cd gitrepo &&
+	echo alpha > alpha &&
+	chmod 0644 alpha &&
+	git add alpha &&
+	git commit -m "add alpha" &&
+	chmod 0755 alpha &&
+	git add alpha &&
+	git commit -m "set executable bit" &&
+	chmod 0644 alpha &&
+	git add alpha &&
+	git commit -m "clear executable bit"
+	) &&
+
+	for x in hg git; do
+		(
+		hg_clone_$x gitrepo hgrepo-$x &&
+		cd hgrepo-$x &&
+		hg_log . &&
+		hg manifest -r 1 -v &&
+		hg manifest -v
+		) > output-$x &&
+
+		git_clone_$x hgrepo-$x gitrepo2-$x &&
+		git_log gitrepo2-$x > log-$x
+	done &&
+	cp -r log-* output-* /tmp/foo/ &&
+
+	test_cmp output-hg output-git &&
+	test_cmp log-hg log-git
+'
+
+test_expect_success 'symlink' '
+	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" &&
+	ln -s alpha beta &&
+	git add beta &&
+	git commit -m "add beta"
+	) &&
+
+	for x in hg git; do
+		(
+		hg_clone_$x gitrepo hgrepo-$x &&
+		cd hgrepo-$x &&
+		hg_log . &&
+		hg manifest -v
+		) > 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 '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/stats/mailmap.pl b/contrib/stats/mailmap.pl
index 4b852e2455..9513f5e35b 100755
--- a/contrib/stats/mailmap.pl
+++ b/contrib/stats/mailmap.pl
@@ -1,38 +1,70 @@
-#!/usr/bin/perl -w
-my %mailmap = ();
-open I, "<", ".mailmap";
-while () {
-	chomp;
-	next if /^#/;
-	if (my ($author, $mail) = /^(.*?)\s+<(.+)>$/) {
-		$mailmap{$mail} = $author;
-	}
+#!/usr/bin/perl
+
+use warnings 'all';
+use strict;
+use Getopt::Long;
+
+my $match_emails;
+my $match_names;
+my $order_by = 'count';
+Getopt::Long::Configure(qw(bundling));
+GetOptions(
+	'emails|e!' => \$match_emails,
+	'names|n!'  => \$match_names,
+	'count|c'   => sub { $order_by = 'count' },
+	'time|t'    => sub { $order_by = 'stamp' },
+) or exit 1;
+$match_emails = 1 unless $match_names;
+
+my $email = {};
+my $name = {};
+
+open(my $fh, '-|', "git log --format='%at <%aE> %aN'");
+while(<$fh>) {
+	my ($t, $e, $n) = /(\S+) <(\S+)> (.*)/;
+	mark($email, $e, $n, $t);
+	mark($name, $n, $e, $t);
 }
-close I;
-
-my %mail2author = ();
-open I, "git log --pretty='format:%ae	%an' |";
-while () {
-	chomp;
-	my ($mail, $author) = split(/\t/, $_);
-	next if exists $mailmap{$mail};
-	$mail2author{$mail} ||= {};
-	$mail2author{$mail}{$author} ||= 0;
-	$mail2author{$mail}{$author}++;
+close($fh);
+
+if ($match_emails) {
+	foreach my $e (dups($email)) {
+		foreach my $n (vals($email->{$e})) {
+			show($n, $e, $email->{$e}->{$n});
+		}
+		print "\n";
+	}
 }
-close I;
-
-while (my ($mail, $authorcount) = each %mail2author) {
-	# %$authorcount is ($author => $count);
-	# sort and show the names from the most frequent ones.
-	my @names = (map { $_->[0] }
-		sort { $b->[1] <=> $a->[1] }
-		map { [$_, $authorcount->{$_}] }
-		keys %$authorcount);
-	if (1 < @names) {
-		for (@names) {
-			print "$_ <$mail>\n";
+if ($match_names) {
+	foreach my $n (dups($name)) {
+		foreach my $e (vals($name->{$n})) {
+			show($n, $e, $name->{$n}->{$e});
 		}
+		print "\n";
 	}
 }
+exit 0;
 
+sub mark {
+	my ($h, $k, $v, $t) = @_;
+	my $e = $h->{$k}->{$v} ||= { count => 0, stamp => 0 };
+	$e->{count}++;
+	$e->{stamp} = $t unless $t < $e->{stamp};
+}
+
+sub dups {
+	my $h = shift;
+	return grep { keys($h->{$_}) > 1 } keys($h);
+}
+
+sub vals {
+	my $h = shift;
+	return sort {
+		$h->{$b}->{$order_by} <=> $h->{$a}->{$order_by}
+	} keys($h);
+}
+
+sub show {
+	my ($n, $e, $h) = @_;
+	print "$n <$e> ($h->{$order_by})\n";
+}
diff --git a/contrib/subtree/.gitignore b/contrib/subtree/.gitignore
index 7e77c9d022..91360a3d7f 100644
--- a/contrib/subtree/.gitignore
+++ b/contrib/subtree/.gitignore
@@ -1,4 +1,5 @@
 *~
+git-subtree
 git-subtree.xml
 git-subtree.1
 mainline
diff --git a/contrib/subtree/git-subtree.txt b/contrib/subtree/git-subtree.txt
index 0c44fda011..c5bce41ac7 100644
--- a/contrib/subtree/git-subtree.txt
+++ b/contrib/subtree/git-subtree.txt
@@ -93,7 +93,7 @@ pull::
 	repository.
 	
 push::
-	Does a 'split' (see above) using the  supplied
+	Does a 'split' (see below) using the  supplied
 	and then does a 'git push' to push the result to the 
 	repository and refspec. This can be used to push your
 	subtree to different branches of the remote repository.
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..17cf6f961f
--- /dev/null
+++ b/contrib/svn-fe/svnrdump_sim.py
@@ -0,0 +1,57 @@
+#!/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
+
+if sys.hexversion < 0x02040000:
+        # The limiter is the ValueError() calls. This may be too conservative
+        sys.stderr.write("svnrdump-sim.py: requires Python 2.4 or later.\n")
+        sys.exit(1)
+
+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/contrib/vim/README b/contrib/vim/README
index fca1e17251..8f16d06972 100644
--- a/contrib/vim/README
+++ b/contrib/vim/README
@@ -17,16 +17,6 @@ To install:
 
   1. Copy these files to vim's syntax directory $HOME/.vim/syntax
   2. To auto-detect the editing of various git-related filetypes:
-	$ cat >>$HOME/.vim/filetype.vim <<'EOF'
-	autocmd BufNewFile,BufRead *.git/COMMIT_EDITMSG    setf gitcommit
-	autocmd BufNewFile,BufRead *.git/config,.gitconfig setf gitconfig
-	autocmd BufNewFile,BufRead git-rebase-todo         setf gitrebase
-	autocmd BufNewFile,BufRead .msg.[0-9]*
-		\ if getline(1) =~ '^From.*# This line is ignored.$' |
-		\   setf gitsendemail |
-		\ endif
-	autocmd BufNewFile,BufRead *.git/**
-		\ if getline(1) =~ '^\x\{40\}\>\|^ref: ' |
-		\   setf git |
-		\ endif
-	EOF
+
+	$ curl http://ftp.vim.org/pub/vim/runtime/filetype.vim |
+		sed -ne '/^" Git$/, /^$/ p' >>$HOME/.vim/filetype.vim
diff --git a/ctype.c b/ctype.c
index 935327164b..0bfebb4e75 100644
--- a/ctype.c
+++ b/ctype.c
@@ -11,18 +11,21 @@ enum {
 	D = GIT_DIGIT,
 	G = GIT_GLOB_SPECIAL,	/* *, ?, [, \\ */
 	R = GIT_REGEX_SPECIAL,	/* $, (, ), +, ., ^, {, | */
-	P = GIT_PATHSPEC_MAGIC  /* other non-alnum, except for ] and } */
+	P = GIT_PATHSPEC_MAGIC, /* other non-alnum, except for ] and } */
+	X = GIT_CNTRL,
+	U = GIT_PUNCT,
+	Z = GIT_CNTRL | GIT_SPACE
 };
 
-unsigned char sane_ctype[256] = {
-	0, 0, 0, 0, 0, 0, 0, 0, 0, S, S, 0, 0, S, 0, 0,		/*   0.. 15 */
-	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,		/*  16.. 31 */
+const unsigned char sane_ctype[256] = {
+	X, X, X, X, X, X, X, X, X, Z, Z, X, X, Z, X, X,		/*   0.. 15 */
+	X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X,		/*  16.. 31 */
 	S, P, P, P, R, P, P, P, R, R, G, R, P, P, R, P,		/*  32.. 47 */
 	D, D, D, D, D, D, D, D, D, D, P, P, P, P, P, G,		/*  48.. 63 */
 	P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A,		/*  64.. 79 */
-	A, A, A, A, A, A, A, A, A, A, A, G, G, 0, R, P,		/*  80.. 95 */
+	A, A, A, A, A, A, A, A, A, A, A, G, G, U, R, P,		/*  80.. 95 */
 	P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A,		/*  96..111 */
-	A, A, A, A, A, A, A, A, A, A, A, R, R, 0, P, 0,		/* 112..127 */
+	A, A, A, A, A, A, A, A, A, A, A, R, R, U, P, X,		/* 112..127 */
 	/* Nothing in the 128.. range */
 };
 
diff --git a/diff.c b/diff.c
index 35d3f07385..348f71b462 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];
@@ -3582,6 +3626,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
 		DIFF_OPT_SET(options, FIND_COPIES_HARDER);
 	else if (!strcmp(arg, "--follow"))
 		DIFF_OPT_SET(options, FOLLOW_RENAMES);
+	else if (!strcmp(arg, "--no-follow"))
+		DIFF_OPT_CLR(options, FOLLOW_RENAMES);
 	else if (!strcmp(arg, "--color"))
 		options->use_color = 1;
 	else if (!prefixcmp(arg, "--color=")) {
@@ -3646,10 +3692,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 +4915,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 a209376354..b097fa7661 100644
--- a/diffcore-pickaxe.c
+++ b/diffcore-pickaxe.c
@@ -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..cf1e6b0082 100644
--- a/dir.c
+++ b/dir.c
@@ -2,12 +2,15 @@
  * This handles recursive filename detection with exclude
  * files, index knowledge etc..
  *
+ * See Documentation/technical/api-directory-listing.txt
+ *
  * Copyright (C) Linus Torvalds, 2005-2006
  *		 Junio Hamano, 2005-2006
  */
 #include "cache.h"
 #include "dir.h"
 #include "refs.h"
+#include "wildmatch.h"
 
 struct path_simplify {
 	int len;
@@ -34,10 +37,33 @@ int fnmatch_icase(const char *pattern, const char *string, int flags)
 	return fnmatch(pattern, string, flags | (ignore_case ? FNM_CASEFOLD : 0));
 }
 
+inline int git_fnmatch(const char *pattern, const char *string,
+		       int flags, int prefix)
+{
+	int fnm_flags = 0;
+	if (flags & GFNM_PATHNAME)
+		fnm_flags |= FNM_PATHNAME;
+	if (prefix > 0) {
+		if (strncmp(pattern, string, prefix))
+			return FNM_NOMATCH;
+		pattern += prefix;
+		string += prefix;
+	}
+	if (flags & GFNM_ONESTAR) {
+		int pattern_len = strlen(++pattern);
+		int string_len = strlen(string);
+		return string_len < pattern_len ||
+		       strcmp(pattern,
+			      string + string_len - pattern_len);
+	}
+	return fnmatch(pattern, string, fnm_flags);
+}
+
 static size_t common_prefix_len(const char **pathspec)
 {
 	const char *n, *first;
 	size_t max = 0;
+	int literal = limit_pathspec_to_literal();
 
 	if (!pathspec)
 		return max;
@@ -47,7 +73,7 @@ static size_t common_prefix_len(const char **pathspec)
 		size_t i, len = 0;
 		for (i = 0; first == n || i < max; i++) {
 			char c = n[i];
-			if (!c || c != first[i] || is_glob_special(c))
+			if (!c || c != first[i] || (!literal && is_glob_special(c)))
 				break;
 			if (c == '/')
 				len = i + 1;
@@ -117,6 +143,7 @@ int within_depth(const char *name, int namelen,
 static int match_one(const char *match, const char *name, int namelen)
 {
 	int matchlen;
+	int literal = limit_pathspec_to_literal();
 
 	/* If the match was just the prefix, we matched */
 	if (!*match)
@@ -126,7 +153,7 @@ static int match_one(const char *match, const char *name, int namelen)
 		for (;;) {
 			unsigned char c1 = tolower(*match);
 			unsigned char c2 = tolower(*name);
-			if (c1 == '\0' || is_glob_special(c1))
+			if (c1 == '\0' || (!literal && is_glob_special(c1)))
 				break;
 			if (c1 != c2)
 				return 0;
@@ -138,7 +165,7 @@ static int match_one(const char *match, const char *name, int namelen)
 		for (;;) {
 			unsigned char c1 = *match;
 			unsigned char c2 = *name;
-			if (c1 == '\0' || is_glob_special(c1))
+			if (c1 == '\0' || (!literal && is_glob_special(c1)))
 				break;
 			if (c1 != c2)
 				return 0;
@@ -148,14 +175,16 @@ static int match_one(const char *match, const char *name, int namelen)
 		}
 	}
 
-
 	/*
 	 * If we don't match the matchstring exactly,
 	 * we need to match by fnmatch
 	 */
 	matchlen = strlen(match);
-	if (strncmp_icase(match, name, matchlen))
+	if (strncmp_icase(match, name, matchlen)) {
+		if (literal)
+			return 0;
 		return !fnmatch_icase(match, name, 0) ? MATCHED_FNMATCH : 0;
+	}
 
 	if (namelen == matchlen)
 		return MATCHED_EXACTLY;
@@ -165,12 +194,19 @@ static int match_one(const char *match, const char *name, int namelen)
 }
 
 /*
- * Given a name and a list of pathspecs, see if the name matches
- * any of the pathspecs.  The caller is also interested in seeing
- * all pathspec matches some names it calls this function with
- * (otherwise the user could have mistyped the unmatched pathspec),
- * and a mark is left in seen[] array for pathspec element that
- * actually matched anything.
+ * Given a name and a list of pathspecs, returns the nature of the
+ * closest (i.e. most specific) match of the name to any of the
+ * pathspecs.
+ *
+ * The caller typically calls this multiple times with the same
+ * pathspec and seen[] array but with different name/namelen
+ * (e.g. entries from the index) and is interested in seeing if and
+ * how each pathspec matches all the names it calls this function
+ * with.  A mark is left in the seen[] array for each pathspec element
+ * indicating the closest type of match that element achieved, so if
+ * seen[n] remains zero after multiple invocations, that means the nth
+ * pathspec did not match any names, which could indicate that the
+ * user mistyped the nth pathspec.
  */
 int match_pathspec(const char **pathspec, const char *name, int namelen,
 		int prefix, char *seen)
@@ -230,19 +266,29 @@ static int match_pathspec_item(const struct pathspec_item *item, int prefix,
 			return MATCHED_RECURSIVELY;
 	}
 
-	if (item->use_wildcard && !fnmatch(match, name, 0))
+	if (item->nowildcard_len < item->len &&
+	    !git_fnmatch(match, name,
+			 item->flags & PATHSPEC_ONESTAR ? GFNM_ONESTAR : 0,
+			 item->nowildcard_len - prefix))
 		return MATCHED_FNMATCH;
 
 	return 0;
 }
 
 /*
- * Given a name and a list of pathspecs, see if the name matches
- * any of the pathspecs.  The caller is also interested in seeing
- * all pathspec matches some names it calls this function with
- * (otherwise the user could have mistyped the unmatched pathspec),
- * and a mark is left in seen[] array for pathspec element that
- * actually matched anything.
+ * Given a name and a list of pathspecs, returns the nature of the
+ * closest (i.e. most specific) match of the name to any of the
+ * pathspecs.
+ *
+ * The caller typically calls this multiple times with the same
+ * pathspec and seen[] array but with different name/namelen
+ * (e.g. entries from the index) and is interested in seeing if and
+ * how each pathspec matches all the names it calls this function
+ * with.  A mark is left in the seen[] array for each pathspec element
+ * indicating the closest type of match that element achieved, so if
+ * seen[n] remains zero after multiple invocations, that means the nth
+ * pathspec did not match any names, which could indicate that the
+ * user mistyped the nth pathspec.
  */
 int match_pathspec_depth(const struct pathspec *ps,
 			 const char *name, int namelen,
@@ -308,44 +354,73 @@ 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)
+		 int baselen, struct exclude_list *el, int srcpos)
 {
 	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;
+	x->srcpos = srcpos;
+	ALLOC_GROW(el->excludes, el->nr + 1, el->alloc);
+	el->excludes[el->nr++] = x;
+	x->el = el;
 }
 
 static void *read_skip_worktree_file_from_index(const char *path, size_t *size)
@@ -371,27 +446,32 @@ static void *read_skip_worktree_file_from_index(const char *path, size_t *size)
 	return data;
 }
 
-void free_excludes(struct exclude_list *el)
+/*
+ * Frees memory within el which was allocated for exclude patterns and
+ * the file buffer.  Does not free el itself.
+ */
+void clear_exclude_list(struct exclude_list *el)
 {
 	int i;
 
 	for (i = 0; i < el->nr; i++)
 		free(el->excludes[i]);
 	free(el->excludes);
+	free(el->filebuf);
 
 	el->nr = 0;
 	el->excludes = NULL;
+	el->filebuf = NULL;
 }
 
 int add_excludes_from_file_to_list(const char *fname,
 				   const char *base,
 				   int baselen,
-				   char **buf_p,
-				   struct exclude_list *which,
+				   struct exclude_list *el,
 				   int check_index)
 {
 	struct stat st;
-	int fd, i;
+	int fd, i, lineno = 1;
 	size_t size = 0;
 	char *buf, *entry;
 
@@ -429,30 +509,53 @@ int add_excludes_from_file_to_list(const char *fname,
 		close(fd);
 	}
 
-	if (buf_p)
-		*buf_p = buf;
+	el->filebuf = buf;
 	entry = buf;
 	for (i = 0; i < size; i++) {
 		if (buf[i] == '\n') {
 			if (entry != buf + i && entry[0] != '#') {
 				buf[i - (i && buf[i-1] == '\r')] = 0;
-				add_exclude(entry, base, baselen, which);
+				add_exclude(entry, base, baselen, el, lineno);
 			}
+			lineno++;
 			entry = buf + i + 1;
 		}
 	}
 	return 0;
 }
 
+struct exclude_list *add_exclude_list(struct dir_struct *dir,
+				      int group_type, const char *src)
+{
+	struct exclude_list *el;
+	struct exclude_list_group *group;
+
+	group = &dir->exclude_list_group[group_type];
+	ALLOC_GROW(group->el, group->nr + 1, group->alloc);
+	el = &group->el[group->nr++];
+	memset(el, 0, sizeof(*el));
+	el->src = src;
+	return el;
+}
+
+/*
+ * Used to set up core.excludesfile and .git/info/exclude lists.
+ */
 void add_excludes_from_file(struct dir_struct *dir, const char *fname)
 {
-	if (add_excludes_from_file_to_list(fname, "", 0, NULL,
-					   &dir->exclude_list[EXC_FILE], 0) < 0)
+	struct exclude_list *el;
+	el = add_exclude_list(dir, EXC_FILE, fname);
+	if (add_excludes_from_file_to_list(fname, "", 0, el, 0) < 0)
 		die("cannot use %s as an exclude file", fname);
 }
 
+/*
+ * Loads the per-directory exclude list for the substring of base
+ * which has a char length of baselen.
+ */
 static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
 {
+	struct exclude_list_group *group;
 	struct exclude_list *el;
 	struct exclude_stack *stk = NULL;
 	int current;
@@ -461,17 +564,21 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
 	    (baselen + strlen(dir->exclude_per_dir) >= PATH_MAX))
 		return; /* too long a path -- ignore */
 
-	/* Pop the ones that are not the prefix of the path being checked. */
-	el = &dir->exclude_list[EXC_DIRS];
+	group = &dir->exclude_list_group[EXC_DIRS];
+
+	/* Pop the exclude lists from the EXCL_DIRS exclude_list_group
+	 * which originate from directories not in the prefix of the
+	 * path being checked. */
 	while ((stk = dir->exclude_stack) != NULL) {
 		if (stk->baselen <= baselen &&
 		    !strncmp(dir->basebuf, base, stk->baselen))
 			break;
+		el = &group->el[dir->exclude_stack->exclude_ix];
 		dir->exclude_stack = stk->prev;
-		while (stk->exclude_ix < el->nr)
-			free(el->excludes[--el->nr]);
-		free(stk->filebuf);
+		free((char *)el->src); /* see strdup() below */
+		clear_exclude_list(el);
 		free(stk);
+		group->nr--;
 	}
 
 	/* Read from the parent directories and push them down. */
@@ -492,36 +599,116 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
 		}
 		stk->prev = dir->exclude_stack;
 		stk->baselen = cp - base;
-		stk->exclude_ix = el->nr;
 		memcpy(dir->basebuf + current, base + current,
 		       stk->baselen - current);
 		strcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir);
+		/*
+		 * dir->basebuf gets reused by the traversal, but we
+		 * need fname to remain unchanged to ensure the src
+		 * member of each struct exclude correctly
+		 * back-references its source file.  Other invocations
+		 * of add_exclude_list provide stable strings, so we
+		 * strdup() and free() here in the caller.
+		 */
+		el = add_exclude_list(dir, EXC_DIRS, strdup(dir->basebuf));
+		stk->exclude_ix = group->nr - 1;
 		add_excludes_from_file_to_list(dir->basebuf,
 					       dir->basebuf, stk->baselen,
-					       &stk->filebuf, el, 1);
+					       el, 1);
 		dir->exclude_stack = stk;
 		current = stk->baselen;
 	}
 	dir->basebuf[baselen] = '\0';
 }
 
-/* Scan the list and let the last match determine the fate.
- * Return 1 for exclude, 0 for include and -1 for undecided.
+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 wildmatch(pattern, name,
+			 ignore_case ? FNM_CASEFOLD : 0) == 0;
+}
+
+/*
+ * Scan the given exclude list in reverse to see whether pathname
+ * should be ignored.  The first match (i.e. the last on the list), if
+ * any, determines the fate.  Returns the exclude_list element which
+ * matched, or NULL for undecided.
  */
-int excluded_from_list(const char *pathname,
-		       int pathlen, const char *basename, int *dtype,
-		       struct exclude_list *el)
+static struct exclude *last_exclude_matching_from_list(const char *pathname,
+						       int pathlen,
+						       const char *basename,
+						       int *dtype,
+						       struct exclude_list *el)
 {
 	int i;
 
 	if (!el->nr)
-		return -1;	/* undefined */
+		return NULL;	/* undefined */
 
 	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 prefix = x->nowildcardlen;
 
 		if (x->flags & EXC_FLAG_MUSTBEDIR) {
 			if (*dtype == DT_UNKNOWN)
@@ -531,73 +718,81 @@ 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;
-			}
+			if (match_basename(basename,
+					   pathlen - (basename - pathname),
+					   exclude, prefix, x->patternlen,
+					   x->flags))
+				return x;
 			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))
-			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))
-			return to_exclude;
+		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 x;
 	}
+	return NULL; /* undecided */
+}
+
+/*
+ * Scan the list and let the last match determine the fate.
+ * Return 1 for exclude, 0 for include and -1 for undecided.
+ */
+int is_excluded_from_list(const char *pathname,
+			  int pathlen, const char *basename, int *dtype,
+			  struct exclude_list *el)
+{
+	struct exclude *exclude;
+	exclude = last_exclude_matching_from_list(pathname, pathlen, basename, dtype, el);
+	if (exclude)
+		return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
 	return -1; /* undecided */
 }
 
-static int excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
+/*
+ * Loads the exclude lists for the directory containing pathname, then
+ * scans all exclude lists to determine whether pathname is excluded.
+ * Returns the exclude_list element which matched, or NULL for
+ * undecided.
+ */
+static struct exclude *last_exclude_matching(struct dir_struct *dir,
+					     const char *pathname,
+					     int *dtype_p)
 {
 	int pathlen = strlen(pathname);
-	int st;
+	int i, j;
+	struct exclude_list_group *group;
+	struct exclude *exclude;
 	const char *basename = strrchr(pathname, '/');
 	basename = (basename) ? basename+1 : pathname;
 
 	prep_exclude(dir, pathname, basename-pathname);
-	for (st = EXC_CMDL; st <= EXC_FILE; st++) {
-		switch (excluded_from_list(pathname, pathlen, basename,
-					   dtype_p, &dir->exclude_list[st])) {
-		case 0:
-			return 0;
-		case 1:
-			return 1;
+
+	for (i = EXC_CMDL; i <= EXC_FILE; i++) {
+		group = &dir->exclude_list_group[i];
+		for (j = group->nr - 1; j >= 0; j--) {
+			exclude = last_exclude_matching_from_list(
+				pathname, pathlen, basename, dtype_p,
+				&group->el[j]);
+			if (exclude)
+				return exclude;
 		}
 	}
+	return NULL;
+}
+
+/*
+ * Loads the exclude lists for the directory containing pathname, then
+ * scans all exclude lists to determine whether pathname is excluded.
+ * Returns 1 if true, otherwise 0.
+ */
+static int is_excluded(struct dir_struct *dir, const char *pathname, int *dtype_p)
+{
+	struct exclude *exclude =
+		last_exclude_matching(dir, pathname, dtype_p);
+	if (exclude)
+		return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
 	return 0;
 }
 
@@ -605,6 +800,7 @@ void path_exclude_check_init(struct path_exclude_check *check,
 			     struct dir_struct *dir)
 {
 	check->dir = dir;
+	check->exclude = NULL;
 	strbuf_init(&check->path, 256);
 }
 
@@ -614,32 +810,41 @@ void path_exclude_check_clear(struct path_exclude_check *check)
 }
 
 /*
- * Is this name excluded?  This is for a caller like show_files() that
- * do not honor directory hierarchy and iterate through paths that are
- * possibly in an ignored directory.
+ * For each subdirectory in name, starting with the top-most, checks
+ * to see if that subdirectory is excluded, and if so, returns the
+ * corresponding exclude structure.  Otherwise, checks whether name
+ * itself (which is presumably a file) is excluded.
  *
  * A path to a directory known to be excluded is left in check->path to
  * optimize for repeated checks for files in the same excluded directory.
  */
-int path_excluded(struct path_exclude_check *check,
-		  const char *name, int namelen, int *dtype)
+struct exclude *last_exclude_matching_path(struct path_exclude_check *check,
+					   const char *name, int namelen,
+					   int *dtype)
 {
 	int i;
 	struct strbuf *path = &check->path;
+	struct exclude *exclude;
 
 	/*
 	 * we allow the caller to pass namelen as an optimization; it
 	 * must match the length of the name, as we eventually call
-	 * excluded() on the whole name string.
+	 * is_excluded() on the whole name string.
 	 */
 	if (namelen < 0)
 		namelen = strlen(name);
 
+	/*
+	 * If path is non-empty, and name is equal to path or a
+	 * subdirectory of path, name should be excluded, because
+	 * it's inside a directory which is already known to be
+	 * excluded and was previously left in check->path.
+	 */
 	if (path->len &&
 	    path->len <= namelen &&
 	    !memcmp(name, path->buf, path->len) &&
 	    (!name[path->len] || name[path->len] == '/'))
-		return 1;
+		return check->exclude;
 
 	strbuf_setlen(path, 0);
 	for (i = 0; name[i]; i++) {
@@ -647,8 +852,12 @@ int path_excluded(struct path_exclude_check *check,
 
 		if (ch == '/') {
 			int dt = DT_DIR;
-			if (excluded(check->dir, path->buf, &dt))
-				return 1;
+			exclude = last_exclude_matching(check->dir,
+							path->buf, &dt);
+			if (exclude) {
+				check->exclude = exclude;
+				return exclude;
+			}
 		}
 		strbuf_addch(path, ch);
 	}
@@ -656,7 +865,22 @@ int path_excluded(struct path_exclude_check *check,
 	/* An entry in the index; cannot be a directory with subentries */
 	strbuf_setlen(path, 0);
 
-	return excluded(check->dir, name, dtype);
+	return last_exclude_matching(check->dir, name, dtype);
+}
+
+/*
+ * Is this name excluded?  This is for a caller like show_files() that
+ * do not honor directory hierarchy and iterate through paths that are
+ * possibly in an ignored directory.
+ */
+int is_path_excluded(struct path_exclude_check *check,
+		  const char *name, int namelen, int *dtype)
+{
+	struct exclude *exclude =
+		last_exclude_matching_path(check, name, namelen, dtype);
+	if (exclude)
+		return exclude->flags & EXC_FLAG_NEGATIVE ? 0 : 1;
+	return 0;
 }
 
 static struct dir_entry *dir_entry_new(const char *pathname, int len)
@@ -672,7 +896,8 @@ static struct dir_entry *dir_entry_new(const char *pathname, int len)
 
 static struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len)
 {
-	if (cache_name_exists(pathname, len, ignore_case))
+	if (!(dir->flags & DIR_SHOW_IGNORED) &&
+	    cache_name_exists(pathname, len, ignore_case))
 		return NULL;
 
 	ALLOC_GROW(dir->entries, dir->nr+1, dir->alloc);
@@ -774,8 +999,9 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len)
  * traversal routine.
  *
  * Case 1: If we *already* have entries in the index under that
- * directory name, we always recurse into the directory to see
- * all the files.
+ * directory name, we recurse into the directory to see all the files,
+ * unless the directory is excluded and we want to show ignored
+ * directories
  *
  * Case 2: If we *already* have that directory name as a gitlink,
  * we always continue to see it as a gitlink, regardless of whether
@@ -789,6 +1015,9 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len)
  *      just a directory, unless "hide_empty_directories" is
  *      also true and the directory is empty, in which case
  *      we just ignore it entirely.
+ *      if we are looking for ignored directories, look if it
+ *      contains only ignored files to decide if it must be shown as
+ *      ignored or not.
  *  (b) if it looks like a git directory, and we don't have
  *      'no_gitlinks' set we treat it as a gitlink, and show it
  *      as a directory.
@@ -801,12 +1030,15 @@ enum directory_treatment {
 };
 
 static enum directory_treatment treat_directory(struct dir_struct *dir,
-	const char *dirname, int len,
+	const char *dirname, int len, int exclude,
 	const struct path_simplify *simplify)
 {
 	/* The "len-1" is to strip the final '/' */
 	switch (directory_exists_in_index(dirname, len-1)) {
 	case index_directory:
+		if ((dir->flags & DIR_SHOW_OTHER_DIRECTORIES) && exclude)
+			break;
+
 		return recurse_into_directory;
 
 	case index_gitdir:
@@ -826,13 +1058,68 @@ static enum directory_treatment treat_directory(struct dir_struct *dir,
 	}
 
 	/* This is the "show_other_directories" case */
-	if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
+
+	/*
+	 * We are looking for ignored files and our directory is not ignored,
+	 * check if it contains only ignored files
+	 */
+	if ((dir->flags & DIR_SHOW_IGNORED) && !exclude) {
+		int ignored;
+		dir->flags &= ~DIR_SHOW_IGNORED;
+		dir->flags |= DIR_HIDE_EMPTY_DIRECTORIES;
+		ignored = read_directory_recursive(dir, dirname, len, 1, simplify);
+		dir->flags &= ~DIR_HIDE_EMPTY_DIRECTORIES;
+		dir->flags |= DIR_SHOW_IGNORED;
+
+		return ignored ? ignore_directory : show_directory;
+	}
+	if (!(dir->flags & DIR_SHOW_IGNORED) &&
+	    !(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
 		return show_directory;
 	if (!read_directory_recursive(dir, dirname, len, 1, simplify))
 		return ignore_directory;
 	return show_directory;
 }
 
+/*
+ * Decide what to do when we find a file while traversing the
+ * filesystem. Mostly two cases:
+ *
+ *  1. We are looking for ignored files
+ *   (a) File is ignored, include it
+ *   (b) File is in ignored path, include it
+ *   (c) File is not ignored, exclude it
+ *
+ *  2. Other scenarios, include the file if not excluded
+ *
+ * Return 1 for exclude, 0 for include.
+ */
+static int treat_file(struct dir_struct *dir, struct strbuf *path, int exclude, int *dtype)
+{
+	struct path_exclude_check check;
+	int exclude_file = 0;
+
+	if (exclude)
+		exclude_file = !(dir->flags & DIR_SHOW_IGNORED);
+	else if (dir->flags & DIR_SHOW_IGNORED) {
+		/* Always exclude indexed files */
+		struct cache_entry *ce = index_name_exists(&the_index,
+		    path->buf, path->len, ignore_case);
+
+		if (ce)
+			return 1;
+
+		path_exclude_check_init(&check, dir);
+
+		if (!is_path_excluded(&check, path->buf, path->len, dtype))
+			exclude_file = 1;
+
+		path_exclude_check_clear(&check);
+	}
+
+	return exclude_file;
+}
+
 /*
  * This is an inexact early pruning of any recursive directory
  * reading - if the path cannot possibly be in the pathspec,
@@ -956,7 +1243,7 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
 					  const struct path_simplify *simplify,
 					  int dtype, struct dirent *de)
 {
-	int exclude = excluded(dir, path->buf, &dtype);
+	int exclude = is_excluded(dir, path->buf, &dtype);
 	if (exclude && (dir->flags & DIR_COLLECT_IGNORED)
 	    && exclude_matches_pathspec(path->buf, path->len, simplify))
 		dir_add_ignored(dir, path->buf, path->len);
@@ -971,27 +1258,14 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
 	if (dtype == DT_UNKNOWN)
 		dtype = get_dtype(de, path->buf, path->len);
 
-	/*
-	 * Do we want to see just the ignored files?
-	 * We still need to recurse into directories,
-	 * even if we don't ignore them, since the
-	 * directory may contain files that we do..
-	 */
-	if (!exclude && (dir->flags & DIR_SHOW_IGNORED)) {
-		if (dtype != DT_DIR)
-			return path_ignored;
-	}
-
 	switch (dtype) {
 	default:
 		return path_ignored;
 	case DT_DIR:
 		strbuf_addch(path, '/');
-		switch (treat_directory(dir, path->buf, path->len, simplify)) {
+
+		switch (treat_directory(dir, path->buf, path->len, exclude, simplify)) {
 		case show_directory:
-			if (exclude != !!(dir->flags
-					  & DIR_SHOW_IGNORED))
-				return path_ignored;
 			break;
 		case recurse_into_directory:
 			return path_recurse;
@@ -1001,7 +1275,12 @@ static enum path_treatment treat_one_path(struct dir_struct *dir,
 		break;
 	case DT_REG:
 	case DT_LNK:
-		break;
+		switch (treat_file(dir, path, exclude, &dtype)) {
+		case 1:
+			return path_ignored;
+		default:
+			break;
+		}
 	}
 	return path_handled;
 }
@@ -1369,9 +1648,18 @@ int init_pathspec(struct pathspec *pathspec, const char **paths)
 
 		item->match = path;
 		item->len = strlen(path);
-		item->use_wildcard = !no_wildcard(path);
-		if (item->use_wildcard)
-			pathspec->has_wildcard = 1;
+		item->flags = 0;
+		if (limit_pathspec_to_literal()) {
+			item->nowildcard_len = item->len;
+		} else {
+			item->nowildcard_len = simple_length(path);
+			if (item->nowildcard_len < item->len) {
+				pathspec->has_wildcard = 1;
+				if (path[item->nowildcard_len] == '*' &&
+				    no_wildcard(path + item->nowildcard_len + 1))
+					item->flags |= PATHSPEC_ONESTAR;
+			}
+		}
 	}
 
 	qsort(pathspec->items, pathspec->nr,
@@ -1385,3 +1673,41 @@ void free_pathspec(struct pathspec *pathspec)
 	free(pathspec->items);
 	pathspec->items = NULL;
 }
+
+int limit_pathspec_to_literal(void)
+{
+	static int flag = -1;
+	if (flag < 0)
+		flag = git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT, 0);
+	return flag;
+}
+
+/*
+ * Frees memory within dir which was allocated for exclude lists and
+ * the exclude_stack.  Does not free dir itself.
+ */
+void clear_directory(struct dir_struct *dir)
+{
+	int i, j;
+	struct exclude_list_group *group;
+	struct exclude_list *el;
+	struct exclude_stack *stk;
+
+	for (i = EXC_CMDL; i <= EXC_FILE; i++) {
+		group = &dir->exclude_list_group[i];
+		for (j = 0; j < group->nr; j++) {
+			el = &group->el[j];
+			if (i == EXC_DIRS)
+				free((char *)el->src);
+			clear_exclude_list(el);
+		}
+		free(group->el);
+	}
+
+	stk = dir->exclude_stack;
+	while (stk) {
+		struct exclude_stack *prev = stk->prev;
+		free(stk);
+		stk = prev;
+	}
+}
diff --git a/dir.h b/dir.h
index 893465a1e8..c3eb4b520e 100644
--- a/dir.h
+++ b/dir.h
@@ -1,6 +1,8 @@
 #ifndef DIR_H
 #define DIR_H
 
+/* See Documentation/technical/api-directory-listing.txt */
+
 #include "strbuf.h"
 
 struct dir_entry {
@@ -11,26 +13,62 @@ struct dir_entry {
 #define EXC_FLAG_NODIR 1
 #define EXC_FLAG_ENDSWITH 4
 #define EXC_FLAG_MUSTBEDIR 8
+#define EXC_FLAG_NEGATIVE 16
 
+/*
+ * Each excludes file will be parsed into a fresh exclude_list which
+ * is appended to the relevant exclude_list_group (either EXC_DIRS or
+ * EXC_FILE).  An exclude_list within the EXC_CMDL exclude_list_group
+ * can also be used to represent the list of --exclude values passed
+ * via CLI args.
+ */
 struct exclude_list {
 	int nr;
 	int alloc;
+
+	/* remember pointer to exclude file contents so we can free() */
+	char *filebuf;
+
+	/* origin of list, e.g. path to filename, or descriptive string */
+	const char *src;
+
 	struct exclude {
+		/*
+		 * This allows callers of last_exclude_matching() etc.
+		 * to determine the origin of the matching pattern.
+		 */
+		struct exclude_list *el;
+
 		const char *pattern;
 		int patternlen;
 		int nowildcardlen;
 		const char *base;
 		int baselen;
-		int to_exclude;
 		int flags;
+
+		/*
+		 * Counting starts from 1 for line numbers in ignore files,
+		 * and from -1 decrementing for patterns from CLI args.
+		 */
+		int srcpos;
 	} **excludes;
 };
 
+/*
+ * The contents of the per-directory exclude files are lazily read on
+ * demand and then cached in memory, one per exclude_stack struct, in
+ * order to avoid opening and parsing each one every time that
+ * directory is traversed.
+ */
 struct exclude_stack {
-	struct exclude_stack *prev;
-	char *filebuf;
+	struct exclude_stack *prev; /* the struct exclude_stack for the parent directory */
 	int baselen;
-	int exclude_ix;
+	int exclude_ix; /* index of exclude_list within EXC_DIRS exclude_list_group */
+};
+
+struct exclude_list_group {
+	int nr, alloc;
+	struct exclude_list *el;
 };
 
 struct dir_struct {
@@ -48,21 +86,42 @@ struct dir_struct {
 
 	/* Exclude info */
 	const char *exclude_per_dir;
-	struct exclude_list exclude_list[3];
+
 	/*
-	 * We maintain three exclude pattern lists:
+	 * We maintain three groups of exclude pattern lists:
+	 *
 	 * EXC_CMDL lists patterns explicitly given on the command line.
 	 * EXC_DIRS lists patterns obtained from per-directory ignore files.
-	 * EXC_FILE lists patterns from fallback ignore files.
+	 * EXC_FILE lists patterns from fallback ignore files, e.g.
+	 *   - .git/info/exclude
+	 *   - core.excludesfile
+	 *
+	 * Each group contains multiple exclude lists, a single list
+	 * per source.
 	 */
 #define EXC_CMDL 0
 #define EXC_DIRS 1
 #define EXC_FILE 2
+	struct exclude_list_group exclude_list_group[3];
 
+	/*
+	 * Temporary variables which are used during loading of the
+	 * per-directory exclude lists.
+	 *
+	 * exclude_stack points to the top of the exclude_stack, and
+	 * basebuf contains the full path to the current
+	 * (sub)directory in the traversal.
+	 */
 	struct exclude_stack *exclude_stack;
 	char basebuf[PATH_MAX];
 };
 
+/*
+ * The ordering of these constants is significant, with
+ * higher-numbered match types signifying "closer" (i.e. more
+ * specific) matches which will override lower-numbered match types
+ * when populating the seen[] array.
+ */
 #define MATCHED_RECURSIVELY 1
 #define MATCHED_FNMATCH 2
 #define MATCHED_EXACTLY 3
@@ -76,30 +135,47 @@ extern int within_depth(const char *name, int namelen, int depth, int max_depth)
 extern int fill_directory(struct dir_struct *dir, const char **pathspec);
 extern int read_directory(struct dir_struct *, const char *path, int len, const char **pathspec);
 
-extern int excluded_from_list(const char *pathname, int pathlen, const char *basename,
-			      int *dtype, struct exclude_list *el);
+extern int is_excluded_from_list(const char *pathname, int pathlen, const char *basename,
+				 int *dtype, struct exclude_list *el);
 struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len);
 
 /*
- * The excluded() API is meant for callers that check each level of leading
- * directory hierarchies with excluded() to avoid recursing into excluded
+ * 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 is_excluded() API is meant for callers that check each level of leading
+ * directory hierarchies with is_excluded() to avoid recursing into excluded
  * directories.  Callers that do not do so should use this API instead.
  */
 struct path_exclude_check {
 	struct dir_struct *dir;
+	struct exclude *exclude;
 	struct strbuf path;
 };
 extern void path_exclude_check_init(struct path_exclude_check *, struct dir_struct *);
 extern void path_exclude_check_clear(struct path_exclude_check *);
-extern int path_excluded(struct path_exclude_check *, const char *, int namelen, int *dtype);
+extern struct exclude *last_exclude_matching_path(struct path_exclude_check *, const char *,
+						  int namelen, int *dtype);
+extern int is_path_excluded(struct path_exclude_check *, const char *, int namelen, int *dtype);
 
 
+extern struct exclude_list *add_exclude_list(struct dir_struct *dir,
+					     int group_type, const char *src);
 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);
+					  struct exclude_list *el, 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);
+			int baselen, struct exclude_list *el, int srcpos);
+extern void clear_exclude_list(struct exclude_list *el);
+extern void clear_directory(struct dir_struct *dir);
 extern int file_exists(const char *);
 
 extern int is_inside_dir(const char *dir);
@@ -128,4 +204,13 @@ extern int strcmp_icase(const char *a, const char *b);
 extern int strncmp_icase(const char *a, const char *b, size_t count);
 extern int fnmatch_icase(const char *pattern, const char *string, int flags);
 
+/*
+ * The prefix part of pattern must not contains wildcards.
+ */
+#define GFNM_PATHNAME 1		/* similar to FNM_PATHNAME */
+#define GFNM_ONESTAR  2		/* there is only _one_ wildcard, a star */
+
+extern int git_fnmatch(const char *pattern, const char *string,
+		       int flags, int prefix);
+
 #endif
diff --git a/editor.c b/editor.c
index d8340031d2..27bdecdaf3 100644
--- a/editor.c
+++ b/editor.c
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "strbuf.h"
 #include "run-command.h"
+#include "sigchain.h"
 
 #ifndef DEFAULT_EDITOR
 #define DEFAULT_EDITOR "vi"
@@ -37,8 +38,25 @@ int launch_editor(const char *path, struct strbuf *buffer, const char *const *en
 
 	if (strcmp(editor, ":")) {
 		const char *args[] = { editor, path, NULL };
+		struct child_process p;
+		int ret, sig;
 
-		if (run_command_v_opt_cd_env(args, RUN_USING_SHELL, NULL, env))
+		memset(&p, 0, sizeof(p));
+		p.argv = args;
+		p.env = env;
+		p.use_shell = 1;
+		if (start_command(&p) < 0)
+			return error("unable to start editor '%s'", editor);
+
+		sigchain_push(SIGINT, SIG_IGN);
+		sigchain_push(SIGQUIT, SIG_IGN);
+		ret = finish_command(&p);
+		sig = ret - 128;
+		sigchain_pop(SIGINT);
+		sigchain_pop(SIGQUIT);
+		if (sig == SIGINT || sig == SIGQUIT)
+			raise(sig);
+		if (ret)
 			return error("There was a problem with the editor '%s'.",
 					editor);
 	}
diff --git a/fetch-pack.c b/fetch-pack.c
new file mode 100644
index 0000000000..f0acdf7331
--- /dev/null
+++ b/fetch-pack.c
@@ -0,0 +1,950 @@
+#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 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) {
+		static struct lock_file lock;
+		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/fsck.c b/fsck.c
index 7395ef6a42..99c0497674 100644
--- a/fsck.c
+++ b/fsck.c
@@ -142,6 +142,9 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func)
 	int has_null_sha1 = 0;
 	int has_full_path = 0;
 	int has_empty_name = 0;
+	int has_dot = 0;
+	int has_dotdot = 0;
+	int has_dotgit = 0;
 	int has_zero_pad = 0;
 	int has_bad_modes = 0;
 	int has_dup_entries = 0;
@@ -168,6 +171,12 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func)
 			has_full_path = 1;
 		if (!*name)
 			has_empty_name = 1;
+		if (!strcmp(name, "."))
+			has_dot = 1;
+		if (!strcmp(name, ".."))
+			has_dotdot = 1;
+		if (!strcmp(name, ".git"))
+			has_dotgit = 1;
 		has_zero_pad |= *(char *)desc.buffer == '0';
 		update_tree_entry(&desc);
 
@@ -217,6 +226,12 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func)
 		retval += error_func(&item->object, FSCK_WARN, "contains full pathnames");
 	if (has_empty_name)
 		retval += error_func(&item->object, FSCK_WARN, "contains empty pathname");
+	if (has_dot)
+		retval += error_func(&item->object, FSCK_WARN, "contains '.'");
+	if (has_dotdot)
+		retval += error_func(&item->object, FSCK_WARN, "contains '..'");
+	if (has_dotgit)
+		retval += error_func(&item->object, FSCK_WARN, "contains '.git'");
 	if (has_zero_pad)
 		retval += error_func(&item->object, FSCK_WARN, "contains zero-padded file modes");
 	if (has_bad_modes)
diff --git a/git-compat-util.h b/git-compat-util.h
index 2e79b8a2f3..dab545e02e 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -75,7 +75,7 @@
 # endif
 #elif !defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__USLC__) && \
       !defined(_M_UNIX) && !defined(__sgi) && !defined(__DragonFly__) && \
-      !defined(__TANDEM)
+      !defined(__TANDEM) && !defined(__QNX__)
 #define _XOPEN_SOURCE 600 /* glibc2 and AIX 5.3L need 500, OpenBSD needs 600 for S_ISLNK() */
 #define _XOPEN_SOURCE_EXTENDED 1 /* AIX 5.3L needs this */
 #endif
@@ -99,12 +99,14 @@
 #include 
 #include 
 #include 
-#ifdef __TANDEM /* or HAVE_STRINGS_H or !NO_STRINGS_H? */
+#ifdef HAVE_STRINGS_H
 #include  /* for strcasecmp() */
 #endif
 #include 
 #include 
+#ifdef NEEDS_SYS_PARAM_H
 #include 
+#endif
 #include 
 #include 
 #include 
@@ -288,6 +290,17 @@ extern NORETURN void die_errno(const char *err, ...) __attribute__((format (prin
 extern int error(const char *err, ...) __attribute__((format (printf, 1, 2)));
 extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2)));
 
+/*
+ * Let callers be aware of the constant return value; this can help
+ * gcc with -Wuninitialized analysis. We have to restrict this trick to
+ * gcc, though, because of the variadic macro and the magic ## comma pasting
+ * behavior. But since we're only trying to help gcc, anyway, it's OK; other
+ * compilers will fall back to using the function as usual.
+ */
+#if defined(__GNUC__) && ! defined(__clang__)
+#define error(fmt, ...) (error((fmt), ##__VA_ARGS__), -1)
+#endif
+
 extern void set_die_routine(NORETURN_PTR void (*routine)(const char *err, va_list params));
 extern void set_error_routine(void (*routine)(const char *err, va_list params));
 
@@ -411,6 +424,10 @@ void *gitmemmem(const void *haystack, size_t haystacklen,
                 const void *needle, size_t needlelen);
 #endif
 
+#ifdef NO_GETPAGESIZE
+#define getpagesize() sysconf(_SC_PAGESIZE)
+#endif
+
 #ifdef FREAD_READS_DIRECTORIES
 #ifdef fopen
 #undef fopen
@@ -511,13 +528,19 @@ extern const char tolower_trans_tbl[256];
 #undef isupper
 #undef tolower
 #undef toupper
-extern unsigned char sane_ctype[256];
+#undef iscntrl
+#undef ispunct
+#undef isxdigit
+
+extern const unsigned char sane_ctype[256];
 #define GIT_SPACE 0x01
 #define GIT_DIGIT 0x02
 #define GIT_ALPHA 0x04
 #define GIT_GLOB_SPECIAL 0x08
 #define GIT_REGEX_SPECIAL 0x10
 #define GIT_PATHSPEC_MAGIC 0x20
+#define GIT_CNTRL 0x40
+#define GIT_PUNCT 0x80
 #define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0)
 #define isascii(x) (((x) & ~0x7f) == 0)
 #define isspace(x) sane_istest(x,GIT_SPACE)
@@ -529,6 +552,10 @@ extern unsigned char sane_ctype[256];
 #define isupper(x) sane_iscase(x, 0)
 #define is_glob_special(x) sane_istest(x,GIT_GLOB_SPECIAL)
 #define is_regex_special(x) sane_istest(x,GIT_GLOB_SPECIAL | GIT_REGEX_SPECIAL)
+#define iscntrl(x) (sane_istest(x,GIT_CNTRL))
+#define ispunct(x) sane_istest(x, GIT_PUNCT | GIT_REGEX_SPECIAL | \
+		GIT_GLOB_SPECIAL | GIT_PATHSPEC_MAGIC)
+#define isxdigit(x) (hexval_table[x] != -1)
 #define tolower(x) sane_case((unsigned char)(x), 0x20)
 #define toupper(x) sane_case((unsigned char)(x), 0)
 #define is_pathspec_magic(x) sane_istest(x,GIT_PATHSPEC_MAGIC)
@@ -637,8 +664,12 @@ int rmdir_or_warn(const char *path);
  */
 int remove_or_warn(unsigned int mode, const char *path);
 
-/* Call access(2), but warn for any error besides ENOENT. */
+/*
+ * Call access(2), but warn for any error except "missing file"
+ * (ENOENT or ENOTDIR).
+ */
 int access_or_warn(const char *path, int mode);
+int access_or_die(const char *path, int mode);
 
 /* Warn on an inaccessible file that ought to be accessible */
 void warn_on_inaccessible(const char *path);
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..3679074983 100755
--- a/git-cvsserver.perl
+++ b/git-cvsserver.perl
@@ -51,11 +51,16 @@
 
 #### 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,
     'valid-requests'  => \&req_validrequests,
     'Directory'       => \&req_Directory,
+    'Sticky'          => \&req_Sticky,
     'Entry'           => \&req_Entry,
     'Modified'        => \&req_Modified,
     'Unchanged'       => \&req_Unchanged,
@@ -80,7 +85,6 @@
     'noop'            => \&req_EMPTY,
     'annotate'        => \&req_annotate,
     'Global_option'   => \&req_Globaloption,
-    #'annotate'        => \&req_CATCHALL,
 };
 
 ##############################################
@@ -467,11 +471,19 @@ sub req_Directory
     {
         $log->info("Setting prepend to '$state->{path}'");
         $state->{prependdir} = $state->{path};
+        my %entries;
         foreach my $entry ( keys %{$state->{entries}} )
         {
-            $state->{entries}{$state->{prependdir} . $entry} = $state->{entries}{$entry};
-            delete $state->{entries}{$entry};
+            $entries{$state->{prependdir} . $entry} = $state->{entries}{$entry};
         }
+        $state->{entries}=\%entries;
+
+        my %dirMap;
+        foreach my $dir ( keys %{$state->{dirMap}} )
+        {
+            $dirMap{$state->{prependdir} . $dir} = $state->{dirMap}{$dir};
+        }
+        $state->{dirMap}=\%dirMap;
     }
 
     if ( defined ( $state->{prependdir} ) )
@@ -479,9 +491,60 @@ sub req_Directory
         $log->debug("Prepending '$state->{prependdir}' to state|directory");
         $state->{directory} = $state->{prependdir} . $state->{directory}
     }
+
+    if ( ! defined($state->{dirMap}{$state->{directory}}) )
+    {
+        $state->{dirMap}{$state->{directory}} =
+            {
+                'names' => {}
+                #'tagspec' => undef
+            };
+    }
+
     $log->debug("req_Directory : localdir=$data repository=$repository path=$state->{path} directory=$state->{directory} module=$state->{module}");
 }
 
+# Sticky tagspec \n
+#     Response expected: no. Tell the server that the directory most
+#     recently specified with Directory has a sticky tag or date
+#     tagspec. The first character of tagspec is T for a tag, D for
+#     a date, or some other character supplied by a Set-sticky
+#     response from a previous request to the server. The remainder
+#     of tagspec contains the actual tag or date, again as supplied
+#     by Set-sticky.
+#          The server should remember Static-directory and Sticky requests
+#     for a particular directory; the client need not resend them each
+#     time it sends a Directory request for a given directory. However,
+#     the server is not obliged to remember them beyond the context
+#     of a single command.
+sub req_Sticky
+{
+    my ( $cmd, $tagspec ) = @_;
+
+    my ( $stickyInfo );
+    if($tagspec eq "")
+    {
+        # nothing
+    }
+    elsif($tagspec=~/^T([^ ]+)\s*$/)
+    {
+        $stickyInfo = { 'tag' => $1 };
+    }
+    elsif($tagspec=~/^D([0-9.]+)\s*$/)
+    {
+        $stickyInfo= { 'date' => $1 };
+    }
+    else
+    {
+        die "Unknown tag_or_date format\n";
+    }
+    $state->{dirMap}{$state->{directory}}{stickyInfo}=$stickyInfo;
+
+    $log->debug("req_Sticky : tagspec=$tagspec repository=$state->{repository}"
+                . " path=$state->{path} directory=$state->{directory}"
+                . " module=$state->{module}");
+}
+
 # Entry entry-line \n
 #     Response expected: no. Tell the server what version of a file is on the
 #     local machine. The name in entry-line is a name relative to the directory
@@ -499,7 +562,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],
@@ -508,6 +571,8 @@ sub req_Entry
         tag_or_date => $data[5],
     };
 
+    $state->{dirMap}{$state->{directory}}{names}{$data[1]} = 'F';
+
     $log->info("Received entry line '$data' => '" . $state->{directory} . $data[1] . "'");
 }
 
@@ -540,21 +605,22 @@ 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}} )
     {
         $filename = filecleanup($filename);
 
-        my $meta = $updater->getmeta($filename);
+        # no -r, -A, or -D with add
+        my $stickyInfo = resolveStickyInfo($filename);
+
+        my $meta = $updater->getmeta($filename,$stickyInfo);
         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 +637,10 @@ 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";
+                my $entryLine = "/$filepart/$meta->{revision}//$kopts/";
+                $entryLine .= getStickyTagOrDate($stickyInfo);
+                $log->debug($entryLine);
+                print "$entryLine\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";
@@ -603,7 +671,8 @@ sub req_add
         print "$filename\n";
         my $kopts = kopts_from_path($filename,"file",
                         $state->{entries}{$filename}{modified_filename});
-        print "/$filepart/0//$kopts/\n";
+        print "/$filepart/0//$kopts/" .
+              getStickyTagOrDate($stickyInfo) . "\n";
 
         my $requestedKopts = $state->{opt}{k};
         if(defined($requestedKopts))
@@ -671,7 +740,10 @@ sub req_remove
             next;
         }
 
-        my $meta = $updater->getmeta($filename);
+        # only from entries
+        my $stickyInfo = resolveStickyInfo($filename);
+
+        my $meta = $updater->getmeta($filename,$stickyInfo);
         my $wrev = revparse($filename);
 
         unless ( defined ( $wrev ) )
@@ -680,13 +752,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 +773,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/" . getStickyTagOrDate($stickyInfo) . "\n";
 
         $rmcount++;
     }
@@ -881,6 +953,9 @@ sub req_co
         return 1;
     }
 
+    my $stickyInfo = { 'tag' => $state->{opt}{r},
+                       'date' => $state->{opt}{D} };
+
     my $module = $state->{args}[0];
     $state->{module} = $module;
     my $checkout_path = $module;
@@ -898,64 +973,32 @@ sub req_co
     my $updater = GITCVS::updater->new($state->{CVSROOT}, $module, $log);
     $updater->update();
 
-    $checkout_path =~ s|/$||; # get rid of trailing slashes
+    my $headHash;
+    if( defined($stickyInfo) && defined($stickyInfo->{tag}) )
+    {
+        $headHash = $updater->lookupCommitRef($stickyInfo->{tag});
+        if( !defined($headHash) )
+        {
+            print "error 1 no such tag `$stickyInfo->{tag}'\n";
+            cleanupWorkTree();
+            exit;
+        }
+    }
 
-    # Eclipse seems to need the Clear-sticky command
-    # to prepare the 'Entries' file for the new directory.
-    print "Clear-sticky $checkout_path/\n";
-    print $state->{CVSROOT} . "/$module/\n";
-    print "Clear-static-directory $checkout_path/\n";
-    print $state->{CVSROOT} . "/$module/\n";
-    print "Clear-sticky $checkout_path/\n"; # yes, twice
-    print $state->{CVSROOT} . "/$module/\n";
-    print "Template $checkout_path/\n";
-    print $state->{CVSROOT} . "/$module/\n";
-    print "0\n";
-
-    # instruct the client that we're checking out to $checkout_path
-    print "E cvs checkout: Updating $checkout_path\n";
+    $checkout_path =~ s|/$||; # get rid of trailing slashes
 
     my %seendirs = ();
     my $lastdir ='';
 
-    # recursive
-    sub prepdir {
-       my ($dir, $repodir, $remotedir, $seendirs) = @_;
-       my $parent = dirname($dir);
-       $dir       =~ s|/+$||;
-       $repodir   =~ s|/+$||;
-       $remotedir =~ s|/+$||;
-       $parent    =~ s|/+$||;
-       $log->debug("announcedir $dir, $repodir, $remotedir" );
-
-       if ($parent eq '.' || $parent eq './') {
-           $parent = '';
-       }
-       # recurse to announce unseen parents first
-       if (length($parent) && !exists($seendirs->{$parent})) {
-           prepdir($parent, $repodir, $remotedir, $seendirs);
-       }
-       # Announce that we are going to modify at the parent level
-       if ($parent) {
-           print "E cvs checkout: Updating $remotedir/$parent\n";
-       } else {
-           print "E cvs checkout: Updating $remotedir\n";
-       }
-       print "Clear-sticky $remotedir/$parent/\n";
-       print "$repodir/$parent/\n";
-
-       print "Clear-static-directory $remotedir/$dir/\n";
-       print "$repodir/$dir/\n";
-       print "Clear-sticky $remotedir/$parent/\n"; # yes, twice
-       print "$repodir/$parent/\n";
-       print "Template $remotedir/$dir/\n";
-       print "$repodir/$dir/\n";
-       print "0\n";
-
-       $seendirs->{$dir} = 1;
-    }
-
-    foreach my $git ( @{$updater->gethead} )
+    prepDirForOutput(
+            ".",
+            $state->{CVSROOT} . "/$module",
+            $checkout_path,
+            \%seendirs,
+            'checkout',
+            $state->{dirArgs} );
+
+    foreach my $git ( @{$updater->getAnyHead($headHash)} )
     {
         # Don't want to check out deleted files
         next if ( $git->{filehash} eq "deleted" );
@@ -963,16 +1006,13 @@ sub req_co
         my $fullName = $git->{name};
         ( $git->{name}, $git->{dir} ) = filenamesplit($git->{name});
 
-       if (length($git->{dir}) && $git->{dir} ne './'
-           && $git->{dir} ne $lastdir ) {
-           unless (exists($seendirs{$git->{dir}})) {
-               prepdir($git->{dir}, $state->{CVSROOT} . "/$module/",
-                       $checkout_path, \%seendirs);
-               $lastdir = $git->{dir};
-               $seendirs{$git->{dir}} = 1;
-           }
-           print "E cvs checkout: Updating /$checkout_path/$git->{dir}\n";
-       }
+        unless (exists($seendirs{$git->{dir}})) {
+            prepDirForOutput($git->{dir}, $state->{CVSROOT} . "/$module/",
+                             $checkout_path, \%seendirs, 'checkout',
+                             $state->{dirArgs} );
+            $lastdir = $git->{dir};
+            $seendirs{$git->{dir}} = 1;
+        }
 
         # modification time of this file
         print "Mod-time $git->{modified}\n";
@@ -992,7 +1032,8 @@ 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/" .
+                        getStickyTagOrDate($stickyInfo) . "\n";
         # permissions
         print "u=$git->{mode},g=$git->{mode},o=$git->{mode}\n";
 
@@ -1005,6 +1046,119 @@ sub req_co
     statecleanup();
 }
 
+# used by req_co and req_update to set up directories for files
+# recursively handles parents
+sub prepDirForOutput
+{
+    my ($dir, $repodir, $remotedir, $seendirs, $request, $dirArgs) = @_;
+
+    my $parent = dirname($dir);
+    $dir       =~ s|/+$||;
+    $repodir   =~ s|/+$||;
+    $remotedir =~ s|/+$||;
+    $parent    =~ s|/+$||;
+
+    if ($parent eq '.' || $parent eq './')
+    {
+        $parent = '';
+    }
+    # recurse to announce unseen parents first
+    if( length($parent) &&
+        !exists($seendirs->{$parent}) &&
+        ( $request eq "checkout" ||
+          exists($dirArgs->{$parent}) ) )
+    {
+        prepDirForOutput($parent, $repodir, $remotedir,
+                         $seendirs, $request, $dirArgs);
+    }
+    # Announce that we are going to modify at the parent level
+    if ($dir eq '.' || $dir eq './')
+    {
+        $dir = '';
+    }
+    if(exists($seendirs->{$dir}))
+    {
+        return;
+    }
+    $log->debug("announcedir $dir, $repodir, $remotedir" );
+    my($thisRemoteDir,$thisRepoDir);
+    if ($dir ne "")
+    {
+        $thisRepoDir="$repodir/$dir";
+        if($remotedir eq ".")
+        {
+            $thisRemoteDir=$dir;
+        }
+        else
+        {
+            $thisRemoteDir="$remotedir/$dir";
+        }
+    }
+    else
+    {
+        $thisRepoDir=$repodir;
+        $thisRemoteDir=$remotedir;
+    }
+    unless ( $state->{globaloptions}{-Q} || $state->{globaloptions}{-q} )
+    {
+        print "E cvs $request: Updating $thisRemoteDir\n";
+    }
+
+    my ($opt_r)=$state->{opt}{r};
+    my $stickyInfo;
+    if(exists($state->{opt}{A}))
+    {
+        # $stickyInfo=undef;
+    }
+    elsif( defined($opt_r) && $opt_r ne "" )
+           # || ( defined($state->{opt}{D}) && $state->{opt}{D} ne "" ) # TODO
+    {
+        $stickyInfo={ 'tag' => (defined($opt_r)?$opt_r:undef) };
+
+        # TODO: Convert -D value into the form 2011.04.10.04.46.57,
+        #   similar to an entry line's sticky date, without the D prefix.
+        #   It sometimes (always?) arrives as something more like
+        #   '10 Apr 2011 04:46:57 -0000'...
+        # $stickyInfo={ 'date' => (defined($stickyDate)?$stickyDate:undef) };
+    }
+    else
+    {
+        $stickyInfo=getDirStickyInfo($state->{prependdir} . $dir);
+    }
+
+    my $stickyResponse;
+    if(defined($stickyInfo))
+    {
+        $stickyResponse = "Set-sticky $thisRemoteDir/\n" .
+                          "$thisRepoDir/\n" .
+                          getStickyTagOrDate($stickyInfo) . "\n";
+    }
+    else
+    {
+        $stickyResponse = "Clear-sticky $thisRemoteDir/\n" .
+                          "$thisRepoDir/\n";
+    }
+
+    unless ( $state->{globaloptions}{-n} )
+    {
+        print $stickyResponse;
+
+        print "Clear-static-directory $thisRemoteDir/\n";
+        print "$thisRepoDir/\n";
+        print $stickyResponse; # yes, twice
+        print "Template $thisRemoteDir/\n";
+        print "$thisRepoDir/\n";
+        print "0\n";
+    }
+
+    $seendirs->{$dir} = 1;
+
+    # FUTURE: This would more accurately emulate CVS by sending
+    #   another copy of sticky after processing the files in that
+    #   directory.  Or intermediate: perhaps send all sticky's for
+    #   $seendirs after after processing all files.
+}
+
 # update \n
 #     Response expected: yes. Actually do a cvs update command. This uses any
 #     previous Argument, Directory, Entry, or Modified requests, if they have
@@ -1048,29 +1202,19 @@ sub req_update
 
     #$log->debug("update state : " . Dumper($state));
 
-    my $last_dirname = "///";
+    my($repoDir);
+    $repoDir=$state->{CVSROOT} . "/$state->{module}/$state->{prependdir}";
+
+    my %seendirs = ();
 
     # foreach file specified on the command line ...
-    foreach my $filename ( @{$state->{args}} )
+    foreach my $argsFilename ( @{$state->{args}} )
     {
-        $filename = filecleanup($filename);
+        my $filename;
+        $filename = filecleanup($argsFilename);
 
         $log->debug("Processing file $filename");
 
-        unless ( $state->{globaloptions}{-Q} || $state->{globaloptions}{-q} )
-        {
-            my $cur_dirname = dirname($filename);
-            if ( $cur_dirname ne $last_dirname )
-            {
-                $last_dirname = $cur_dirname;
-                if ( $cur_dirname eq "" )
-                {
-                    $cur_dirname = ".";
-                }
-                print "E cvs update: Updating $cur_dirname\n";
-            }
-        }
-
         # if we have a -C we should pretend we never saw modified stuff
         if ( exists ( $state->{opt}{C} ) )
         {
@@ -1079,13 +1223,11 @@ sub req_update
             $state->{entries}{$filename}{unchanged} = 1;
         }
 
-        my $meta;
-        if ( defined($state->{opt}{r}) and $state->{opt}{r} =~ /^1\.(\d+)/ )
-        {
-            $meta = $updater->getmeta($filename, $1);
-        } else {
-            $meta = $updater->getmeta($filename);
-        }
+        my $stickyInfo = resolveStickyInfo($filename,
+                                           $state->{opt}{r},
+                                           $state->{opt}{D},
+                                           exists($state->{opt}{A}));
+        my $meta = $updater->getmeta($filename, $stickyInfo);
 
         # If -p was given, "print" the contents of the requested revision.
         if ( exists ( $state->{opt}{p} ) ) {
@@ -1098,23 +1240,41 @@ sub req_update
             next;
         }
 
+        # Directories:
+        prepDirForOutput(
+                dirname($argsFilename),
+                $repoDir,
+                ".",
+                \%seendirs,
+                "update",
+                $state->{dirArgs} );
+
+        my $wrev = revparse($filename);
+
 	if ( ! defined $meta )
 	{
 	    $meta = {
 	        name => $filename,
-	        revision => 0,
+	        revision => '0',
 	        filehash => 'added'
 	    };
+	    if($wrev ne "0")
+	    {
+	        $meta->{filehash}='deleted';
+	    }
 	}
 
         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} )
+        my $oldWrev=$wrev;
+        if(defined($oldWrev))
         {
-            $oldmeta = $updater->getmeta($filename, $wrev);
+            $oldWrev=~s/^-//;
+            if($oldWrev ne $meta->{revision})
+            {
+                $oldmeta = $updater->getmeta($filename, $oldWrev);
+            }
         }
 
         #$log->debug("Target revision is $meta->{revision}, current working revision is $wrev");
@@ -1123,7 +1283,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 +1291,8 @@ 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 $wrev ne "0"
              and defined($state->{entries}{$filename}{modified_hash})
              and not exists ( $state->{opt}{C} ) )
         {
@@ -1142,8 +1303,12 @@ sub req_update
             next;
         }
 
-        if ( $meta->{filehash} eq "deleted" )
+        if ( $meta->{filehash} eq "deleted" && $wrev ne "0" )
         {
+            # 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 +1326,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 +1334,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 +1345,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";
@@ -1199,10 +1365,6 @@ sub req_update
 		    $log->debug("Updating existing file 'Update-existing $dirpart'");
 		} else {
 		    # instruct client we're sending a file to put in this path as a new file
-		    print "Clear-static-directory $dirpart\n";
-		    print $state->{CVSROOT} . "/$state->{module}/$dirpart\n";
-		    print "Clear-sticky $dirpart\n";
-		    print $state->{CVSROOT} . "/$state->{module}/$dirpart\n";
 
 		    $log->debug("Creating new file 'Created $dirpart'");
 		    print "Created $dirpart\n";
@@ -1211,8 +1373,10 @@ 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";
+                my $entriesLine = "/$filepart/$meta->{revision}//$kopts/";
+                $entriesLine .= getStickyTagOrDate($stickyInfo);
+		$log->debug($entriesLine);
+		print "$entriesLine\n";
 
 		# permissions
 		$log->debug("SEND : u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}");
@@ -1222,7 +1386,6 @@ sub req_update
 		transmitfile($meta->{filehash});
 	    }
         } else {
-            $log->info("Updating '$filename'");
             my ( $filepart, $dirpart ) = filenamesplit($meta->{name},1);
 
             my $mergeDir = setupTmpDir();
@@ -1237,7 +1400,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 +1423,10 @@ 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/");
+                    my $entriesLine="/$filepart/$meta->{revision}//$kopts/";
+                    $entriesLine .= getStickyTagOrDate($stickyInfo);
+                    print "$entriesLine\n";
                 }
             }
             elsif ( $return == 1 )
@@ -1277,7 +1442,9 @@ 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";
+                    my $entriesLine = "/$filepart/$meta->{revision}/+/$kopts/";
+                    $entriesLine .= getStickyTagOrDate($stickyInfo);
+                    print "$entriesLine\n";
                 }
             }
             else
@@ -1305,6 +1472,43 @@ sub req_update
 
     }
 
+    # prepDirForOutput() any other existing directories unless they already
+    # have the right sticky tag:
+    unless ( $state->{globaloptions}{n} )
+    {
+        my $dir;
+        foreach $dir (keys(%{$state->{dirMap}}))
+        {
+            if( ! $seendirs{$dir} &&
+                exists($state->{dirArgs}{$dir}) )
+            {
+                my($oldTag);
+                $oldTag=$state->{dirMap}{$dir}{tagspec};
+
+                unless( ( exists($state->{opt}{A}) &&
+                          defined($oldTag) ) ||
+                          ( defined($state->{opt}{r}) &&
+                            ( !defined($oldTag) ||
+                              $state->{opt}{r} ne $oldTag ) ) )
+                        # TODO?: OR sticky dir is different...
+                {
+                    next;
+                }
+
+                prepDirForOutput(
+                        $dir,
+                        $repoDir,
+                        ".",
+                        \%seendirs,
+                        'update',
+                        $state->{dirArgs} );
+            }
+
+            # TODO?: Consider sending a final duplicate Sticky response
+            #   to more closely mimic real CVS.
+        }
+    }
+
     print "ok\n";
 }
 
@@ -1337,23 +1541,11 @@ sub req_ci
     my $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log);
     $updater->update();
 
-    # Remember where the head was at the beginning.
-    my $parenthash = `git show-ref -s refs/heads/$state->{module}`;
-    chomp $parenthash;
-    if ($parenthash !~ /^[0-9a-f]{40}$/) {
-	    print "error 1 pserver cannot find the current HEAD of module";
-	    cleanupWorkTree();
-	    exit;
-    }
-
-    setupWorkTree($parenthash);
-
-    $log->info("Lockless commit start, basing commit on '$work->{workDir}', index file is '$work->{index}'");
-
-    $log->info("Created index '$work->{index}' for head $state->{module} - exit status $?");
-
     my @committedfiles = ();
     my %oldmeta;
+    my $stickyInfo;
+    my $branchRef;
+    my $parenthash;
 
     # foreach file specified on the command line ...
     foreach my $filename ( @{$state->{args}} )
@@ -1363,7 +1555,67 @@ sub req_ci
 
         next unless ( exists $state->{entries}{$filename}{modified_filename} or not $state->{entries}{$filename}{unchanged} );
 
-        my $meta = $updater->getmeta($filename);
+        #####
+        # Figure out which branch and parenthash we are committing
+        # to, and setup worktree:
+
+        # should always come from entries:
+        my $fileStickyInfo = resolveStickyInfo($filename);
+        if( !defined($branchRef) )
+        {
+            $stickyInfo = $fileStickyInfo;
+            if( defined($stickyInfo) &&
+                ( defined($stickyInfo->{date}) ||
+                  !defined($stickyInfo->{tag}) ) )
+            {
+                print "error 1 cannot commit with sticky date for file `$filename'\n";
+                cleanupWorkTree();
+                exit;
+            }
+
+            $branchRef = "refs/heads/$state->{module}";
+            if ( defined($stickyInfo) && defined($stickyInfo->{tag}) )
+            {
+                $branchRef = "refs/heads/$stickyInfo->{tag}";
+            }
+
+            $parenthash = `git show-ref -s $branchRef`;
+            chomp $parenthash;
+            if ($parenthash !~ /^[0-9a-f]{40}$/)
+            {
+                if ( defined($stickyInfo) && defined($stickyInfo->{tag}) )
+                {
+                    print "error 1 sticky tag `$stickyInfo->{tag}' for file `$filename' is not a branch\n";
+                }
+                else
+                {
+                    print "error 1 pserver cannot find the current HEAD of module";
+                }
+                cleanupWorkTree();
+                exit;
+            }
+
+            setupWorkTree($parenthash);
+
+            $log->info("Lockless commit start, basing commit on '$work->{workDir}', index file is '$work->{index}'");
+
+            $log->info("Created index '$work->{index}' for head $state->{module} - exit status $?");
+        }
+        elsif( !refHashEqual($stickyInfo,$fileStickyInfo) )
+        {
+            #TODO: We could split the cvs commit into multiple
+            #  git commits by distinct stickyTag values, but that
+            #  is lowish priority.
+            print "error 1 Committing different files to different"
+                  . " branches is not currently supported\n";
+            cleanupWorkTree();
+            exit;
+        }
+
+        #####
+        # Process this file:
+
+        my $meta = $updater->getmeta($filename,$stickyInfo);
 	$oldmeta{$filename} = $meta;
 
         my $wrev = revparse($filename);
@@ -1380,11 +1632,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 +1674,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);
         }
     }
@@ -1464,7 +1717,7 @@ sub req_ci
     }
 
 	### Emulate git-receive-pack by running hooks/update
-	my @hook = ( $ENV{GIT_DIR}.'hooks/update', "refs/heads/$state->{module}",
+	my @hook = ( $ENV{GIT_DIR}.'hooks/update', $branchRef,
 			$parenthash, $commithash );
 	if( -x $hook[0] ) {
 		unless( system( @hook ) == 0 )
@@ -1478,7 +1731,7 @@ sub req_ci
 
 	### Update the ref
 	if (system(qw(git update-ref -m), "cvsserver ci",
-			"refs/heads/$state->{module}", $commithash, $parenthash)) {
+			$branchRef, $commithash, $parenthash)) {
 		$log->warn("update-ref for $state->{module} failed.");
 		print "error 1 Cannot commit -- update first\n";
 		cleanupWorkTree();
@@ -1492,7 +1745,7 @@ sub req_ci
 
 		local $SIG{PIPE} = sub { die 'pipe broke' };
 
-		print $pipe "$parenthash $commithash refs/heads/$state->{module}\n";
+		print $pipe "$parenthash $commithash $branchRef\n";
 
 		close $pipe || die "bad pipe: $! $?";
 	}
@@ -1502,7 +1755,7 @@ sub req_ci
 	### Then hooks/post-update
 	$hook = $ENV{GIT_DIR}.'hooks/post-update';
 	if (-x $hook) {
-		system($hook, "refs/heads/$state->{module}");
+		system($hook, $branchRef);
 	}
 
     # foreach file specified on the command line ...
@@ -1510,9 +1763,9 @@ sub req_ci
     {
         $filename = filecleanup($filename);
 
-        my $meta = $updater->getmeta($filename);
+        my $meta = $updater->getmeta($filename,$stickyInfo);
 	unless (defined $meta->{revision}) {
-	  $meta->{revision} = 1;
+	  $meta->{revision} = "1.1";
 	}
 
         my ( $filepart, $dirpart ) = filenamesplit($filename, 1);
@@ -1522,19 +1775,20 @@ 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/" .
+                  getStickyTagOrDate($stickyInfo) . "\n";
         }
     }
 
@@ -1552,10 +1806,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 +1819,140 @@ sub req_status
     {
         $filename = filecleanup($filename);
 
-        next if exists($state->{opt}{l}) && index($filename, '/', length($state->{prependdir})) >= 0;
-
-        my $meta = $updater->getmeta($filename);
-        my $oldmeta = $meta;
+        if ( exists($state->{opt}{l}) &&
+             index($filename, '/', length($state->{prependdir})) >= 0 )
+        {
+           next;
+        }
 
         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} )
+        my $stickyInfo = resolveStickyInfo($filename);
+        my $meta = $updater->getmeta($filename,$stickyInfo);
+        my $oldmeta = $meta;
+
+        # 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);
+            my($rmRev)=$wrev;
+            $rmRev=~s/^-//;
+            $oldmeta = $updater->getmeta($filename, $rmRev);
         }
 
         # 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
+             $wrev ne "0" 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
+             ( !defined($meta->{revision}) ||
+               $meta->{revision} eq "0" ) )
+        {
+            $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,96 +1980,149 @@ 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);
 
+    my($foundDiff);
+
     # foreach file specified on the command line ...
-    foreach my $filename ( @{$state->{args}} )
+    foreach my $argFilename ( @{$state->{args}} )
     {
-        $filename = filecleanup($filename);
+        my($filename) = filecleanup($argFilename);
 
         my ( $fh, $file1, $file2, $meta1, $meta2, $filediff );
 
         my $wrev = revparse($filename);
 
-        # We need _something_ to diff against
-        next unless ( defined ( $wrev ) );
+        # Priority for revision1:
+        #  1. First -r (missing file: check -N)
+        #  2. wrev from client's Entry line
+        #      - missing line/file: check -N
+        #      - "0": added file not committed (empty contents for rev1)
+        #      - Prefixed with dash (to be removed): check -N
 
-        # if we have a -r switch, use it
         if ( defined ( $revision1 ) )
         {
-            ( undef, $file1 ) = tempfile( DIR => $TEMP_DIR, OPEN => 0 );
             $meta1 = $updater->getmeta($filename, $revision1);
-            unless ( defined ( $meta1 ) and $meta1->{filehash} ne "deleted" )
+        }
+        elsif( defined($wrev) && $wrev ne "0" )
+        {
+            my($rmRev)=$wrev;
+            $rmRev=~s/^-//;
+            $meta1 = $updater->getmeta($filename, $rmRev);
+        }
+        if ( !defined($meta1) ||
+             $meta1->{filehash} eq "deleted" )
+        {
+            if( !exists($state->{opt}{N}) )
             {
-                print "E File $filename at revision 1.$revision1 doesn't exist\n";
+                if(!defined($revision1))
+                {
+                    print "E File $filename at revision $revision1 doesn't exist\n";
+                }
                 next;
             }
-            transmitfile($meta1->{filehash}, { targetfile => $file1 });
-        }
-        # otherwise we just use the working copy revision
-        else
-        {
-            ( undef, $file1 ) = tempfile( DIR => $TEMP_DIR, OPEN => 0 );
-            $meta1 = $updater->getmeta($filename, $wrev);
-            transmitfile($meta1->{filehash}, { targetfile => $file1 });
+            elsif( !defined($meta1) )
+            {
+                $meta1 = {
+                    name => $filename,
+                    revision => '0',
+                    filehash => 'deleted'
+                };
+            }
         }
 
+        # Priority for revision2:
+        #  1. Second -r (missing file: check -N)
+        #  2. Modified file contents from client
+        #  3. wrev from client's Entry line
+        #      - missing line/file: check -N
+        #      - Prefixed with dash (to be removed): check -N
+
         # if we have a second -r switch, use it too
         if ( defined ( $revision2 ) )
         {
-            ( undef, $file2 ) = tempfile( DIR => $TEMP_DIR, OPEN => 0 );
             $meta2 = $updater->getmeta($filename, $revision2);
-
-            unless ( defined ( $meta2 ) and $meta2->{filehash} ne "deleted" )
+        }
+        elsif(defined($state->{entries}{$filename}{modified_filename}))
+        {
+            $file2 = $state->{entries}{$filename}{modified_filename};
+	    $meta2 = {
+                name => $filename,
+	        revision => '0',
+	        filehash => 'modified'
+            };
+        }
+        elsif( defined($wrev) && ($wrev!~/^-/) )
+        {
+            if(!defined($revision1))  # no revision and no modifications:
             {
-                print "E File $filename at revision 1.$revision2 doesn't exist\n";
                 next;
             }
-
-            transmitfile($meta2->{filehash}, { targetfile => $file2 });
+            $meta2 = $updater->getmeta($filename, $wrev);
         }
-        # otherwise we just use the working copy
-        else
+        if(!defined($file2))
         {
-            $file2 = $state->{entries}{$filename}{modified_filename};
+            if ( !defined($meta2) ||
+                 $meta2->{filehash} eq "deleted" )
+            {
+                if( !exists($state->{opt}{N}) )
+                {
+                    if(!defined($revision2))
+                    {
+                        print "E File $filename at revision $revision2 doesn't exist\n";
+                    }
+                    next;
+                }
+                elsif( !defined($meta2) )
+                {
+	            $meta2 = {
+                        name => $filename,
+	                revision => '0',
+	                filehash => 'deleted'
+                    };
+                }
+            }
         }
 
-        # if we have been given -r, and we don't have a $file2 yet, lets get one
-        if ( defined ( $revision1 ) and not defined ( $file2 ) )
+        if( $meta1->{filehash} eq $meta2->{filehash} )
         {
-            ( undef, $file2 ) = tempfile( DIR => $TEMP_DIR, OPEN => 0 );
-            $meta2 = $updater->getmeta($filename, $wrev);
-            transmitfile($meta2->{filehash}, { targetfile => $file2 });
+            $log->info("unchanged $filename");
+            next;
         }
 
-        # 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} ) )
-                  );
+        # Retrieve revision contents:
+        ( undef, $file1 ) = tempfile( DIR => $TEMP_DIR, OPEN => 0 );
+        transmitfile($meta1->{filehash}, { targetfile => $file1 });
 
-        # Apparently we only show diffs for locally modified files
-        next unless ( defined($meta2) or defined ( $state->{entries}{$filename}{modified_filename} ) );
+        if(!defined($file2))
+        {
+            ( undef, $file2 ) = tempfile( DIR => $TEMP_DIR, OPEN => 0 );
+            transmitfile($meta2->{filehash}, { targetfile => $file2 });
+        }
 
-        print "M Index: $filename\n";
-        print "M ===================================================================\n";
+        # Generate the actual diff:
+        print "M Index: $argFilename\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 ) && $meta1->{revision} ne "0" )
+        {
+            print "M retrieving revision $meta1->{revision}\n"
+        }
+        if ( defined ( $meta2 ) && $meta2->{revision} ne "0" )
+        {
+            print "M retrieving revision $meta2->{revision}\n"
+        }
         print "M diff ";
         foreach my $opt ( keys %{$state->{opt}} )
         {
@@ -1752,30 +2134,79 @@ 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" ));
-
-        ( $fh, $filediff ) = tempfile ( DIR => $TEMP_DIR );
-
-        if ( exists $state->{opt}{u} )
+        print "$argFilename\n";
+
+        $log->info("Diffing $filename -r $meta1->{revision} -r " .
+                   ( $meta2->{revision} or "workingcopy" ));
+
+        # TODO: Use --label instead of -L because -L is no longer
+        #  documented and may go away someday.  Not sure if there there are
+        #  versions that only support -L, which would make this change risky?
+        #  http://osdir.com/ml/bug-gnu-utils-gnu/2010-12/msg00060.html
+        #    ("man diff" should actually document the best migration strategy,
+        #  [current behavior, future changes, old compatibility issues
+        #  or lack thereof, etc], not just stop mentioning the option...)
+        # TODO: Real CVS seems to include a date in the label, before
+        #  the revision part, without the keyword "revision".  The following
+        #  has minimal changes compared to original versions of
+        #  git-cvsserver.perl.  (Mostly tab vs space after filename.)
+
+        my (@diffCmd) = ( 'diff' );
+        if ( exists($state->{opt}{N}) )
         {
-            system("diff -u -L '$filename revision 1.$meta1->{revision}' -L '$filename " . ( defined($meta2->{revision}) ? "revision 1.$meta2->{revision}" : "working copy" ) . "' $file1 $file2 > $filediff");
-        } else {
-            system("diff $file1 $file2 > $filediff");
+            push @diffCmd,"-N";
         }
-
-        while ( <$fh> )
+        if ( exists $state->{opt}{u} )
         {
-            print "M $_";
-        }
-        close $fh;
+            push @diffCmd,("-u","-L");
+            if( $meta1->{filehash} eq "deleted" )
+            {
+                push @diffCmd,"/dev/null";
+            } else {
+                push @diffCmd,("$argFilename\trevision $meta1->{revision}");
+            }
+
+            if( defined($meta2->{filehash}) )
+            {
+                if( $meta2->{filehash} eq "deleted" )
+                {
+                    push @diffCmd,("-L","/dev/null");
+                } else {
+                    push @diffCmd,("-L",
+                                   "$argFilename\trevision $meta2->{revision}");
+                }
+            } else {
+                push @diffCmd,("-L","$argFilename\tworking copy");
+            }
+        }
+        push @diffCmd,($file1,$file2);
+        if(!open(DIFF,"-|",@diffCmd))
+        {
+            $log->warn("Unable to run diff: $!");
+        }
+        my($diffLine);
+        while(defined($diffLine=))
+        {
+            print "M $diffLine";
+            $foundDiff=1;
+        }
+        close(DIFF);
     }
 
-    print "ok\n";
+    if($foundDiff)
+    {
+        print "error  \n";
+    }
+    else
+    {
+        print "ok\n";
+    }
 }
 
 sub req_log
@@ -1787,22 +2218,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 +2240,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 +2385,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},
@@ -2005,7 +2426,7 @@ sub argsplit
         $opt = { A => 0, N => 0, P => 0, R => 0, c => 0, f => 0, l => 0, n => 0, p => 0, s => 0, r => 1, D => 1, d => 1, k => 1, j => 1, } if ( $type eq "co" );
         $opt = { v => 0, l => 0, R => 0 } if ( $type eq "status" );
         $opt = { A => 0, P => 0, C => 0, d => 0, f => 0, l => 0, R => 0, p => 0, k => 1, r => 1, D => 1, j => 1, I => 1, W => 1 } if ( $type eq "update" );
-        $opt = { l => 0, R => 0, k => 1, D => 1, D => 1, r => 2 } if ( $type eq "diff" );
+        $opt = { l => 0, R => 0, k => 1, D => 1, D => 1, r => 2, N => 0 } if ( $type eq "diff" );
         $opt = { c => 0, R => 0, l => 0, f => 0, F => 1, m => 1, r => 1 } if ( $type eq "ci" );
         $opt = { k => 1, m => 1 } if ( $type eq "add" );
         $opt = { f => 0, l => 0, R => 0 } if ( $type eq "remove" );
@@ -2071,74 +2492,346 @@ sub argsplit
     }
 }
 
-# This method uses $state->{directory} to populate $state->{args} with a list of filenames
-sub argsfromdir
+# Used by argsfromdir
+sub expandArg
 {
-    my $updater = shift;
+    my ($updater,$outNameMap,$outDirMap,$path,$isDir) = @_;
 
-    $state->{args} = [] if ( scalar(@{$state->{args}}) == 1 and $state->{args}[0] eq "." );
+    my $fullPath = filecleanup($path);
 
-    return if ( scalar ( @{$state->{args}} ) > 1 );
+      # Is it a directory?
+    if( defined($state->{dirMap}{$fullPath}) ||
+        defined($state->{dirMap}{"$fullPath/"}) )
+    {
+          # It is a directory in the user's sandbox.
+        $isDir=1;
 
-    my @gethead = @{$updater->gethead};
+        if(defined($state->{entries}{$fullPath}))
+        {
+            $log->fatal("Inconsistent file/dir type");
+            die "Inconsistent file/dir type";
+        }
+    }
+    elsif(defined($state->{entries}{$fullPath}))
+    {
+          # It is a file in the user's sandbox.
+        $isDir=0;
+    }
+    my($revDirMap,$otherRevDirMap);
+    if(!defined($isDir) || $isDir)
+    {
+          # Resolve version tree for sticky tag:
+          # (for now we only want list of files for the version, not
+          # particular versions of those files: assume it is a directory
+          # for the moment; ignore Entry's stick tag)
+
+          # Order of precedence of sticky tags:
+          #    -A       [head]
+          #    -r /tag/
+          #    [file entry sticky tag, but that is only relevant to files]
+          #    [the tag specified in dir req_Sticky]
+          #    [the tag specified in a parent dir req_Sticky]
+          #    [head]
+          # Also, -r may appear twice (for diff).
+          #
+          # FUTURE: When/if -j (merges) are supported, we also
+          #  need to add relevant files from one or two
+          #  versions specified with -j.
+
+        if(exists($state->{opt}{A}))
+        {
+            $revDirMap=$updater->getRevisionDirMap();
+        }
+        elsif( defined($state->{opt}{r}) and
+               ref $state->{opt}{r} eq "ARRAY" )
+        {
+            $revDirMap=$updater->getRevisionDirMap($state->{opt}{r}[0]);
+            $otherRevDirMap=$updater->getRevisionDirMap($state->{opt}{r}[1]);
+        }
+        elsif(defined($state->{opt}{r}))
+        {
+            $revDirMap=$updater->getRevisionDirMap($state->{opt}{r});
+        }
+        else
+        {
+            my($sticky)=getDirStickyInfo($fullPath);
+            $revDirMap=$updater->getRevisionDirMap($sticky->{tag});
+        }
 
-    # push added files
-    foreach my $file (keys %{$state->{entries}}) {
-	if ( exists $state->{entries}{$file}{revision} &&
-		$state->{entries}{$file}{revision} == 0 )
-	{
-	    push @gethead, { name => $file, filehash => 'added' };
-	}
+          # Is it a directory?
+        if( defined($revDirMap->{$fullPath}) ||
+            defined($otherRevDirMap->{$fullPath}) )
+        {
+            $isDir=1;
+        }
     }
 
-    if ( scalar(@{$state->{args}}) == 1 )
+      # What to do with it?
+    if(!$isDir)
+    {
+        $outNameMap->{$fullPath}=1;
+    }
+    else
     {
-        my $arg = $state->{args}[0];
-        $arg .= $state->{prependdir} if ( defined ( $state->{prependdir} ) );
+        $outDirMap->{$fullPath}=1;
+
+        if(defined($revDirMap->{$fullPath}))
+        {
+            addDirMapFiles($updater,$outNameMap,$outDirMap,
+                           $revDirMap->{$fullPath});
+        }
+        if( defined($otherRevDirMap) &&
+            defined($otherRevDirMap->{$fullPath}) )
+        {
+            addDirMapFiles($updater,$outNameMap,$outDirMap,
+                           $otherRevDirMap->{$fullPath});
+        }
+    }
+}
 
-        $log->info("Only one arg specified, checking for directory expansion on '$arg'");
+# Used by argsfromdir
+# Add entries from dirMap to outNameMap.  Also recurse into entries
+# that are subdirectories.
+sub addDirMapFiles
+{
+    my($updater,$outNameMap,$outDirMap,$dirMap)=@_;
 
-        foreach my $file ( @gethead )
+    my($fullName);
+    foreach $fullName (keys(%$dirMap))
+    {
+        my $cleanName=$fullName;
+        if(defined($state->{prependdir}))
         {
-            next if ( $file->{filehash} eq "deleted" and not defined ( $state->{entries}{$file->{name}} ) );
-            next unless ( $file->{name} =~ /^$arg\// or $file->{name} eq $arg  );
-            push @{$state->{args}}, $file->{name};
+            if(!($cleanName=~s/^\Q$state->{prependdir}\E//))
+            {
+                $log->fatal("internal error stripping prependdir");
+                die "internal error stripping prependdir";
+            }
         }
 
-        shift @{$state->{args}} if ( scalar(@{$state->{args}}) > 1 );
-    } else {
-        $log->info("Only one arg specified, populating file list automatically");
+        if($dirMap->{$fullName} eq "F")
+        {
+            $outNameMap->{$cleanName}=1;
+        }
+        elsif($dirMap->{$fullName} eq "D")
+        {
+            if(!$state->{opt}{l})
+            {
+                expandArg($updater,$outNameMap,$outDirMap,$cleanName,1);
+            }
+        }
+        else
+        {
+            $log->fatal("internal error in addDirMapFiles");
+            die "internal error in addDirMapFiles";
+        }
+    }
+}
+
+# This method replaces $state->{args} with a directory-expanded
+# list of all relevant filenames (recursively unless -d), based
+# on $state->{entries}, and the "current" list of files in
+# each directory.  "Current" files as determined by
+# either the requested (-r/-A) or "req_Sticky" version of
+# that directory.
+#    Both the input args and the new output args are relative
+# to the cvs-client's CWD, although some of the internal
+# computations are relative to the top of the project.
+sub argsfromdir
+{
+    my $updater = shift;
+
+    # Notes about requirements for specific callers:
+    #   update # "standard" case (entries; a single -r/-A/default; -l)
+    #          # Special case: -d for create missing directories.
+    #   diff # 0 or 1 -r's: "standard" case.
+    #        # 2 -r's: We could ignore entries (just use the two -r's),
+    #        # but it doesn't really matter.
+    #   annotate # "standard" case
+    #   log # Punting: log -r has a more complex non-"standard"
+    #       # meaning, and we don't currently try to support log'ing
+    #       # branches at all (need a lot of work to
+    #       # support CVS-consistent branch relative version
+    #       # numbering).
+#HERE: But we still want to expand directories.  Maybe we should
+#  essentially force "-A".
+    #   status # "standard", except that -r/-A/default are not possible.
+    #          # Mostly only used to expand entries only)
+    #
+    # Don't use argsfromdir at all:
+    #   add # Explicit arguments required.  Directory args imply add
+    #       # the directory itself, not the files in it.
+    #   co  # Obtain list directly.
+    #   remove # HERE: TEST: MAYBE client does the recursion for us,
+    #          # since it only makes sense to remove stuff already in
+    #          # the sandobx?
+    #   ci # HERE: Similar to remove...
+    #      # Don't try to implement the confusing/weird
+    #      # ci -r bug er.."feature".
+
+    if(scalar(@{$state->{args}})==0)
+    {
+        $state->{args} = [ "." ];
+    }
+    my %allArgs;
+    my %allDirs;
+    for my $file (@{$state->{args}})
+    {
+        expandArg($updater,\%allArgs,\%allDirs,$file);
+    }
+
+    # Include any entries from sandbox.  Generally client won't
+    # send entries that shouldn't be used.
+    foreach my $file (keys %{$state->{entries}})
+    {
+        $allArgs{remove_prependdir($file)} = 1;
+    }
+
+    $state->{dirArgs} = \%allDirs;
+    $state->{args} = [
+        sort {
+                # Sort priority: by directory depth, then actual file name:
+            my @piecesA=split('/',$a);
+            my @piecesB=split('/',$b);
+
+            my $count=scalar(@piecesA);
+            my $tmp=scalar(@piecesB);
+            return $count<=>$tmp if($count!=$tmp);
+
+            for($tmp=0;$tmp<$count;$tmp++)
+            {
+                if($piecesA[$tmp] ne $piecesB[$tmp])
+                {
+                    return $piecesA[$tmp] cmp $piecesB[$tmp]
+                }
+            }
+            return 0;
+        } keys(%allArgs) ];
+}
+
+## look up directory sticky tag, of either fullPath or a parent:
+sub getDirStickyInfo
+{
+    my($fullPath)=@_;
+
+    $fullPath=~s%/+$%%;
+    while($fullPath ne "" && !defined($state->{dirMap}{"$fullPath/"}))
+    {
+        $fullPath=~s%/?[^/]*$%%;
+    }
+
+    if( !defined($state->{dirMap}{"$fullPath/"}) &&
+        ( $fullPath eq "" ||
+          $fullPath eq "." ) )
+    {
+        return $state->{dirMap}{""}{stickyInfo};
+    }
+    else
+    {
+        return $state->{dirMap}{"$fullPath/"}{stickyInfo};
+    }
+}
 
-        $state->{args} = [];
+# Resolve precedence of various ways of specifying which version of
+# a file you want.  Returns undef (for default head), or a ref to a hash
+# that contains "tag" and/or "date" keys.
+sub resolveStickyInfo
+{
+    my($filename,$stickyTag,$stickyDate,$reset) = @_;
+
+    # Order of precedence of sticky tags:
+    #    -A       [head]
+    #    -r /tag/
+    #    [file entry sticky tag]
+    #    [the tag specified in dir req_Sticky]
+    #    [the tag specified in a parent dir req_Sticky]
+    #    [head]
+
+    my $result;
+    if($reset)
+    {
+        # $result=undef;
+    }
+    elsif( defined($stickyTag) && $stickyTag ne "" )
+           # || ( defined($stickyDate) && $stickyDate ne "" )   # TODO
+    {
+        $result={ 'tag' => (defined($stickyTag)?$stickyTag:undef) };
 
-        foreach my $file ( @gethead )
+        # TODO: Convert -D value into the form 2011.04.10.04.46.57,
+        #   similar to an entry line's sticky date, without the D prefix.
+        #   It sometimes (always?) arrives as something more like
+        #   '10 Apr 2011 04:46:57 -0000'...
+        # $result={ 'date' => (defined($stickyDate)?$stickyDate:undef) };
+    }
+    elsif( defined($state->{entries}{$filename}) &&
+           defined($state->{entries}{$filename}{tag_or_date}) &&
+           $state->{entries}{$filename}{tag_or_date} ne "" )
+    {
+        my($tagOrDate)=$state->{entries}{$filename}{tag_or_date};
+        if($tagOrDate=~/^T([^ ]+)\s*$/)
+        {
+            $result = { 'tag' => $1 };
+        }
+        elsif($tagOrDate=~/^D([0-9.]+)\s*$/)
+        {
+            $result= { 'date' => $1 };
+        }
+        else
         {
-            next if ( $file->{filehash} eq "deleted" and not defined ( $state->{entries}{$file->{name}} ) );
-            next unless ( $file->{name} =~ s/^$state->{prependdir}// );
-            push @{$state->{args}}, $file->{name};
+            die "Unknown tag_or_date format\n";
         }
     }
+    else
+    {
+        $result=getDirStickyInfo($filename);
+    }
+
+    return $result;
+}
+
+# Convert a stickyInfo (ref to a hash) as returned by resolveStickyInfo into
+# a form appropriate for the sticky tag field of an Entries
+# line (field index 5, 0-based).
+sub getStickyTagOrDate
+{
+    my($stickyInfo)=@_;
+
+    my $result;
+    if(defined($stickyInfo) && defined($stickyInfo->{tag}))
+    {
+        $result="T$stickyInfo->{tag}";
+    }
+    # TODO: When/if we actually pick versions by {date} properly,
+    #   also handle it here:
+    #   "D$stickyInfo->{date}" (example: "D2011.04.13.20.37.07").
+    else
+    {
+        $result="";
+    }
+
+    return $result;
 }
 
 # This method cleans up the $state variable after a command that uses arguments has run
 sub statecleanup
 {
     $state->{files} = [];
+    $state->{dirArgs} = {};
     $state->{args} = [];
     $state->{arguments} = [];
     $state->{entries} = {};
+    $state->{dirMap} = {};
 }
 
+# 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
@@ -2217,6 +2910,9 @@ sub filenamesplit
     return ( $filepart, $dirpart );
 }
 
+# Cleanup various junk in filename (try to canonicalize it), and
+# add prependdir to accomodate running CVS client from a
+# subdirectory (so the output is relative to top directory of the project).
 sub filecleanup
 {
     my $filename = shift;
@@ -2228,11 +2924,36 @@ sub filecleanup
         return undef;
     }
 
+    if($filename eq ".")
+    {
+        $filename="";
+    }
     $filename =~ s/^\.\///g;
+    $filename =~ s%/+%/%g;
     $filename = $state->{prependdir} . $filename;
+    $filename =~ s%/$%%;
     return $filename;
 }
 
+# Remove prependdir from the path, so that is is relative to the directory
+# the CVS client was started from, rather than the top of the project.
+# Essentially the inverse of filecleanup().
+sub remove_prependdir
+{
+    my($path) = @_;
+    if(defined($state->{prependdir}) && $state->{prependdir} ne "")
+    {
+        my($pre)=$state->{prependdir};
+        $pre=~s%/$%%;
+        if(!($path=~s%^\Q$pre\E/?%%))
+        {
+            $log->fatal("internal error missing prependdir");
+            die("internal error missing prependdir");
+        }
+    }
+    return $path;
+}
+
 sub validateGitDir
 {
     if( !defined($state->{CVSROOT}) )
@@ -2444,42 +3165,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 +3279,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}$/ )
         {
@@ -2674,6 +3367,45 @@ sub descramble
     return $ret;
 }
 
+# Test if the (deep) values of two references to a hash are the same.
+sub refHashEqual
+{
+    my($v1,$v2) = @_;
+
+    my $out;
+    if(!defined($v1))
+    {
+        if(!defined($v2))
+        {
+            $out=1;
+        }
+    }
+    elsif( !defined($v2) ||
+           scalar(keys(%{$v1})) != scalar(keys(%{$v2})) )
+    {
+        # $out=undef;
+    }
+    else
+    {
+        $out=1;
+
+        my $key;
+        foreach $key (keys(%{$v1}))
+        {
+            if( !exists($v2->{$key}) ||
+                defined($v1->{$key}) ne defined($v2->{$key}) ||
+                ( defined($v1->{$key}) &&
+                  $v1->{$key} ne $v2->{$key} ) )
+            {
+               $out=undef;
+               last;
+            }
+        }
+    }
+
+    return $out;
+}
+
 
 package GITCVS::log;
 
@@ -2894,6 +3626,9 @@ sub new
 
     die "Git repo '$self->{git_path}' doesn't exist" unless ( -d $self->{git_path} );
 
+    # Stores full sha1's for various branch/tag names, abbreviations, etc:
+    $self->{commitRefCache} = {};
+
     $self->{dbdriver} = $cfg->{gitcvs}{$state->{method}}{dbdriver} ||
         $cfg->{gitcvs}{dbdriver} || "SQLite";
     $self->{dbname} = $cfg->{gitcvs}{$state->{method}}{dbname} ||
@@ -2929,6 +3664,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 +3701,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 +3732,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 +3745,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 +3781,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
 {
@@ -3043,6 +3811,8 @@ sub update
     my $lastcommit = $self->_get_prop("last_commit");
 
     if (defined $lastcommit && $lastcommit eq $commitsha1) { # up-to-date
+         # invalidate the gethead cache
+         $self->clearCommitRefCaches();
          return 1;
     }
 
@@ -3060,45 +3830,10 @@ sub update
         push @git_log_params, $self->{module};
     }
     # git-rev-list is the backend / plumbing version of git-log
-    open(GITLOG, '-|', 'git', 'rev-list', @git_log_params) or die "Cannot call git-rev-list: $!";
-
-    my @commits;
-
-    my %commit = ();
-
-    while (  )
-    {
-        chomp;
-        if (m/^commit\s+(.*)$/) {
-            # on ^commit lines put the just seen commit in the stack
-            # and prime things for the next one
-            if (keys %commit) {
-                my %copy = %commit;
-                unshift @commits, \%copy;
-                %commit = ();
-            }
-            my @parents = split(m/\s+/, $1);
-            $commit{hash} = shift @parents;
-            $commit{parents} = \@parents;
-        } elsif (m/^(\w+?):\s+(.*)$/ && !exists($commit{message})) {
-            # on rfc822-like lines seen before we see any message,
-            # lowercase the entry and put it in the hash as key-value
-            $commit{lc($1)} = $2;
-        } else {
-            # message lines - skip initial empty line
-            # and trim whitespace
-            if (!exists($commit{message}) && m/^\s*$/) {
-                # define it to mark the end of headers
-                $commit{message} = '';
-                next;
-            }
-            s/^\s+//; s/\s+$//; # trim ws
-            $commit{message} .= $_ . "\n";
-        }
-    }
-    close GITLOG;
-
-    unshift @commits, \%commit if ( keys %commit );
+    open(my $gitLogPipe, '-|', 'git', 'rev-list', @git_log_params)
+                or die "Cannot call git-rev-list: $!";
+    my @commits=readCommits($gitLogPipe);
+    close $gitLogPipe;
 
     # Now all the commits are in the @commits bucket
     # ordered by time DESC. for each commit that needs processing,
@@ -3116,7 +3851,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 +3869,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);
@@ -3196,7 +3932,7 @@ sub update
         }
 
         # convert the date to CVS-happy format
-        $commit->{date} = "$2 $1 $4 $3 $5" if ( $commit->{date} =~ /^\w+\s+(\w+)\s+(\d+)\s+(\d+:\d+:\d+)\s+(\d+)\s+([+-]\d+)$/ );
+        my $cvsDate = convertToCvsDate($commit->{date});
 
         if ( defined ( $lastpicked ) )
         {
@@ -3205,7 +3941,7 @@ sub update
             while (  )
             {
 		chomp;
-                unless ( /^:\d{6}\s+\d{3}(\d)\d{2}\s+[a-zA-Z0-9]{40}\s+([a-zA-Z0-9]{40})\s+(\w)$/o )
+                unless ( /^:\d{6}\s+([0-7]{6})\s+[a-f0-9]{40}\s+([a-f0-9]{40})\s+(\w)$/o )
                 {
                     die("Couldn't process git-diff-tree line : $_");
                 }
@@ -3215,11 +3951,7 @@ sub update
 
                 # $log->debug("File mode=$mode, hash=$hash, change=$change, name=$name");
 
-                my $git_perms = "";
-                $git_perms .= "r" if ( $mode & 4 );
-                $git_perms .= "w" if ( $mode & 2 );
-                $git_perms .= "x" if ( $mode & 1 );
-                $git_perms = "rw" if ( $git_perms eq "" );
+                my $dbMode = convertToDbMode($mode);
 
                 if ( $change eq "D" )
                 {
@@ -3229,11 +3961,11 @@ sub update
                         revision => $head->{$name}{revision} + 1,
                         filehash => "deleted",
                         commithash => $commit->{hash},
-                        modified => $commit->{date},
+                        modified => $cvsDate,
                         author => $commit->{author},
-                        mode => $git_perms,
+                        mode => $dbMode,
                     };
-                    $self->insert_rev($name, $head->{$name}{revision}, $hash, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
+                    $self->insert_rev($name, $head->{$name}{revision}, $hash, $commit->{hash}, $cvsDate, $commit->{author}, $dbMode);
                 }
                 elsif ( $change eq "M" || $change eq "T" )
                 {
@@ -3243,11 +3975,11 @@ sub update
                         revision => $head->{$name}{revision} + 1,
                         filehash => $hash,
                         commithash => $commit->{hash},
-                        modified => $commit->{date},
+                        modified => $cvsDate,
                         author => $commit->{author},
-                        mode => $git_perms,
+                        mode => $dbMode,
                     };
-                    $self->insert_rev($name, $head->{$name}{revision}, $hash, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
+                    $self->insert_rev($name, $head->{$name}{revision}, $hash, $commit->{hash}, $cvsDate, $commit->{author}, $dbMode);
                 }
                 elsif ( $change eq "A" )
                 {
@@ -3257,11 +3989,11 @@ sub update
                         revision => $head->{$name}{revision} ? $head->{$name}{revision}+1 : 1,
                         filehash => $hash,
                         commithash => $commit->{hash},
-                        modified => $commit->{date},
+                        modified => $cvsDate,
                         author => $commit->{author},
-                        mode => $git_perms,
+                        mode => $dbMode,
                     };
-                    $self->insert_rev($name, $head->{$name}{revision}, $hash, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
+                    $self->insert_rev($name, $head->{$name}{revision}, $hash, $commit->{hash}, $cvsDate, $commit->{author}, $dbMode);
                 }
                 else
                 {
@@ -3284,7 +4016,7 @@ sub update
                     die("Couldn't process git-ls-tree line : $_");
                 }
 
-                my ( $git_perms, $git_type, $git_hash, $git_filename ) = ( $1, $2, $3, $4 );
+                my ( $mode, $git_type, $git_hash, $git_filename ) = ( $1, $2, $3, $4 );
 
                 $seen_files->{$git_filename} = 1;
 
@@ -3294,18 +4026,10 @@ sub update
                     $head->{$git_filename}{mode}
                 );
 
-                if ( $git_perms =~ /^\d\d\d(\d)\d\d/o )
-                {
-                    $git_perms = "";
-                    $git_perms .= "r" if ( $1 & 4 );
-                    $git_perms .= "w" if ( $1 & 2 );
-                    $git_perms .= "x" if ( $1 & 1 );
-                } else {
-                    $git_perms = "rw";
-                }
+                my $dbMode = convertToDbMode($mode);
 
                 # unless the file exists with the same hash, we need to update it ...
-                unless ( defined($oldhash) and $oldhash eq $git_hash and defined($oldmode) and $oldmode eq $git_perms )
+                unless ( defined($oldhash) and $oldhash eq $git_hash and defined($oldmode) and $oldmode eq $dbMode )
                 {
                     my $newrevision = ( $oldrevision or 0 ) + 1;
 
@@ -3314,13 +4038,13 @@ sub update
                         revision => $newrevision,
                         filehash => $git_hash,
                         commithash => $commit->{hash},
-                        modified => $commit->{date},
+                        modified => $cvsDate,
                         author => $commit->{author},
-                        mode => $git_perms,
+                        mode => $dbMode,
                     };
 
 
-                    $self->insert_rev($git_filename, $newrevision, $git_hash, $commit->{hash}, $commit->{date}, $commit->{author}, $git_perms);
+                    $self->insert_rev($git_filename, $newrevision, $git_hash, $commit->{hash}, $cvsDate, $commit->{author}, $dbMode);
                 }
             }
             close FILELIST;
@@ -3333,10 +4057,10 @@ sub update
                     $head->{$file}{revision}++;
                     $head->{$file}{filehash} = "deleted";
                     $head->{$file}{commithash} = $commit->{hash};
-                    $head->{$file}{modified} = $commit->{date};
+                    $head->{$file}{modified} = $cvsDate;
                     $head->{$file}{author} = $commit->{author};
 
-                    $self->insert_rev($file, $head->{$file}{revision}, $head->{$file}{filehash}, $commit->{hash}, $commit->{date}, $commit->{author}, $head->{$file}{mode});
+                    $self->insert_rev($file, $head->{$file}{revision}, $head->{$file}{filehash}, $commit->{hash}, $cvsDate, $commit->{author}, $head->{$file}{mode});
                 }
             }
             # END : "Detect deleted files"
@@ -3367,35 +4091,116 @@ sub update
         );
     }
     # invalidate the gethead cache
-    $self->{gethead_cache} = undef;
+    $self->clearCommitRefCaches();
 
 
     # Ending exclusive lock here
     $self->{dbh}->commit() or die "Failed to commit changes to SQLite";
 }
 
-sub insert_rev
+sub readCommits
 {
-    my $self = shift;
-    my $name = shift;
-    my $revision = shift;
-    my $filehash = shift;
-    my $commithash = shift;
-    my $modified = shift;
-    my $author = shift;
-    my $mode = shift;
-    my $tablename = $self->tablename("revision");
+    my $pipeHandle = shift;
+    my @commits;
 
-    my $insert_rev = $self->{dbh}->prepare_cached("INSERT INTO $tablename (name, revision, filehash, commithash, modified, author, mode) VALUES (?,?,?,?,?,?,?)",{},1);
-    $insert_rev->execute($name, $revision, $filehash, $commithash, $modified, $author, $mode);
-}
+    my %commit = ();
 
-sub insert_mergelog
-{
-    my $self = shift;
-    my $key = shift;
-    my $value = shift;
-    my $tablename = $self->tablename("commitmsgs");
+    while ( <$pipeHandle> )
+    {
+        chomp;
+        if (m/^commit\s+(.*)$/) {
+            # on ^commit lines put the just seen commit in the stack
+            # and prime things for the next one
+            if (keys %commit) {
+                my %copy = %commit;
+                unshift @commits, \%copy;
+                %commit = ();
+            }
+            my @parents = split(m/\s+/, $1);
+            $commit{hash} = shift @parents;
+            $commit{parents} = \@parents;
+        } elsif (m/^(\w+?):\s+(.*)$/ && !exists($commit{message})) {
+            # on rfc822-like lines seen before we see any message,
+            # lowercase the entry and put it in the hash as key-value
+            $commit{lc($1)} = $2;
+        } else {
+            # message lines - skip initial empty line
+            # and trim whitespace
+            if (!exists($commit{message}) && m/^\s*$/) {
+                # define it to mark the end of headers
+                $commit{message} = '';
+                next;
+            }
+            s/^\s+//; s/\s+$//; # trim ws
+            $commit{message} .= $_ . "\n";
+        }
+    }
+
+    unshift @commits, \%commit if ( keys %commit );
+
+    return @commits;
+}
+
+sub convertToCvsDate
+{
+    my $date = shift;
+    # Convert from: "git rev-list --pretty" formatted date
+    # Convert to: "the format specified by RFC822 as modified by RFC1123."
+    # Example: 26 May 1997 13:01:40 -0400
+    if( $date =~ /^\w+\s+(\w+)\s+(\d+)\s+(\d+:\d+:\d+)\s+(\d+)\s+([+-]\d+)$/ )
+    {
+        $date = "$2 $1 $4 $3 $5";
+    }
+
+    return $date;
+}
+
+sub convertToDbMode
+{
+    my $mode = shift;
+
+    # NOTE: The CVS protocol uses a string similar "u=rw,g=rw,o=rw",
+    #  but the database "mode" column historically (and currently)
+    #  only stores the "rw" (for user) part of the string.
+    #    FUTURE: It might make more sense to persist the raw
+    #  octal mode (or perhaps the final full CVS form) instead of
+    #  this half-converted form, but it isn't currently worth the
+    #  backwards compatibility headaches.
+
+    $mode=~/^\d\d(\d)\d{3}$/;
+    my $userBits=$1;
+
+    my $dbMode = "";
+    $dbMode .= "r" if ( $userBits & 4 );
+    $dbMode .= "w" if ( $userBits & 2 );
+    $dbMode .= "x" if ( $userBits & 1 );
+    $dbMode = "rw" if ( $dbMode eq "" );
+
+    return $dbMode;
+}
+
+sub insert_rev
+{
+    my $self = shift;
+    my $name = shift;
+    my $revision = shift;
+    my $filehash = shift;
+    my $commithash = shift;
+    my $modified = shift;
+    my $author = shift;
+    my $mode = shift;
+    my $tablename = $self->tablename("revision");
+
+    my $insert_rev = $self->{dbh}->prepare_cached("INSERT INTO $tablename (name, revision, filehash, commithash, modified, author, mode) VALUES (?,?,?,?,?,?,?)",{},1);
+    $insert_rev->execute($name, $revision, $filehash, $commithash, $modified, $author, $mode);
+}
+
+sub insert_mergelog
+{
+    my $self = shift;
+    my $key = shift;
+    my $value = shift;
+    my $tablename = $self->tablename("commitmsgs");
 
     my $insert_mergelog = $self->{dbh}->prepare_cached("INSERT INTO $tablename (key, value) VALUES (?,?)",{},1);
     $insert_mergelog->execute($key, $value);
@@ -3426,19 +4231,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 +4270,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 +4281,10 @@ sub gethead
     my $tree = [];
     while ( my $file = $db_query->fetchrow_hashref )
     {
+        if(!$intRev)
+        {
+            $file->{revision} = "1.$file->{revision}"
+        }
         push @$tree, $file;
     }
 
@@ -3496,26 +4293,222 @@ sub gethead
     return $tree;
 }
 
+=head2 getAnyHead
+
+Returns a reference to an array of getmeta structures, one
+per file in the specified tree hash.
+
+=cut
+
+sub getAnyHead
+{
+    my ($self,$hash) = @_;
+
+    if(!defined($hash))
+    {
+        return $self->gethead();
+    }
+
+    my @files;
+    {
+        open(my $filePipe, '-|', 'git', 'ls-tree', '-z', '-r', $hash)
+                or die("Cannot call git-ls-tree : $!");
+        local $/ = "\0";
+        @files=<$filePipe>;
+        close $filePipe;
+    }
+
+    my $tree=[];
+    my($line);
+    foreach $line (@files)
+    {
+        $line=~s/\0$//;
+        unless ( $line=~/^(\d+)\s+(\w+)\s+([a-zA-Z0-9]+)\t(.*)$/o )
+        {
+            die("Couldn't process git-ls-tree line : $_");
+        }
+
+        my($mode, $git_type, $git_hash, $git_filename) = ($1, $2, $3, $4);
+        push @$tree, $self->getMetaFromCommithash($git_filename,$hash);
+    }
+
+    return $tree;
+}
+
+=head2 getRevisionDirMap
+
+A "revision dir map" contains all the plain-file filenames associated
+with a particular revision (treeish), organized by directory:
+
+  $type = $out->{$dir}{$fullName}
+
+The type of each is "F" (for ordinary file) or "D" (for directory,
+for which the map $out->{$fullName} will also exist).
+
+=cut
+
+sub getRevisionDirMap
+{
+    my ($self,$ver)=@_;
+
+    if(!defined($self->{revisionDirMapCache}))
+    {
+        $self->{revisionDirMapCache}={};
+    }
+
+        # Get file list (previously cached results are dependent on HEAD,
+        # but are early in each case):
+    my $cacheKey;
+    my (@fileList);
+    if( !defined($ver) || $ver eq "" )
+    {
+        $cacheKey="";
+        if( defined($self->{revisionDirMapCache}{$cacheKey}) )
+        {
+            return $self->{revisionDirMapCache}{$cacheKey};
+        }
+
+        my @head = @{$self->gethead()};
+        foreach my $file ( @head )
+        {
+            next if ( $file->{filehash} eq "deleted" );
+
+            push @fileList,$file->{name};
+        }
+    }
+    else
+    {
+        my ($hash)=$self->lookupCommitRef($ver);
+        if( !defined($hash) )
+        {
+            return undef;
+        }
+
+        $cacheKey=$hash;
+        if( defined($self->{revisionDirMapCache}{$cacheKey}) )
+        {
+            return $self->{revisionDirMapCache}{$cacheKey};
+        }
+
+        open(my $filePipe, '-|', 'git', 'ls-tree', '-z', '-r', $hash)
+                or die("Cannot call git-ls-tree : $!");
+        local $/ = "\0";
+        while ( <$filePipe> )
+        {
+            chomp;
+            unless ( /^(\d+)\s+(\w+)\s+([a-zA-Z0-9]+)\t(.*)$/o )
+            {
+                die("Couldn't process git-ls-tree line : $_");
+            }
+
+            my($mode, $git_type, $git_hash, $git_filename) = ($1, $2, $3, $4);
+
+            push @fileList, $git_filename;
+        }
+        close $filePipe;
+    }
+
+        # Convert to normalized form:
+    my %revMap;
+    my $file;
+    foreach $file (@fileList)
+    {
+        my($dir) = ($file=~m%^(?:(.*)/)?([^/]*)$%);
+        $dir='' if(!defined($dir));
+
+            # parent directories:
+            # ... create empty dir maps for parent dirs:
+        my($td)=$dir;
+        while(!defined($revMap{$td}))
+        {
+            $revMap{$td}={};
+
+            my($tp)=($td=~m%^(?:(.*)/)?([^/]*)$%);
+            $tp='' if(!defined($tp));
+            $td=$tp;
+        }
+            # ... add children to parent maps (now that they exist):
+        $td=$dir;
+        while($td ne "")
+        {
+            my($tp)=($td=~m%^(?:(.*)/)?([^/]*)$%);
+            $tp='' if(!defined($tp));
+
+            if(defined($revMap{$tp}{$td}))
+            {
+                if($revMap{$tp}{$td} ne 'D')
+                {
+                    die "Weird file/directory inconsistency in $cacheKey";
+                }
+                last;   # loop exit
+            }
+            $revMap{$tp}{$td}='D';
+
+            $td=$tp;
+        }
+
+            # file
+        $revMap{$dir}{$file}='F';
+    }
+
+        # Save in cache:
+    $self->{revisionDirMapCache}{$cacheKey}=\%revMap;
+    return $self->{revisionDirMapCache}{$cacheKey};
+}
+
 =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
@@ -3523,6 +4516,19 @@ =head2 getmeta
 This function takes a filename (with path) argument and returns a hashref of
 metadata for that file.
 
+There are several ways $revision can be specified:
+
+   - A reference to hash that contains a "tag" that is the
+     actual revision (one of the below).  TODO: Also allow it to
+     specify a "date" in the hash.
+   - undef, to refer to the latest version on the main branch.
+   - Full CVS client revision number (mapped to integer in DB, without the
+     "1." prefix),
+   - Complex CVS-compatible "special" revision number for
+     non-linear history (see comment below)
+   - git commit sha1 hash
+   - branch or tag name
+
 =cut
 
 sub getmeta
@@ -3533,22 +4539,347 @@ sub getmeta
     my $tablename_rev = $self->tablename("revision");
     my $tablename_head = $self->tablename("head");
 
-    my $db_query;
-    if ( defined($revision) and $revision =~ /^\d+$/ )
+    if ( ref($revision) eq "HASH" )
     {
-        $db_query = $self->{dbh}->prepare_cached("SELECT * FROM $tablename_rev WHERE name=? AND revision=?",{},1);
-        $db_query->execute($filename, $revision);
+        $revision = $revision->{tag};
     }
-    elsif ( defined($revision) and $revision =~ /^[a-zA-Z0-9]{40}$/ )
+
+    # Overview of CVS revision numbers:
+    #
+    # General CVS numbering scheme:
+    #   - Basic mainline branch numbers: "1.1", "1.2", "1.3", etc.
+    #   - Result of "cvs checkin -r" (possible, but not really
+    #     recommended): "2.1", "2.2", etc
+    #   - Branch tag: "1.2.0.n", where "1.2" is revision it was branched
+    #     from, "0" is a magic placeholder that identifies it as a
+    #     branch tag instead of a version tag, and n is 2 times the
+    #     branch number off of "1.2", starting with "2".
+    #   - Version on a branch: "1.2.n.x", where "1.2" is branch-from, "n"
+    #     is branch number off of "1.2" (like n above), and "x" is
+    #     the version number on the branch.
+    #   - Branches can branch off of branches: "1.3.2.7.4.1" (even number
+    #     of components).
+    #   - Odd "n"s are used by "vendor branches" that result
+    #     from "cvs import".  Vendor branches have additional
+    #     strangeness in the sense that the main rcs "head" of the main
+    #     branch will (temporarily until first normal commit) point
+    #     to the version on the vendor branch, rather than the actual
+    #     main branch.  (FUTURE: This may provide an opportunity
+    #     to use "strange" revision numbers for fast-forward-merged
+    #     branch tip when CVS client is asking for the main branch.)
+    #
+    # git-cvsserver CVS-compatible special numbering schemes:
+    #   - Currently git-cvsserver only tries to be identical to CVS for
+    #     simple "1.x" numbers on the "main" branch (as identified
+    #     by the module name that was originally cvs checkout'ed).
+    #   - The database only stores the "x" part, for historical reasons.
+    #     But most of the rest of the cvsserver preserves
+    #     and thinks using the full revision number.
+    #   - To handle non-linear history, it uses a version of the form
+    #     "2.1.1.2000.b.b.b."..., where the 2.1.1.2000 is to help uniquely
+    #     identify this as a special revision number, and there are
+    #     20 b's that together encode the sha1 git commit from which
+    #     this version of this file originated.  Each b is
+    #     the numerical value of the corresponding byte plus
+    #     100.
+    #      - "plus 100" avoids "0"s, and also reduces the
+    #        likelyhood of a collision in the case that someone someday
+    #        writes an import tool that tries to preserve original
+    #        CVS revision numbers, and the original CVS data had done
+    #        lots of branches off of branches and other strangeness to
+    #        end up with a real version number that just happens to look
+    #        like this special revision number form.  Also, if needed
+    #        there are several ways to extend/identify alternative encodings
+    #        within the "2.1.1.2000" part if necessary.
+    #      - Unlike real CVS revisions, you can't really reconstruct what
+    #        relation a revision of this form has to other revisions.
+    #   - FUTURE: TODO: Rework database somehow to make up and remember
+    #     fully-CVS-compatible branches and branch version numbers.
+
+    my $meta;
+    if ( defined($revision) )
     {
-        $db_query = $self->{dbh}->prepare_cached("SELECT * FROM $tablename_rev WHERE name=? AND commithash=?",{},1);
-        $db_query->execute($filename, $revision);
-    } else {
-        $db_query = $self->{dbh}->prepare_cached("SELECT * FROM $tablename_head WHERE name=?",{},1);
+        if ( $revision =~ /^1\.(\d+)$/ )
+        {
+            my ($intRev) = $1;
+            my $db_query;
+            $db_query = $self->{dbh}->prepare_cached(
+                "SELECT * FROM $tablename_rev WHERE name=? AND revision=?",
+                {},1);
+            $db_query->execute($filename, $intRev);
+            $meta = $db_query->fetchrow_hashref;
+        }
+        elsif ( $revision =~ /^2\.1\.1\.2000(\.[1-3][0-9][0-9]){20}$/ )
+        {
+            my ($commitHash)=($revision=~/^2\.1\.1\.2000(.*)$/);
+            $commitHash=~s/\.([0-9]+)/sprintf("%02x",$1-100)/eg;
+            if($commitHash=~/^[0-9a-f]{40}$/)
+            {
+                return $self->getMetaFromCommithash($filename,$commitHash);
+            }
+
+            # error recovery: fall back on head version below
+            print "E Failed to find $filename version=$revision or commit=$commitHash\n";
+            $log->warning("failed get $revision with commithash=$commitHash");
+            undef $revision;
+        }
+        elsif ( $revision =~ /^[0-9a-f]{40}$/ )
+        {
+            # Try DB first.  This is mostly only useful for req_annotate(),
+            # which only calls this for stuff that should already be in
+            # the DB.  It is fairly likely to be a waste of time
+            # in most other cases [unless the file happened to be
+            # modified in $revision specifically], but
+            # it is probably in the noise compared to how long
+            # getMetaFromCommithash() will take.
+            my $db_query;
+            $db_query = $self->{dbh}->prepare_cached(
+                "SELECT * FROM $tablename_rev WHERE name=? AND commithash=?",
+                {},1);
+            $db_query->execute($filename, $revision);
+            $meta = $db_query->fetchrow_hashref;
+
+            if(! $meta)
+            {
+                my($revCommit)=$self->lookupCommitRef($revision);
+                if($revCommit=~/^[0-9a-f]{40}$/)
+                {
+                    return $self->getMetaFromCommithash($filename,$revCommit);
+                }
+
+                # error recovery: nothing found:
+                print "E Failed to find $filename version=$revision\n";
+                $log->warning("failed get $revision");
+                return $meta;
+            }
+        }
+        else
+        {
+            my($revCommit)=$self->lookupCommitRef($revision);
+            if($revCommit=~/^[0-9a-f]{40}$/)
+            {
+                return $self->getMetaFromCommithash($filename,$revCommit);
+            }
+
+            # error recovery: fall back on head version below
+            print "E Failed to find $filename version=$revision\n";
+            $log->warning("failed get $revision");
+            undef $revision;  # Allow fallback
+        }
+    }
+
+    if(!defined($revision))
+    {
+        my $db_query;
+        $db_query = $self->{dbh}->prepare_cached(
+                "SELECT * FROM $tablename_head WHERE name=?",{},1);
         $db_query->execute($filename);
+        $meta = $db_query->fetchrow_hashref;
+    }
+
+    if($meta)
+    {
+        $meta->{revision} = "1.$meta->{revision}";
+    }
+    return $meta;
+}
+
+sub getMetaFromCommithash
+{
+    my $self = shift;
+    my $filename = shift;
+    my $revCommit = shift;
+
+    # NOTE: This function doesn't scale well (lots of forks), especially
+    #   if you have many files that have not been modified for many commits
+    #   (each git-rev-parse redoes a lot of work for each file
+    #   that theoretically could be done in parallel by smarter
+    #   graph traversal).
+    #
+    # TODO: Possible optimization strategies:
+    #   - Solve the issue of assigning and remembering "real" CVS
+    #     revision numbers for branches, and ensure the
+    #     data structure can do this efficiently.  Perhaps something
+    #     similar to "git notes", and carefully structured to take
+    #     advantage same-sha1-is-same-contents, to roll the same
+    #     unmodified subdirectory data onto multiple commits?
+    #   - Write and use a C tool that is like git-blame, but
+    #     operates on multiple files with file granularity, instead
+    #     of one file with line granularity.  Cache
+    #     most-recently-modified in $self->{commitRefCache}{$revCommit}.
+    #     Try to be intelligent about how many files we do with
+    #     one fork (perhaps one directory at a time, without recursion,
+    #     and/or include directory as one line item, recurse from here
+    #     instead of in C tool?).
+    #   - Perhaps we could ask the DB for (filename,fileHash),
+    #     and just guess that it is correct (that the file hadn't
+    #     changed between $revCommit and the found commit, then
+    #     changed back, confusing anything trying to interpret
+    #     history).  Probably need to add another index to revisions
+    #     DB table for this.
+    #   - NOTE: Trying to store all (commit,file) keys in DB [to
+    #     find "lastModfiedCommit] (instead of
+    #     just files that changed in each commit as we do now) is
+    #     probably not practical from a disk space perspective.
+
+        # Does the file exist in $revCommit?
+    # TODO: Include file hash in dirmap cache.
+    my($dirMap)=$self->getRevisionDirMap($revCommit);
+    my($dir,$file)=($filename=~m%^(?:(.*)/)?([^/]*$)%);
+    if(!defined($dir))
+    {
+        $dir="";
+    }
+    if( !defined($dirMap->{$dir}) ||
+        !defined($dirMap->{$dir}{$filename}) )
+    {
+        my($fileHash)="deleted";
+
+        my($retVal)={};
+        $retVal->{name}=$filename;
+        $retVal->{filehash}=$fileHash;
+
+            # not needed and difficult to compute:
+        $retVal->{revision}="0";  # $revision;
+        $retVal->{commithash}=$revCommit;
+        #$retVal->{author}=$commit->{author};
+        #$retVal->{modified}=convertToCvsDate($commit->{date});
+        #$retVal->{mode}=convertToDbMode($mode);
+
+        return $retVal;
+    }
+
+    my($fileHash)=safe_pipe_capture("git","rev-parse","$revCommit:$filename");
+    chomp $fileHash;
+    if(!($fileHash=~/^[0-9a-f]{40}$/))
+    {
+        die "Invalid fileHash '$fileHash' looking up"
+                    ." '$revCommit:$filename'\n";
+    }
+
+    # information about most recent commit to modify $filename:
+    open(my $gitLogPipe, '-|', 'git', 'rev-list',
+         '--max-count=1', '--pretty', '--parents',
+         $revCommit, '--', $filename)
+                or die "Cannot call git-rev-list: $!";
+    my @commits=readCommits($gitLogPipe);
+    close $gitLogPipe;
+    if(scalar(@commits)!=1)
+    {
+        die "Can't find most recent commit changing $filename\n";
+    }
+    my($commit)=$commits[0];
+    if( !defined($commit) || !defined($commit->{hash}) )
+    {
+        return undef;
+    }
+
+    # does this (commit,file) have a real assigned CVS revision number?
+    my $tablename_rev = $self->tablename("revision");
+    my $db_query;
+    $db_query = $self->{dbh}->prepare_cached(
+        "SELECT * FROM $tablename_rev WHERE name=? AND commithash=?",
+        {},1);
+    $db_query->execute($filename, $commit->{hash});
+    my($meta)=$db_query->fetchrow_hashref;
+    if($meta)
+    {
+        $meta->{revision} = "1.$meta->{revision}";
+        return $meta;
+    }
+
+    # fall back on special revision number
+    my($revision)=$commit->{hash};
+    $revision=~s/(..)/'.' . (hex($1)+100)/eg;
+    $revision="2.1.1.2000$revision";
+
+    # meta data about $filename:
+    open(my $filePipe, '-|', 'git', 'ls-tree', '-z',
+                $commit->{hash}, '--', $filename)
+            or die("Cannot call git-ls-tree : $!");
+    local $/ = "\0";
+    my $line;
+    $line=<$filePipe>;
+    if(defined(<$filePipe>))
+    {
+        die "Expected only a single file for git-ls-tree $filename\n";
+    }
+    close $filePipe;
+
+    chomp $line;
+    unless ( $line=~m/^(\d+)\s+(\w+)\s+([a-zA-Z0-9]+)\t(.*)$/o )
+    {
+        die("Couldn't process git-ls-tree line : $line\n");
+    }
+    my ( $mode, $git_type, $git_hash, $git_filename ) = ( $1, $2, $3, $4 );
+
+    # save result:
+    my($retVal)={};
+    $retVal->{name}=$filename;
+    $retVal->{revision}=$revision;
+    $retVal->{filehash}=$fileHash;
+    $retVal->{commithash}=$revCommit;
+    $retVal->{author}=$commit->{author};
+    $retVal->{modified}=convertToCvsDate($commit->{date});
+    $retVal->{mode}=convertToDbMode($mode);
+
+    return $retVal;
+}
+
+=head2 lookupCommitRef
+
+Convert tag/branch/abbreviation/etc into a commit sha1 hash.  Caches
+the result so looking it up again is fast.
+
+=cut
+
+sub lookupCommitRef
+{
+    my $self = shift;
+    my $ref = shift;
+
+    my $commitHash = $self->{commitRefCache}{$ref};
+    if(defined($commitHash))
+    {
+        return $commitHash;
     }
 
-    return $db_query->fetchrow_hashref;
+    $commitHash=safe_pipe_capture("git","rev-parse","--verify","--quiet",
+                                  $self->unescapeRefName($ref));
+    $commitHash=~s/\s*$//;
+    if(!($commitHash=~/^[0-9a-f]{40}$/))
+    {
+        $commitHash=undef;
+    }
+
+    if( defined($commitHash) )
+    {
+        my $type=safe_pipe_capture("git","cat-file","-t",$commitHash);
+        if( ! ($type=~/^commit\s*$/ ) )
+        {
+            $commitHash=undef;
+        }
+    }
+    if(defined($commitHash))
+    {
+        $self->{commitRefCache}{$ref}=$commitHash;
+    }
+    return $commitHash;
+}
+
+=head2 clearCommitRefCaches
+
+Clears cached commit cache (sha1's for various tags/abbeviations/etc),
+and related caches.
+
+=cut
+
+sub clearCommitRefCaches
+{
+    my $self = shift;
+    $self->{commitRefCache} = {};
+    $self->{revisionDirMapCache} = undef;
+    $self->{gethead_cache} = undef;
 }
 
 =head2 commitmessage
@@ -3583,46 +4914,128 @@ sub commitmessage
     return $message;
 }
 
-=head2 gethistory
+=head2 gethistorydense
 
 This function takes a filename (with path) argument and returns an arrayofarrays
-containing revision,filehash,commithash ordered by revision descending
+containing revision,filehash,commithash ordered by revision descending.
+
+This version of gethistory skips deleted entries -- so it is useful for annotate.
+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 gethistory
+sub gethistorydense
 {
     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 = $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 gethistorydense
+=head2 escapeRefName
 
-This function takes a filename (with path) argument and returns an arrayofarrays
-containing revision,filehash,commithash ordered by revision descending.
+Apply an escape mechanism to compensate for characters that
+git ref names can have that CVS tags can not.
 
-This version of gethistory skips deleted entries -- so it is useful for annotate.
-The 'dense' part is a reference to a '--dense' option available for git-rev-list
-and other git tools that depend on it.
+=cut
+sub escapeRefName
+{
+    my($self,$refName)=@_;
+
+    # CVS officially only allows [-_A-Za-z0-9] in tag names (or in
+    # many contexts it can also be a CVS revision number).
+    #
+    # Git tags commonly use '/' and '.' as well, but also handle
+    # anything else just in case:
+    #
+    #   = "_-s-"  For '/'.
+    #   = "_-p-"  For '.'.
+    #   = "_-u-"  For underscore, in case someone wants a literal "_-" in
+    #     a tag name.
+    #   = "_-xx-" Where "xx" is the hexadecimal representation of the
+    #     desired ASCII character byte. (for anything else)
+
+    if(! $refName=~/^[1-9][0-9]*(\.[1-9][0-9]*)*$/)
+    {
+        $refName=~s/_-/_-u--/g;
+        $refName=~s/\./_-p-/g;
+        $refName=~s%/%_-s-%g;
+        $refName=~s/[^-_a-zA-Z0-9]/sprintf("_-%02x-",$1)/eg;
+    }
+}
+
+=head2 unescapeRefName
+
+Undo an escape mechanism to compensate for characters that
+git ref names can have that CVS tags can not.
 
 =cut
-sub gethistorydense
+sub unescapeRefName
 {
-    my $self = shift;
-    my $filename = shift;
-    my $tablename = $self->tablename("revision");
+    my($self,$refName)=@_;
 
-    my $db_query;
-    $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);
+    # see escapeRefName() for description of escape mechanism.
+
+    $refName=~s/_-([spu]|[0-9a-f][0-9a-f])-/unescapeRefNameChar($1)/eg;
+
+    # allowed tag names
+    # TODO: Perhaps use git check-ref-format, with an in-process cache of
+    #  validated names?
+    if( !( $refName=~m%^[^-][-a-zA-Z0-9_/.]*$% ) ||
+        ( $refName=~m%[/.]$% ) ||
+        ( $refName=~/\.lock$/ ) ||
+        ( $refName=~m%\.\.|/\.|[[\\:?*~]|\@\{% ) )  # matching }
+    {
+        # Error:
+        $log->warn("illegal refName: $refName");
+        $refName=undef;
+    }
+    return $refName;
+}
+
+sub unescapeRefNameChar
+{
+    my($char)=@_;
+
+    if($char eq "s")
+    {
+        $char="/";
+    }
+    elsif($char eq "p")
+    {
+        $char=".";
+    }
+    elsif($char eq "u")
+    {
+        $char="_";
+    }
+    elsif($char=~/^[0-9a-f][0-9a-f]$/)
+    {
+        $char=chr(hex($char));
+    }
+    else
+    {
+        # Error case: Maybe it has come straight from user, and
+        # wasn't supposed to be escaped?  Restore it the way we got it:
+        $char="_-$char-";
+    }
 
-    return $db_query->fetchall_arrayref;
+    return $char;
 }
 
 =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..2da564995d 100755
--- a/git-p4.py
+++ b/git-p4.py
@@ -8,7 +8,13 @@
 # License: MIT 
 #
 
-import optparse, sys, os, marshal, subprocess, shelve
+import sys
+if sys.hexversion < 0x02040000:
+    # The limiter is the subprocess module
+    sys.stderr.write("git-p4: requires Python 2.4 or later.\n")
+    sys.exit(1)
+
+import optparse, os, marshal, subprocess, shelve
 import tempfile, getopt, os.path, time, platform
 import re, shutil
 
@@ -129,6 +135,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 +194,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 +275,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
@@ -505,29 +553,49 @@ def gitConfigList(key):
         _gitConfig[key] = read_pipe("git config --get-all %s" % key, ignore_error=True).strip().split(os.linesep)
     return _gitConfig[key]
 
-def p4BranchesInGit(branchesAreInRemotes = True):
+def p4BranchesInGit(branchesAreInRemotes=True):
+    """Find all the branches whose names start with "p4/", looking
+       in remotes or heads as specified by the argument.  Return
+       a dictionary of { branch: revision } for each one found.
+       The branch names are the short names, without any
+       "p4/" prefix."""
+
     branches = {}
 
     cmdline = "git rev-parse --symbolic "
     if branchesAreInRemotes:
-        cmdline += " --remotes"
+        cmdline += "--remotes"
     else:
-        cmdline += " --branches"
+        cmdline += "--branches"
 
     for line in read_pipe_lines(cmdline):
         line = line.strip()
 
-        ## only import to p4/
-        if not line.startswith('p4/') or line == "p4/HEAD":
+        # only import to p4/
+        if not line.startswith('p4/'):
+            continue
+        # special symbolic ref to p4/master
+        if line == "p4/HEAD":
             continue
-        branch = line
 
-        # strip off p4
-        branch = re.sub ("^p4/", "", line)
+        # strip off p4/ prefix
+        branch = line[len("p4/"):]
 
         branches[branch] = parseRevision(line)
+
     return branches
 
+def branch_exists(branch):
+    """Make sure that the given ref name really exists."""
+
+    cmd = [ "git", "rev-parse", "--symbolic", "--verify", branch ]
+    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    out, _ = p.communicate()
+    if p.returncode:
+        return False
+    # expect exactly one line of output: the branch name
+    return out.rstrip() == branch
+
 def findUpstreamBranchPoint(head = "HEAD"):
     branches = p4BranchesInGit()
     # map from depot-path to branch name
@@ -859,7 +927,8 @@ def __init__(self):
                 optparse.make_option("--dry-run", "-n", dest="dry_run", action="store_true"),
                 optparse.make_option("--prepare-p4-only", dest="prepare_p4_only", action="store_true"),
                 optparse.make_option("--conflict", dest="conflict_behavior",
-                                     choices=self.conflict_behavior_choices)
+                                     choices=self.conflict_behavior_choices),
+                optparse.make_option("--branch", dest="branch"),
         ]
         self.description = "Submit changes from git to the perforce depot."
         self.usage += " [name of git branch to submit into perforce depot]"
@@ -871,7 +940,8 @@ 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()
+        self.branch = None
 
     def check(self):
         if len(p4CmdList("opened ...")) > 0:
@@ -1608,6 +1678,8 @@ def run(self, args):
             print "All commits applied!"
 
             sync = P4Sync()
+            if self.branch:
+                sync.branch = self.branch
             sync.run([])
 
             rebase = P4Rebase()
@@ -2097,6 +2169,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 +2436,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))
@@ -2438,13 +2533,6 @@ def getBranchMappingFromGitBranches(self):
                 branch = branch[len(self.projectName):]
             self.knownBranches[branch] = branch
 
-    def listExistingP4GitBranches(self):
-        # branches holds mapping from name to commit
-        branches = p4BranchesInGit(self.importIntoRemotes)
-        self.p4BranchesInGit = branches.keys()
-        for branch in branches.keys():
-            self.initialParents[self.refPrefix + branch] = branches[branch]
-
     def updateOptionDict(self, d):
         option_keys = {}
         if self.keepRepoPath:
@@ -2543,7 +2631,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:
@@ -2616,6 +2704,7 @@ def importChanges(self, changes):
                     files = self.extractFilesFromCommit(description)
                     self.commit(description, files, self.branch,
                                 self.initialParent)
+                    # only needed once, to connect to the previous commit
                     self.initialParent = ""
             except IOError:
                 print self.gitError.read()
@@ -2667,14 +2756,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:
@@ -2687,34 +2770,31 @@ def importHeadRevision(self, revision):
     def run(self, args):
         self.depotPaths = []
         self.changeRange = ""
-        self.initialParent = ""
         self.previousDepotPaths = []
+        self.hasOrigin = False
 
         # map from branch depot path to parent branch
         self.knownBranches = {}
         self.initialParents = {}
-        self.hasOrigin = originP4BranchesExist()
-        if not self.syncWithOrigin:
-            self.hasOrigin = False
 
         if self.importIntoRemotes:
             self.refPrefix = "refs/remotes/p4/"
         else:
             self.refPrefix = "refs/heads/p4/"
 
-        if self.syncWithOrigin and self.hasOrigin:
-            if not self.silent:
-                print "Syncing with origin first by calling git fetch origin"
-            system("git fetch origin")
+        if self.syncWithOrigin:
+            self.hasOrigin = originP4BranchesExist()
+            if self.hasOrigin:
+                if not self.silent:
+                    print 'Syncing with origin first, using "git fetch origin"'
+                system("git fetch origin")
 
+        branch_arg_given = bool(self.branch)
         if len(self.branch) == 0:
             self.branch = self.refPrefix + "master"
             if gitBranchExists("refs/heads/p4") and self.importIntoRemotes:
                 system("git update-ref %s refs/heads/p4" % self.branch)
-                system("git branch -D p4");
-            # create it /after/ importing, when master exists
-            if not gitBranchExists(self.refPrefix + "HEAD") and self.importIntoRemotes and gitBranchExists(self.branch):
-                system("git symbolic-ref %sHEAD %s" % (self.refPrefix, self.branch))
+                system("git branch -D p4")
 
         # accept either the command-line option, or the configuration variable
         if self.useClientSpec:
@@ -2731,12 +2811,25 @@ def run(self, args):
         if args == []:
             if self.hasOrigin:
                 createOrUpdateBranchesFromOrigin(self.refPrefix, self.silent)
-            self.listExistingP4GitBranches()
+
+            # branches holds mapping from branch name to sha1
+            branches = p4BranchesInGit(self.importIntoRemotes)
+
+            # restrict to just this one, disabling detect-branches
+            if branch_arg_given:
+                short = self.branch.split("/")[-1]
+                if short in branches:
+                    self.p4BranchesInGit = [ short ]
+            else:
+                self.p4BranchesInGit = branches.keys()
 
             if len(self.p4BranchesInGit) > 1:
                 if not self.silent:
                     print "Importing from/into multiple branches"
                 self.detectBranches = True
+                for branch in branches.keys():
+                    self.initialParents[self.refPrefix + branch] = \
+                        branches[branch]
 
             if self.verbose:
                 print "branches: %s" % self.p4BranchesInGit
@@ -2773,13 +2866,21 @@ def run(self, args):
             if p4Change > 0:
                 self.depotPaths = sorted(self.previousDepotPaths)
                 self.changeRange = "@%s,#head" % p4Change
-                if not self.detectBranches:
-                    self.initialParent = parseRevision(self.branch)
                 if not self.silent and not self.detectBranches:
                     print "Performing incremental import into %s git branch" % self.branch
 
+        # accept multiple ref name abbreviations:
+        #    refs/foo/bar/branch -> use it exactly
+        #    p4/branch -> prepend refs/remotes/ or refs/heads/
+        #    branch -> prepend refs/remotes/p4/ or refs/heads/p4/
         if not self.branch.startswith("refs/"):
-            self.branch = "refs/heads/" + self.branch
+            if self.importIntoRemotes:
+                prepend = "refs/remotes/"
+            else:
+                prepend = "refs/heads/"
+            if not self.branch.startswith("p4/"):
+                prepend += "p4/"
+            self.branch = prepend + self.branch
 
         if len(args) == 0 and self.depotPaths:
             if not self.silent:
@@ -2864,12 +2965,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)
@@ -2889,8 +2991,21 @@ def run(self, args):
             else:
                 # catch "git p4 sync" with no new branches, in a repo that
                 # does not have any existing p4 branches
-                if len(args) == 0 and not self.p4BranchesInGit:
-                    die("No remote p4 branches.  Perhaps you never did \"git p4 clone\" in here.");
+                if len(args) == 0:
+                    if not self.p4BranchesInGit:
+                        die("No remote p4 branches.  Perhaps you never did \"git p4 clone\" in here.")
+
+                    # The default branch is master, unless --branch is used to
+                    # specify something else.  Make sure it exists, or complain
+                    # nicely about how to use --branch.
+                    if not self.detectBranches:
+                        if not branch_exists(self.branch):
+                            if branch_arg_given:
+                                die("Error: branch %s does not exist." % self.branch)
+                            else:
+                                die("Error: no branch %s; perhaps specify one with --branch." %
+                                    self.branch)
+
                 if self.verbose:
                     print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
                                                               self.changeRange)
@@ -2908,6 +3023,14 @@ def run(self, args):
 
                 self.updatedBranches = set()
 
+                if not self.detectBranches:
+                    if args:
+                        # start a new branch
+                        self.initialParent = ""
+                    else:
+                        # build on a previous revision
+                        self.initialParent = parseRevision(self.branch)
+
                 self.importChanges(changes)
 
                 if not self.silent:
@@ -2929,7 +3052,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()
@@ -2940,6 +3063,13 @@ def run(self, args):
                 read_pipe("git update-ref -d %s" % branch)
             os.rmdir(os.path.join(os.environ.get("GIT_DIR", ".git"), self.tempBranchLocation))
 
+        # Create a symbolic ref p4/HEAD pointing to p4/ to allow
+        # a convenient shortcut refname "p4".
+        if self.importIntoRemotes:
+            head_ref = self.refPrefix + "HEAD"
+            if not gitBranchExists(head_ref) and gitBranchExists(self.branch):
+                system(["git", "symbolic-ref", head_ref, self.branch])
+
         return True
 
 class P4Rebase(Command):
@@ -3047,17 +3177,15 @@ def run(self, args):
 
         if not P4Sync.run(self, depotPaths):
             return False
-        if self.branch != "master":
-            if self.importIntoRemotes:
-                masterbranch = "refs/remotes/p4/master"
-            else:
-                masterbranch = "refs/heads/p4/master"
-            if gitBranchExists(masterbranch):
-                system("git branch master %s" % masterbranch)
-                if not self.cloneBare:
-                    system("git checkout -f")
-            else:
-                print "Could not detect main branch. No checkout/master branch created."
+
+        # create a master branch and check out a work tree
+        if gitBranchExists(self.branch):
+            system([ "git", "branch", "master", self.branch ])
+            if not self.cloneBare:
+                system([ "git", "checkout", "-f" ])
+        else:
+            print 'Not checking out any branch, use ' \
+                  '"git checkout -q -b master "'
 
         # auto-set this variable if invoked with --use-client-spec
         if self.useClientSpec_from_options:
@@ -3128,7 +3256,6 @@ def main():
         printUsage(commands.keys())
         sys.exit(2)
 
-    cmd = ""
     cmdName = sys.argv[1]
     try:
         klass = commands[cmdName]
diff --git a/git-rebase--am.sh b/git-rebase--am.sh
index 392ebc9790..97f31dc7af 100644
--- a/git-rebase--am.sh
+++ b/git-rebase--am.sh
@@ -18,6 +18,7 @@ esac
 
 test -n "$rebase_root" && root_flag=--root
 
+ret=0
 if test -n "$keep_empty"
 then
 	# we have to do this the hard way.  git format-patch completely squashes
@@ -25,13 +26,49 @@ then
 	# itself well to recording empty patches.  fortunately, cherry-pick
 	# makes this easy
 	git cherry-pick --allow-empty "$revisions"
+	ret=$?
 else
+	rm -f "$GIT_DIR/rebased-patches"
+
 	git format-patch -k --stdout --full-index --ignore-if-in-upstream \
 		--src-prefix=a/ --dst-prefix=b/ \
-		--no-renames $root_flag "$revisions" |
-	git am $git_am_opt --rebasing --resolvemsg="$resolvemsg"
-fi && move_to_original_branch
+		--no-renames $root_flag "$revisions" >"$GIT_DIR/rebased-patches"
+	ret=$?
+
+	if test 0 != $ret
+	then
+		rm -f "$GIT_DIR/rebased-patches"
+		case "$head_name" in
+		refs/heads/*)
+			git checkout -q "$head_name"
+			;;
+		*)
+			git checkout -q "$orig_head"
+			;;
+		esac
+
+		cat >&2 <<-EOF
+
+		git encountered an error while preparing the patches to replay
+		these revisions:
+
+		    $revisions
+
+		As a result, git cannot rebase them.
+		EOF
+		exit $?
+	fi
+
+	git am $git_am_opt --rebasing --resolvemsg="$resolvemsg" <"$GIT_DIR/rebased-patches"
+	ret=$?
+
+	rm -f "$GIT_DIR/rebased-patches"
+fi
+
+if test 0 != $ret
+then
+	test -d "$state_dir" && write_basic_state
+	exit $ret
+fi
 
-ret=$?
-test 0 != $ret -a -d "$state_dir" && write_basic_state
-exit $ret
+move_to_original_branch
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index 44901d53c4..8ed7fccc18 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -190,6 +190,11 @@ is_empty_commit() {
 	test "$tree" = "$ptree"
 }
 
+is_merge_commit()
+{
+	git rev-parse --verify --quiet "$1"^2 >/dev/null 2>&1
+}
+
 # Run command with GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
 # GIT_AUTHOR_DATE exported from the current environment.
 do_with_author () {
@@ -874,7 +879,7 @@ git rev-list $merges_option --pretty=oneline --abbrev-commit \
 while read -r shortsha1 rest
 do
 
-	if test -z "$keep_empty" && is_empty_commit $shortsha1
+	if test -z "$keep_empty" && is_empty_commit $shortsha1 && ! is_merge_commit $shortsha1
 	then
 		comment_out="# "
 	else
diff --git a/git-remote-testgit b/git-remote-testgit
new file mode 100755
index 0000000000..b395c8de59
--- /dev/null
+++ b/git-remote-testgit
@@ -0,0 +1,90 @@
+#!/usr/bin/env bash
+# Copyright (c) 2012 Felipe Contreras
+
+alias=$1
+url=$2
+
+dir="$GIT_DIR/testgit/$alias"
+prefix="refs/testgit/$alias"
+
+default_refspec="refs/heads/*:${prefix}/heads/*"
+
+refspec="${GIT_REMOTE_TESTGIT_REFSPEC-$default_refspec}"
+
+test -z "$refspec" && prefix="refs"
+
+export GIT_DIR="$url/.git"
+
+mkdir -p "$dir"
+
+if test -z "$GIT_REMOTE_TESTGIT_NO_MARKS"
+then
+	gitmarks="$dir/git.marks"
+	testgitmarks="$dir/testgit.marks"
+	test -e "$gitmarks" || >"$gitmarks"
+	test -e "$testgitmarks" || >"$testgitmarks"
+	testgitmarks_args=( "--"{import,export}"-marks=$testgitmarks" )
+fi
+
+while read line
+do
+	case $line in
+	capabilities)
+		echo 'import'
+		echo 'export'
+		test -n "$refspec" && echo "refspec $refspec"
+		if test -n "$gitmarks"
+		then
+			echo "*import-marks $gitmarks"
+			echo "*export-marks $gitmarks"
+		fi
+		echo
+		;;
+	list)
+		git for-each-ref --format='? %(refname)' 'refs/heads/'
+		head=$(git symbolic-ref HEAD)
+		echo "@$head HEAD"
+		echo
+		;;
+	import*)
+		# read all import lines
+		while true
+		do
+			ref="${line#* }"
+			refs="$refs $ref"
+			read line
+			test "${line%% *}" != "import" && break
+		done
+
+		if test -n "$gitmarks"
+		then
+			echo "feature import-marks=$gitmarks"
+			echo "feature export-marks=$gitmarks"
+		fi
+		echo "feature done"
+		git fast-export "${testgitmarks_args[@]}" $refs |
+		sed -e "s#refs/heads/#${prefix}/heads/#g"
+		echo "done"
+		;;
+	export)
+		before=$(git for-each-ref --format='%(refname) %(objectname)')
+
+		git fast-import "${testgitmarks_args[@]}" --quiet
+
+		after=$(git for-each-ref --format='%(refname) %(objectname)')
+
+		# figure out which refs were updated
+		join -e 0 -o '0 1.2 2.2' -a 2 <(echo "$before") <(echo "$after") |
+		while read ref a b
+		do
+			test $a == $b && continue
+			echo "ok $ref"
+		done
+
+		echo
+		;;
+	'')
+		exit
+		;;
+	esac
+done
diff --git a/git-remote-testgit.py b/git-remote-testgit.py
deleted file mode 100644
index 5f3ebd244d..0000000000
--- a/git-remote-testgit.py
+++ /dev/null
@@ -1,272 +0,0 @@
-#!/usr/bin/env python
-
-# This command is a simple remote-helper, that is used both as a
-# testcase for the remote-helper functionality, and as an example to
-# show remote-helper authors one possible implementation.
-#
-# This is a Git <-> Git importer/exporter, that simply uses git
-# fast-import and git fast-export to consume and produce fast-import
-# streams.
-#
-# To understand better the way things work, one can activate debug
-# traces by setting (to any value) the environment variables
-# GIT_TRANSPORT_HELPER_DEBUG and GIT_DEBUG_TESTGIT, to see messages
-# from the transport-helper side, or from this example remote-helper.
-
-# hashlib is only available in python >= 2.5
-try:
-    import hashlib
-    _digest = hashlib.sha1
-except ImportError:
-    import sha
-    _digest = sha.new
-import sys
-import os
-import time
-sys.path.insert(0, os.getenv("GITPYTHONLIB","."))
-
-from git_remote_helpers.util import die, debug, warn
-from git_remote_helpers.git.repo import GitRepo
-from git_remote_helpers.git.exporter import GitExporter
-from git_remote_helpers.git.importer import GitImporter
-from git_remote_helpers.git.non_local import NonLocalGit
-
-def get_repo(alias, url):
-    """Returns a git repository object initialized for usage.
-    """
-
-    repo = GitRepo(url)
-    repo.get_revs()
-    repo.get_head()
-
-    hasher = _digest()
-    hasher.update(repo.path)
-    repo.hash = hasher.hexdigest()
-
-    repo.get_base_path = lambda base: os.path.join(
-        base, 'info', 'fast-import', repo.hash)
-
-    prefix = 'refs/testgit/%s/' % alias
-    debug("prefix: '%s'", prefix)
-
-    repo.gitdir = os.environ["GIT_DIR"]
-    repo.alias = alias
-    repo.prefix = prefix
-
-    repo.exporter = GitExporter(repo)
-    repo.importer = GitImporter(repo)
-    repo.non_local = NonLocalGit(repo)
-
-    return repo
-
-
-def local_repo(repo, path):
-    """Returns a git repository object initalized for usage.
-    """
-
-    local = GitRepo(path)
-
-    local.non_local = None
-    local.gitdir = repo.gitdir
-    local.alias = repo.alias
-    local.prefix = repo.prefix
-    local.hash = repo.hash
-    local.get_base_path = repo.get_base_path
-    local.exporter = GitExporter(local)
-    local.importer = GitImporter(local)
-
-    return local
-
-
-def do_capabilities(repo, args):
-    """Prints the supported capabilities.
-    """
-
-    print "import"
-    print "export"
-    print "refspec refs/heads/*:%s*" % repo.prefix
-
-    dirname = repo.get_base_path(repo.gitdir)
-
-    if not os.path.exists(dirname):
-        os.makedirs(dirname)
-
-    path = os.path.join(dirname, 'testgit.marks')
-
-    print "*export-marks %s" % path
-    if os.path.exists(path):
-        print "*import-marks %s" % path
-
-    print # end capabilities
-
-
-def do_list(repo, args):
-    """Lists all known references.
-
-    Bug: This will always set the remote head to master for non-local
-    repositories, since we have no way of determining what the remote
-    head is at clone time.
-    """
-
-    for ref in repo.revs:
-        debug("? refs/heads/%s", ref)
-        print "? refs/heads/%s" % ref
-
-    if repo.head:
-        debug("@refs/heads/%s HEAD" % repo.head)
-        print "@refs/heads/%s HEAD" % repo.head
-    else:
-        debug("@refs/heads/master HEAD")
-        print "@refs/heads/master HEAD"
-
-    print # end list
-
-
-def update_local_repo(repo):
-    """Updates (or clones) a local repo.
-    """
-
-    if repo.local:
-        return repo
-
-    path = repo.non_local.clone(repo.gitdir)
-    repo.non_local.update(repo.gitdir)
-    repo = local_repo(repo, path)
-    return repo
-
-
-def do_import(repo, args):
-    """Exports a fast-import stream from testgit for git to import.
-    """
-
-    if len(args) != 1:
-        die("Import needs exactly one ref")
-
-    if not repo.gitdir:
-        die("Need gitdir to import")
-
-    ref = args[0]
-    refs = [ref]
-
-    while True:
-        line = sys.stdin.readline()
-        if line == '\n':
-            break
-        if not line.startswith('import '):
-            die("Expected import line.")
-
-        # strip of leading 'import '
-        ref = line[7:].strip()
-        refs.append(ref)
-
-    repo = update_local_repo(repo)
-    repo.exporter.export_repo(repo.gitdir, refs)
-
-    print "done"
-
-
-def do_export(repo, args):
-    """Imports a fast-import stream from git to testgit.
-    """
-
-    if not repo.gitdir:
-        die("Need gitdir to export")
-
-    update_local_repo(repo)
-    changed = repo.importer.do_import(repo.gitdir)
-
-    if not repo.local:
-        repo.non_local.push(repo.gitdir)
-
-    for ref in changed:
-        print "ok %s" % ref
-    print
-
-
-COMMANDS = {
-    'capabilities': do_capabilities,
-    'list': do_list,
-    'import': do_import,
-    'export': do_export,
-}
-
-
-def sanitize(value):
-    """Cleans up the url.
-    """
-
-    if value.startswith('testgit::'):
-        value = value[9:]
-
-    return value
-
-
-def read_one_line(repo):
-    """Reads and processes one command.
-    """
-
-    sleepy = os.environ.get("GIT_REMOTE_TESTGIT_SLEEPY")
-    if sleepy:
-        debug("Sleeping %d sec before readline" % int(sleepy))
-        time.sleep(int(sleepy))
-
-    line = sys.stdin.readline()
-
-    cmdline = line
-
-    if not cmdline:
-        warn("Unexpected EOF")
-        return False
-
-    cmdline = cmdline.strip().split()
-    if not cmdline:
-        # Blank line means we're about to quit
-        return False
-
-    cmd = cmdline.pop(0)
-    debug("Got command '%s' with args '%s'", cmd, ' '.join(cmdline))
-
-    if cmd not in COMMANDS:
-        die("Unknown command, %s", cmd)
-
-    func = COMMANDS[cmd]
-    func(repo, cmdline)
-    sys.stdout.flush()
-
-    return True
-
-
-def main(args):
-    """Starts a new remote helper for the specified repository.
-    """
-
-    if len(args) != 3:
-        die("Expecting exactly three arguments.")
-        sys.exit(1)
-
-    if os.getenv("GIT_DEBUG_TESTGIT"):
-        import git_remote_helpers.util
-        git_remote_helpers.util.DEBUG = True
-
-    alias = sanitize(args[1])
-    url = sanitize(args[2])
-
-    if not alias.isalnum():
-        warn("non-alnum alias '%s'", alias)
-        alias = "tmp"
-
-    args[1] = alias
-    args[2] = url
-
-    repo = get_repo(alias, url)
-
-    debug("Got arguments %s", args[1:])
-
-    more = True
-
-    sys.stdin = os.fdopen(sys.stdin.fileno(), 'r', 0)
-    while (more):
-        more = read_one_line(repo)
-
-if __name__ == '__main__':
-    sys.exit(main(sys.argv))
diff --git a/git-remote-testpy.py b/git-remote-testpy.py
new file mode 100644
index 0000000000..d94a66a870
--- /dev/null
+++ b/git-remote-testpy.py
@@ -0,0 +1,285 @@
+#!/usr/bin/env python
+
+# This command is a simple remote-helper, that is used both as a
+# testcase for the remote-helper functionality, and as an example to
+# show remote-helper authors one possible implementation.
+#
+# This is a Git <-> Git importer/exporter, that simply uses git
+# fast-import and git fast-export to consume and produce fast-import
+# streams.
+#
+# To understand better the way things work, one can activate debug
+# traces by setting (to any value) the environment variables
+# GIT_TRANSPORT_HELPER_DEBUG and GIT_DEBUG_TESTGIT, to see messages
+# from the transport-helper side, or from this example remote-helper.
+
+# hashlib is only available in python >= 2.5
+try:
+    import hashlib
+    _digest = hashlib.sha1
+except ImportError:
+    import sha
+    _digest = sha.new
+import sys
+import os
+import time
+sys.path.insert(0, os.getenv("GITPYTHONLIB","."))
+
+from git_remote_helpers.util import die, debug, warn
+from git_remote_helpers.git.repo import GitRepo
+from git_remote_helpers.git.exporter import GitExporter
+from git_remote_helpers.git.importer import GitImporter
+from git_remote_helpers.git.non_local import NonLocalGit
+
+if sys.hexversion < 0x01050200:
+    # os.makedirs() is the limiter
+    sys.stderr.write("git-remote-testgit: requires Python 1.5.2 or later.\n")
+    sys.exit(1)
+
+def get_repo(alias, url):
+    """Returns a git repository object initialized for usage.
+    """
+
+    repo = GitRepo(url)
+    repo.get_revs()
+    repo.get_head()
+
+    hasher = _digest()
+    hasher.update(repo.path)
+    repo.hash = hasher.hexdigest()
+
+    repo.get_base_path = lambda base: os.path.join(
+        base, 'info', 'fast-import', repo.hash)
+
+    prefix = 'refs/testgit/%s/' % alias
+    debug("prefix: '%s'", prefix)
+
+    repo.gitdir = os.environ["GIT_DIR"]
+    repo.alias = alias
+    repo.prefix = prefix
+
+    repo.exporter = GitExporter(repo)
+    repo.importer = GitImporter(repo)
+    repo.non_local = NonLocalGit(repo)
+
+    return repo
+
+
+def local_repo(repo, path):
+    """Returns a git repository object initalized for usage.
+    """
+
+    local = GitRepo(path)
+
+    local.non_local = None
+    local.gitdir = repo.gitdir
+    local.alias = repo.alias
+    local.prefix = repo.prefix
+    local.hash = repo.hash
+    local.get_base_path = repo.get_base_path
+    local.exporter = GitExporter(local)
+    local.importer = GitImporter(local)
+
+    return local
+
+
+def do_capabilities(repo, args):
+    """Prints the supported capabilities.
+    """
+
+    print "import"
+    print "export"
+    print "refspec refs/heads/*:%s*" % repo.prefix
+
+    dirname = repo.get_base_path(repo.gitdir)
+
+    if not os.path.exists(dirname):
+        os.makedirs(dirname)
+
+    path = os.path.join(dirname, 'git.marks')
+
+    print "*export-marks %s" % path
+    if os.path.exists(path):
+        print "*import-marks %s" % path
+
+    print # end capabilities
+
+
+def do_list(repo, args):
+    """Lists all known references.
+
+    Bug: This will always set the remote head to master for non-local
+    repositories, since we have no way of determining what the remote
+    head is at clone time.
+    """
+
+    for ref in repo.revs:
+        debug("? refs/heads/%s", ref)
+        print "? refs/heads/%s" % ref
+
+    if repo.head:
+        debug("@refs/heads/%s HEAD" % repo.head)
+        print "@refs/heads/%s HEAD" % repo.head
+    else:
+        debug("@refs/heads/master HEAD")
+        print "@refs/heads/master HEAD"
+
+    print # end list
+
+
+def update_local_repo(repo):
+    """Updates (or clones) a local repo.
+    """
+
+    if repo.local:
+        return repo
+
+    path = repo.non_local.clone(repo.gitdir)
+    repo.non_local.update(repo.gitdir)
+    repo = local_repo(repo, path)
+    return repo
+
+
+def do_import(repo, args):
+    """Exports a fast-import stream from testgit for git to import.
+    """
+
+    if len(args) != 1:
+        die("Import needs exactly one ref")
+
+    if not repo.gitdir:
+        die("Need gitdir to import")
+
+    ref = args[0]
+    refs = [ref]
+
+    while True:
+        line = sys.stdin.readline()
+        if line == '\n':
+            break
+        if not line.startswith('import '):
+            die("Expected import line.")
+
+        # strip of leading 'import '
+        ref = line[7:].strip()
+        refs.append(ref)
+
+    print "feature done"
+
+    if os.environ.get("GIT_REMOTE_TESTGIT_FAILURE"):
+        die('Told to fail')
+
+    repo = update_local_repo(repo)
+    repo.exporter.export_repo(repo.gitdir, refs)
+
+    print "done"
+
+
+def do_export(repo, args):
+    """Imports a fast-import stream from git to testgit.
+    """
+
+    if not repo.gitdir:
+        die("Need gitdir to export")
+
+    if os.environ.get("GIT_REMOTE_TESTGIT_FAILURE"):
+        die('Told to fail')
+
+    update_local_repo(repo)
+    changed = repo.importer.do_import(repo.gitdir)
+
+    if not repo.local:
+        repo.non_local.push(repo.gitdir)
+
+    for ref in changed:
+        print "ok %s" % ref
+    print
+
+
+COMMANDS = {
+    'capabilities': do_capabilities,
+    'list': do_list,
+    'import': do_import,
+    'export': do_export,
+}
+
+
+def sanitize(value):
+    """Cleans up the url.
+    """
+
+    if value.startswith('testgit::'):
+        value = value[9:]
+
+    return value
+
+
+def read_one_line(repo):
+    """Reads and processes one command.
+    """
+
+    sleepy = os.environ.get("GIT_REMOTE_TESTGIT_SLEEPY")
+    if sleepy:
+        debug("Sleeping %d sec before readline" % int(sleepy))
+        time.sleep(int(sleepy))
+
+    line = sys.stdin.readline()
+
+    cmdline = line
+
+    if not cmdline:
+        warn("Unexpected EOF")
+        return False
+
+    cmdline = cmdline.strip().split()
+    if not cmdline:
+        # Blank line means we're about to quit
+        return False
+
+    cmd = cmdline.pop(0)
+    debug("Got command '%s' with args '%s'", cmd, ' '.join(cmdline))
+
+    if cmd not in COMMANDS:
+        die("Unknown command, %s", cmd)
+
+    func = COMMANDS[cmd]
+    func(repo, cmdline)
+    sys.stdout.flush()
+
+    return True
+
+
+def main(args):
+    """Starts a new remote helper for the specified repository.
+    """
+
+    if len(args) != 3:
+        die("Expecting exactly three arguments.")
+        sys.exit(1)
+
+    if os.getenv("GIT_DEBUG_TESTGIT"):
+        import git_remote_helpers.util
+        git_remote_helpers.util.DEBUG = True
+
+    alias = sanitize(args[1])
+    url = sanitize(args[2])
+
+    if not alias.isalnum():
+        warn("non-alnum alias '%s'", alias)
+        alias = "tmp"
+
+    args[1] = alias
+    args[2] = url
+
+    repo = get_repo(alias, url)
+
+    debug("Got arguments %s", args[1:])
+
+    more = True
+
+    sys.stdin = os.fdopen(sys.stdin.fileno(), 'r', 0)
+    while (more):
+        more = read_one_line(repo)
+
+if __name__ == '__main__':
+    sys.exit(main(sys.argv))
diff --git a/git-send-email.perl b/git-send-email.perl
index aea66a0d47..be809e5b59 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../) {
@@ -1227,10 +1285,10 @@ sub send_message {
 		}
 
 		if (defined $input_format && $input_format eq 'mbox') {
-			if (/^Subject:\s+(.*)$/) {
+			if (/^Subject:\s+(.*)$/i) {
 				$subject = $1;
 			}
-			elsif (/^From:\s+(.*)$/) {
+			elsif (/^From:\s+(.*)$/i) {
 				($author, $author_encoding) = unquote_rfc2047($1);
 				next if $suppress_cc{'author'};
 				next if $suppress_cc{'self'} and $author eq $sender;
@@ -1238,14 +1296,14 @@ sub send_message {
 					$1, $_) unless $quiet;
 				push @cc, $1;
 			}
-			elsif (/^To:\s+(.*)$/) {
+			elsif (/^To:\s+(.*)$/i) {
 				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+(.*)$/) {
+			elsif (/^Cc:\s+(.*)$/i) {
 				foreach my $addr (parse_address_line($1)) {
 					if (unquote_rfc2047($addr) eq $sender) {
 						next if ($suppress_cc{'self'});
@@ -1267,7 +1325,7 @@ sub send_message {
 			elsif (/^Message-Id: (.*)/i) {
 				$message_id = $1;
 			}
-			elsif (!/^Date:\s/ && /^[-A-Za-z]+:\s+\S/) {
+			elsif (!/^Date:\s/i && /^[-A-Za-z]+:\s+\S/) {
 				push @xh, $_;
 			}
 
@@ -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..795edd2852 100644
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
@@ -12,8 +12,11 @@
 # But we protect ourselves from such a user mistake nevertheless.
 unset CDPATH
 
-# Similarly for IFS
-unset IFS
+# Similarly for IFS, but some shells (e.g. FreeBSD 7.2) are buggy and
+# do not equate an unset IFS with IFS with the default, so here is
+# an explicit SP HT LF.
+IFS=' 	
+'
 
 git_broken_path_fix () {
 	case ":$PATH:" in
@@ -191,28 +194,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 02f82e9ed5..22ec5b63b4 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 ] [-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] update [--init] [--remote] [-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
@@ -26,9 +26,11 @@ cached=
 recursive=
 init=
 files=
+remote=
 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
@@ -151,6 +153,32 @@ die_if_unmatched ()
 	fi
 }
 
+#
+# Print a submodule configuration setting
+#
+# $1 = submodule name
+# $2 = option name
+# $3 = default value
+#
+# Checks in the usual git-config places first (for overrides),
+# otherwise it falls back on .gitmodules.  This allows you to
+# distribute project-wide defaults in .gitmodules, while still
+# customizing individual repositories if necessary.  If the option is
+# not in .gitmodules either, print a default value.
+#
+get_submodule_config () {
+	name="$1"
+	option="$2"
+	default="$3"
+	value=$(git config submodule."$name"."$option")
+	if test -z "$value"
+	then
+		value=$(git config -f .gitmodules submodule."$name"."$option")
+	fi
+	printf '%s' "${value:-$default}"
+}
+
+
 #
 # Map submodule path to submodule name
 #
@@ -179,8 +207,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 +218,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 +297,10 @@ cmd_add()
 			;;
 		--reference=*)
 			reference="$1"
+			;;
+		--name)
+			case "$2" in '') usage ;; esac
+			custom_name=$2
 			shift
 			;;
 		--)
@@ -336,6 +367,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 +385,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 +410,17 @@ 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" &&
+	if test -n "$branch"
+	then
+		git config -f .gitmodules submodule."$sm_name".branch "$branch"
+	fi &&
 	git add --force .gitmodules ||
 	die "$(eval_gettext "Failed to register submodule '\$sm_path'")"
 }
@@ -509,6 +564,9 @@ cmd_update()
 		-i|--init)
 			init=1
 			;;
+		--remote)
+			remote=1
+			;;
 		-N|--no-fetch)
 			nofetch=1
 			;;
@@ -569,6 +627,7 @@ cmd_update()
 		fi
 		name=$(module_name "$sm_path") || exit
 		url=$(git config submodule."$name".url)
+		branch=$(get_submodule_config "$name" branch master)
 		if ! test -z "$update"
 		then
 			update_module=$update
@@ -594,7 +653,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
@@ -603,6 +662,20 @@ Maybe you want to use 'update --init'?")"
 			die "$(eval_gettext "Unable to find current revision in submodule path '\$sm_path'")"
 		fi
 
+		if test -n "$remote"
+		then
+			if test -z "$nofetch"
+			then
+				# Fetch remote before determining tracking $sha1
+				(clear_local_git_env; cd "$sm_path" && git-fetch) ||
+				die "$(eval_gettext "Unable to fetch in submodule path '\$sm_path'")"
+			fi
+			remote_name=$(clear_local_git_env; cd "$sm_path" && get_default_remote)
+			sha1=$(clear_local_git_env; cd "$sm_path" &&
+				git rev-parse --verify "${remote_name}/${branch}") ||
+			die "$(eval_gettext "Unable to find current ${remote_name}/${branch} revision in submodule path '\$sm_path'")"
+		fi
+
 		if test "$subsha1" != "$sha1" -o -n "$force"
 		then
 			subforce=$force
@@ -926,7 +999,6 @@ cmd_summary() {
 cmd_status()
 {
 	# parse $args after "submodule ... status".
-	orig_flags=
 	while test $# -ne 0
 	do
 		case "$1" in
@@ -950,7 +1022,6 @@ cmd_status()
 			break
 			;;
 		esac
-		orig_flags="$orig_flags $(git rev-parse --sq-quote "$1")"
 		shift
 	done
 
@@ -990,7 +1061,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 +1081,10 @@ cmd_sync()
 			GIT_QUIET=1
 			shift
 			;;
+		--recursive)
+			recursive=1
+			shift
+			;;
 		--)
 			shift
 			break
@@ -1051,7 +1126,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 +1136,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-svn.perl b/git-svn.perl
index bd5266c86b..d0866946ce 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -114,6 +114,7 @@ sub _req_svn {
 	$_message, $_file, $_branch_dest,
 	$_template, $_shared,
 	$_version, $_fetch_all, $_no_rebase, $_fetch_parent,
+	$_before, $_after,
 	$_merge, $_strategy, $_preserve_merges, $_dry_run, $_local,
 	$_prefix, $_no_checkout, $_url, $_verbose,
 	$_commit_url, $_tag, $_merge_info, $_interactive);
@@ -258,7 +259,8 @@ sub _req_svn {
 			} ],
 	'find-rev' => [ \&cmd_find_rev,
 	                "Translate between SVN revision numbers and tree-ish",
-			{} ],
+			{ 'before' => \$_before,
+			  'after' => \$_after } ],
 	'rebase' => [ \&cmd_rebase, "Fetch and rebase your working directory",
 			{ 'merge|m|M' => \$_merge,
 			  'verbose|v' => \$_verbose,
@@ -1191,7 +1193,13 @@ sub cmd_find_rev {
 			    "$head history\n";
 		}
 		my $desired_revision = substr($revision_or_hash, 1);
-		$result = $gs->rev_map_get($desired_revision, $uuid);
+		if ($_before) {
+			$result = $gs->find_rev_before($desired_revision, 1);
+		} elsif ($_after) {
+			$result = $gs->find_rev_after($desired_revision, 1);
+		} else {
+			$result = $gs->rev_map_get($desired_revision, $uuid);
+		}
 	} else {
 		my (undef, $rev, undef) = cmt_metadata($revision_or_hash);
 		$result = $rev;
diff --git a/git.c b/git.c
index 8788b32ccd..b10c18b041 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) {
@@ -168,6 +135,14 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
 			git_config_push_parameter((*argv)[1]);
 			(*argv)++;
 			(*argc)--;
+		} else if (!strcmp(cmd, "--literal-pathspecs")) {
+			setenv(GIT_LITERAL_PATHSPECS_ENVIRONMENT, "1", 1);
+			if (envchanged)
+				*envchanged = 1;
+		} else if (!strcmp(cmd, "--no-literal-pathspecs")) {
+			setenv(GIT_LITERAL_PATHSPECS_ENVIRONMENT, "0", 1);
+			if (envchanged)
+				*envchanged = 1;
 		} else {
 			fprintf(stderr, "Unknown option: %s\n", cmd);
 			usage(git_usage_string);
@@ -338,6 +313,7 @@ static void handle_internal_command(int argc, const char **argv)
 		{ "bundle", cmd_bundle, RUN_SETUP_GENTLY },
 		{ "cat-file", cmd_cat_file, RUN_SETUP },
 		{ "check-attr", cmd_check_attr, RUN_SETUP },
+		{ "check-ignore", cmd_check_ignore, RUN_SETUP | NEED_WORK_TREE },
 		{ "check-ref-format", cmd_check_ref_format },
 		{ "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE },
 		{ "checkout-index", cmd_checkout_index,
diff --git a/git_remote_helpers/git/__init__.py b/git_remote_helpers/git/__init__.py
index e69de29bb2..1dbb1b0148 100644
--- a/git_remote_helpers/git/__init__.py
+++ b/git_remote_helpers/git/__init__.py
@@ -0,0 +1,5 @@
+import sys
+if sys.hexversion < 0x02040000:
+    # The limiter is the subprocess module
+    sys.stderr.write("git_remote_helpers: requires Python 2.4 or later.\n")
+    sys.exit(1)
diff --git a/git_remote_helpers/git/importer.py b/git_remote_helpers/git/importer.py
index 5c6b595e16..e28cc8f986 100644
--- a/git_remote_helpers/git/importer.py
+++ b/git_remote_helpers/git/importer.py
@@ -39,7 +39,7 @@ def do_import(self, base):
             gitdir = self.repo.gitpath
         else:
             gitdir = os.path.abspath(os.path.join(dirname, '.git'))
-        path = os.path.abspath(os.path.join(dirname, 'git.marks'))
+        path = os.path.abspath(os.path.join(dirname, 'testgit.marks'))
 
         if not os.path.exists(dirname):
             os.makedirs(dirname)
diff --git a/gitk-git/gitk b/gitk-git/gitk
index d93bd990a9..b3706fc9b9 100755
--- a/gitk-git/gitk
+++ b/gitk-git/gitk
@@ -2161,7 +2161,7 @@ proc makewindow {} {
     trace add variable sha1string write sha1change
     pack $sha1entry -side left -pady 2
 
-    image create bitmap bm-left -data {
+    set bm_left_data {
 	#define left_width 16
 	#define left_height 16
 	static unsigned char left_bits[] = {
@@ -2169,7 +2169,7 @@ proc makewindow {} {
 	0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
 	0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
     }
-    image create bitmap bm-right -data {
+    set bm_right_data {
 	#define right_width 16
 	#define right_height 16
 	static unsigned char right_bits[] = {
@@ -2177,11 +2177,24 @@ proc makewindow {} {
 	0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
 	0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
     }
-    ${NS}::button .tf.bar.leftbut -image bm-left -command goback \
-	-state disabled -width 26
+    image create bitmap bm-left -data $bm_left_data
+    image create bitmap bm-left-gray -data $bm_left_data -foreground "#999"
+    image create bitmap bm-right -data $bm_right_data
+    image create bitmap bm-right-gray -data $bm_right_data -foreground "#999"
+
+    ${NS}::button .tf.bar.leftbut -command goback -state disabled -width 26
+    if {$use_ttk} {
+	.tf.bar.leftbut configure -image [list bm-left disabled bm-left-gray]
+    } else {
+	.tf.bar.leftbut configure -image bm-left
+    }
     pack .tf.bar.leftbut -side left -fill y
-    ${NS}::button .tf.bar.rightbut -image bm-right -command goforw \
-	-state disabled -width 26
+    ${NS}::button .tf.bar.rightbut -command goforw -state disabled -width 26
+    if {$use_ttk} {
+	.tf.bar.rightbut configure -image [list bm-right disabled bm-right-gray]
+    } else {
+	.tf.bar.rightbut configure -image bm-right
+    }
     pack .tf.bar.rightbut -side left -fill y
 
     ${NS}::label .tf.bar.rowlabel -text [mc "Row"]
@@ -2361,6 +2374,8 @@ proc makewindow {} {
     $ctext tag conf mresult -font textfontbold
     $ctext tag conf msep -font textfontbold
     $ctext tag conf found -back yellow
+    $ctext tag conf currentsearchhit -back orange
+    $ctext tag conf wwrap -wrap word
 
     .pwbottom add .bleft
     if {!$use_ttk} {
@@ -2495,10 +2510,9 @@ proc makewindow {} {
     bindkey ? {dofind -1 1}
     bindkey f nextfile
     bind .  updatecommits
-    bind .  reloadcommits
+    bindmodfunctionkey Shift 5 reloadcommits
     bind .  showrefs
-    bind .  {newview 0}
-    catch { bind .  {newview 0} }
+    bindmodfunctionkey Shift 4 {newview 0}
     bind .  edit_or_newview
     bind . <$M1B-q> doquit
     bind . <$M1B-f> {dofind 1 1}
@@ -2523,6 +2537,7 @@ proc makewindow {} {
     bind $cflist $ctxbut {pop_flist_menu %W %X %Y %x %y}
     bind $ctext $ctxbut {pop_diff_menu %W %X %Y %x %y}
     bind $ctext  {focus %W}
+    bind $ctext <> rehighlight_search_results
 
     set maincursor [. cget -cursor]
     set textcursor [$ctext cget -cursor]
@@ -2646,6 +2661,11 @@ proc bindkey {ev script} {
     }
 }
 
+proc bindmodfunctionkey {mod n script} {
+    bind . <$mod-F$n> $script
+    catch { bind . <$mod-XF86_Switch_VT_$n> $script }
+}
+
 # set the focus back to the toplevel for any click outside
 # the entry widgets
 proc click {w} {
@@ -2702,7 +2722,7 @@ proc savestuff {w} {
     global cmitmode wrapcomment datetimeformat limitdiffs
     global colors uicolor bgcolor fgcolor diffcolors diffcontext selectbgcolor
     global autoselect autosellen extdifftool perfile_attrs markbgcolor use_ttk
-    global hideremotes want_ttk
+    global hideremotes want_ttk maxrefs
 
     if {$stuffsaved} return
     if {![winfo viewable .]} return
@@ -2724,6 +2744,7 @@ proc savestuff {w} {
 	puts $f [list set autoselect $autoselect]
 	puts $f [list set autosellen $autosellen]
 	puts $f [list set showneartags $showneartags]
+	puts $f [list set maxrefs $maxrefs]
 	puts $f [list set hideremotes $hideremotes]
 	puts $f [list set showlocalchanges $showlocalchanges]
 	puts $f [list set datetimeformat $datetimeformat]
@@ -3309,6 +3330,7 @@ proc sel_flist {w x y} {
     } else {
 	catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
     }
+    suppress_highlighting_file_for_current_scrollpos
 }
 
 proc pop_flist_menu {w X Y x y} {
@@ -6857,7 +6879,7 @@ proc viewnextline {dir} {
 # add a list of tag or branch names at position pos
 # returns the number of names inserted
 proc appendrefs {pos ids var} {
-    global ctext linknum curview $var maxrefs
+    global ctext linknum curview $var maxrefs mainheadid
 
     if {[catch {$ctext index $pos}]} {
 	return 0
@@ -6870,24 +6892,54 @@ proc appendrefs {pos ids var} {
 	    lappend tags [list $tag $id]
 	}
     }
+
+    set sep {}
+    set tags [lsort -index 0 -decreasing $tags]
+    set nutags 0
+
     if {[llength $tags] > $maxrefs} {
-	$ctext insert $pos "[mc "many"] ([llength $tags])"
-    } else {
-	set tags [lsort -index 0 -decreasing $tags]
-	set sep {}
-	foreach ti $tags {
-	    set id [lindex $ti 1]
-	    set lk link$linknum
-	    incr linknum
-	    $ctext tag delete $lk
-	    $ctext insert $pos $sep
-	    $ctext insert $pos [lindex $ti 0] $lk
-	    setlink $id $lk
-	    set sep ", "
+	# If we are displaying heads, and there are too many,
+	# see if there are some important heads to display.
+	# Currently this means "master" and the current head.
+	set itags {}
+	if {$var eq "idheads"} {
+	    set utags {}
+	    foreach ti $tags {
+		set hname [lindex $ti 0]
+		set id [lindex $ti 1]
+		if {($hname eq "master" || $id eq $mainheadid) &&
+		    [llength $itags] < $maxrefs} {
+		    lappend itags $ti
+		} else {
+		    lappend utags $ti
+		}
+	    }
+	    set tags $utags
+	}
+	if {$itags ne {}} {
+	    set str [mc "and many more"]
+	    set sep " "
+	} else {
+	    set str [mc "many"]
 	}
+	$ctext insert $pos "$str ([llength $tags])"
+	set nutags [llength $tags]
+	set tags $itags
+    }
+
+    foreach ti $tags {
+	set id [lindex $ti 1]
+	set lk link$linknum
+	incr linknum
+	$ctext tag delete $lk
+	$ctext insert $pos $sep
+	$ctext insert $pos [lindex $ti 0] $lk
+	setlink $id $lk
+	set sep ", "
     }
+    $ctext tag add wwrap "$pos linestart" "$pos lineend"
     $ctext conf -state disabled
-    return [llength $tags]
+    return [expr {[llength $tags] + $nutags}]
 }
 
 # called when we have finished computing the nearby tags
@@ -7947,32 +7999,45 @@ proc changediffdisp {} {
     $ctext tag conf dresult -elide [lindex $diffelide 1]
 }
 
-proc highlightfile {loc cline} {
-    global ctext cflist cflist_top
+proc highlightfile {cline} {
+    global cflist cflist_top
+
+    if {![info exists cflist_top]} return
 
-    $ctext yview $loc
     $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
     $cflist tag add highlight $cline.0 "$cline.0 lineend"
     $cflist see $cline.0
     set cflist_top $cline
 }
 
+proc highlightfile_for_scrollpos {topidx} {
+    global cmitmode difffilestart
+
+    if {$cmitmode eq "tree"} return
+    if {![info exists difffilestart]} return
+
+    set top [lindex [split $topidx .] 0]
+    if {$difffilestart eq {} || $top < [lindex $difffilestart 0]} {
+	highlightfile 0
+    } else {
+	highlightfile [expr {[bsearch $difffilestart $top] + 2}]
+    }
+}
+
 proc prevfile {} {
     global difffilestart ctext cmitmode
 
     if {$cmitmode eq "tree"} return
     set prev 0.0
-    set prevline 1
     set here [$ctext index @0,0]
     foreach loc $difffilestart {
 	if {[$ctext compare $loc >= $here]} {
-	    highlightfile $prev $prevline
+	    $ctext yview $prev
 	    return
 	}
 	set prev $loc
-	incr prevline
     }
-    highlightfile $prev $prevline
+    $ctext yview $prev
 }
 
 proc nextfile {} {
@@ -7980,11 +8045,9 @@ proc nextfile {} {
 
     if {$cmitmode eq "tree"} return
     set here [$ctext index @0,0]
-    set line 1
     foreach loc $difffilestart {
-	incr line
 	if {[$ctext compare $loc > $here]} {
-	    highlightfile $loc $line
+	    $ctext yview $loc
 	    return
 	}
     }
@@ -8030,7 +8093,6 @@ proc settabs {{firstab {}}} {
 proc incrsearch {name ix op} {
     global ctext searchstring searchdirn
 
-    $ctext tag remove found 1.0 end
     if {[catch {$ctext index anchor}]} {
 	# no anchor set, use start of selection, or of visible area
 	set sel [$ctext tag ranges sel]
@@ -8043,12 +8105,17 @@ proc incrsearch {name ix op} {
 	}
     }
     if {$searchstring ne {}} {
-	set here [$ctext search $searchdirn -- $searchstring anchor]
+	set here [$ctext search -count mlen $searchdirn -- $searchstring anchor]
 	if {$here ne {}} {
 	    $ctext see $here
+	    set mend "$here + $mlen c"
+	    $ctext tag remove sel 1.0 end
+	    $ctext tag add sel $here $mend
+	    suppress_highlighting_file_for_current_scrollpos
+	    highlightfile_for_scrollpos $here
 	}
-	searchmarkvisible 1
     }
+    rehighlight_search_results
 }
 
 proc dosearch {} {
@@ -8071,9 +8138,12 @@ proc dosearch {} {
 	    return
 	}
 	$ctext see $match
+	suppress_highlighting_file_for_current_scrollpos
+	highlightfile_for_scrollpos $match
 	set mend "$match + $mlen c"
 	$ctext tag add sel $match $mend
 	$ctext mark unset anchor
+	rehighlight_search_results
     }
 }
 
@@ -8097,21 +8167,41 @@ proc dosearchback {} {
 	    return
 	}
 	$ctext see $match
+	suppress_highlighting_file_for_current_scrollpos
+	highlightfile_for_scrollpos $match
 	set mend "$match + $ml c"
 	$ctext tag add sel $match $mend
 	$ctext mark unset anchor
+	rehighlight_search_results
+    }
+}
+
+proc rehighlight_search_results {} {
+    global ctext searchstring
+
+    $ctext tag remove found 1.0 end
+    $ctext tag remove currentsearchhit 1.0 end
+
+    if {$searchstring ne {}} {
+	searchmarkvisible 1
     }
 }
 
 proc searchmark {first last} {
     global ctext searchstring
 
+    set sel [$ctext tag ranges sel]
+
     set mend $first.0
     while {1} {
 	set match [$ctext search -count mlen -- $searchstring $mend $last.end]
 	if {$match eq {}} break
 	set mend "$match + $mlen c"
-	$ctext tag add found $match $mend
+	if {$sel ne {} && [$ctext compare $match == [lindex $sel 0]]} {
+	    $ctext tag add currentsearchhit $match $mend
+	} else {
+	    $ctext tag add found $match $mend
+	}
     }
 }
 
@@ -8137,8 +8227,23 @@ proc searchmarkvisible {doall} {
     }
 }
 
+proc suppress_highlighting_file_for_current_scrollpos {} {
+    global ctext suppress_highlighting_file_for_this_scrollpos
+
+    set suppress_highlighting_file_for_this_scrollpos [$ctext index @0,0]
+}
+
 proc scrolltext {f0 f1} {
-    global searchstring
+    global searchstring cmitmode ctext
+    global suppress_highlighting_file_for_this_scrollpos
+
+    set topidx [$ctext index @0,0]
+    if {![info exists suppress_highlighting_file_for_this_scrollpos]
+	|| $topidx ne $suppress_highlighting_file_for_this_scrollpos} {
+	highlightfile_for_scrollpos $topidx
+    }
+
+    catch {unset suppress_highlighting_file_for_this_scrollpos}
 
     .bleft.bottom.sb set $f0 $f1
     if {$searchstring ne {}} {
@@ -10509,13 +10614,13 @@ proc anctags {id} {
 # including id itself if it has a head.
 proc descheads {id} {
     global arcnos arcstart arcids archeads idheads cached_dheads
-    global allparents
+    global allparents arcout
 
     if {![info exists allparents($id)]} {
 	return {}
     }
     set aret {}
-    if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
+    if {![info exists arcout($id)]} {
 	# part-way along an arc; check it first
 	set a [lindex $arcnos($id) 0]
 	if {$archeads($a) ne {}} {
@@ -10864,7 +10969,7 @@ proc create_prefs_page {w} {
 proc prefspage_general {notebook} {
     global NS maxwidth maxgraphpct showneartags showlocalchanges
     global tabstop limitdiffs autoselect autosellen extdifftool perfile_attrs
-    global hideremotes want_ttk have_ttk
+    global hideremotes want_ttk have_ttk maxrefs
 
     set page [create_prefs_page $notebook.general]
 
@@ -10893,9 +10998,12 @@ proc prefspage_general {notebook} {
     ${NS}::label $page.tabstopl -text [mc "Tab spacing"]
     spinbox $page.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
     grid x $page.tabstopl $page.tabstop -sticky w
-    ${NS}::checkbutton $page.ntag -text [mc "Display nearby tags"] \
+    ${NS}::checkbutton $page.ntag -text [mc "Display nearby tags/heads"] \
 	-variable showneartags
     grid x $page.ntag -sticky w
+    ${NS}::label $page.maxrefsl -text [mc "Maximum # tags/heads to show"]
+    spinbox $page.maxrefs -from 1 -to 1000 -width 4 -textvariable maxrefs
+    grid x $page.maxrefsl $page.maxrefs -sticky w
     ${NS}::checkbutton $page.ldiff -text [mc "Limit diffs to listed paths"] \
 	-variable limitdiffs
     grid x $page.ldiff -sticky w
diff --git a/gitk-git/po/sv.po b/gitk-git/po/sv.po
index 2f07a2e2d9..8cc98dc079 100644
--- a/gitk-git/po/sv.po
+++ b/gitk-git/po/sv.po
@@ -1,40 +1,50 @@
 # Swedish translation for gitk
-# Copyright (C) 2005-2010 Paul Mackerras
+# Copyright (C) 2005-2012 Paul Mackerras
 # This file is distributed under the same license as the gitk package.
 #
-# Peter Krefting , 2008-2010.
 # Mikael Magnusson , 2008.
+# Peter Krefting , 2008, 2009, 2010, 2012.
+#
 msgid ""
 msgstr ""
 "Project-Id-Version: sv\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2010-09-12 21:14+0100\n"
-"PO-Revision-Date: 2010-09-12 21:16+0100\n"
+"POT-Creation-Date: 2012-10-03 08:09+0100\n"
+"PO-Revision-Date: 2012-10-03 08:13+0100\n"
 "Last-Translator: Peter Krefting \n"
 "Language-Team: Swedish \n"
+"Language: sv\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit"
+"Content-Transfer-Encoding: 8bit\n"
 
-#: gitk:115
+#: gitk:140
 msgid "Couldn't get list of unmerged files:"
 msgstr "Kunde inte hämta lista över ej sammanslagna filer:"
 
-#: gitk:274
+#: gitk:210 gitk:2317
+msgid "Color words"
+msgstr "Färga ord"
+
+#: gitk:215 gitk:2317 gitk:7888 gitk:7921
+msgid "Markup words"
+msgstr "Märk upp ord"
+
+#: gitk:312
 msgid "Error parsing revisions:"
 msgstr "Fel vid tolkning av revisioner:"
 
-#: gitk:330
+#: gitk:368
 msgid "Error executing --argscmd command:"
 msgstr "Fel vid körning av --argscmd-kommando:"
 
-#: gitk:343
+#: gitk:381
 msgid "No files selected: --merge specified but no files are unmerged."
 msgstr ""
 "Inga filer valdes: --merge angavs men det finns inga filer som inte har "
 "slagits samman."
 
-#: gitk:346
+#: gitk:384
 msgid ""
 "No files selected: --merge specified but no unmerged files are within file "
 "limit."
@@ -42,605 +52,617 @@ msgstr ""
 "Inga filer valdes: --merge angavs men det finns inga filer inom "
 "filbegränsningen."
 
-#: gitk:368 gitk:516
+#: gitk:406 gitk:554
 msgid "Error executing git log:"
 msgstr "Fel vid körning av git log:"
 
-#: gitk:386 gitk:532
+#: gitk:424 gitk:570
 msgid "Reading"
 msgstr "Läser"
 
-#: gitk:446 gitk:4271
+#: gitk:484 gitk:4353
 msgid "Reading commits..."
 msgstr "Läser incheckningar..."
 
-#: gitk:449 gitk:1580 gitk:4274
+#: gitk:487 gitk:1625 gitk:4356
 msgid "No commits selected"
 msgstr "Inga incheckningar markerade"
 
-#: gitk:1456
+#: gitk:1499
 msgid "Can't parse git log output:"
 msgstr "Kan inte tolka utdata från git log:"
 
-#: gitk:1676
+#: gitk:1719
 msgid "No commit information available"
 msgstr "Ingen incheckningsinformation är tillgänglig"
 
-#: gitk:1818
+#: gitk:1876
 msgid "mc"
 msgstr "mc"
 
-#: gitk:1853 gitk:4064 gitk:9067 gitk:10607 gitk:10817
+#: gitk:1911 gitk:4146 gitk:9282 gitk:10824 gitk:11100
 msgid "OK"
 msgstr "OK"
 
-#: gitk:1855 gitk:4066 gitk:8657 gitk:8736 gitk:8851 gitk:8900 gitk:9069
-#: gitk:10608 gitk:10818
+#: gitk:1913 gitk:4148 gitk:8871 gitk:8950 gitk:9065 gitk:9114 gitk:9284
+#: gitk:10825 gitk:11101
 msgid "Cancel"
 msgstr "Avbryt"
 
-#: gitk:1980
+#: gitk:2040
 msgid "Update"
 msgstr "Uppdatera"
 
-#: gitk:1981
+#: gitk:2041
 msgid "Reload"
 msgstr "Ladda om"
 
-#: gitk:1982
+#: gitk:2042
 msgid "Reread references"
 msgstr "Läs om referenser"
 
-#: gitk:1983
+#: gitk:2043
 msgid "List references"
 msgstr "Visa referenser"
 
-#: gitk:1985
+#: gitk:2045
 msgid "Start git gui"
 msgstr "Starta git gui"
 
-#: gitk:1987
+#: gitk:2047
 msgid "Quit"
 msgstr "Avsluta"
 
-#: gitk:1979
+#: gitk:2039
 msgid "File"
 msgstr "Arkiv"
 
-#: gitk:1991
+#: gitk:2051
 msgid "Preferences"
 msgstr "Inställningar"
 
-#: gitk:1990
+#: gitk:2050
 msgid "Edit"
 msgstr "Redigera"
 
-#: gitk:1995
+#: gitk:2055
 msgid "New view..."
 msgstr "Ny vy..."
 
-#: gitk:1996
+#: gitk:2056
 msgid "Edit view..."
 msgstr "Ändra vy..."
 
-#: gitk:1997
+#: gitk:2057
 msgid "Delete view"
 msgstr "Ta bort vy"
 
-#: gitk:1999
+#: gitk:2059
 msgid "All files"
 msgstr "Alla filer"
 
-#: gitk:1994 gitk:3817
+#: gitk:2054 gitk:3899
 msgid "View"
 msgstr "Visa"
 
-#: gitk:2004 gitk:2014 gitk:2787
+#: gitk:2064 gitk:2074 gitk:2869
 msgid "About gitk"
 msgstr "Om gitk"
 
-#: gitk:2005 gitk:2019
+#: gitk:2065 gitk:2079
 msgid "Key bindings"
 msgstr "Tangentbordsbindningar"
 
-#: gitk:2003 gitk:2018
+#: gitk:2063 gitk:2078
 msgid "Help"
 msgstr "Hjälp"
 
-#: gitk:2096 gitk:8132
+#: gitk:2156 gitk:8330
 msgid "SHA1 ID:"
 msgstr "SHA1-id:"
 
-#: gitk:2127
+#: gitk:2192
 msgid "Row"
 msgstr "Rad"
 
-#: gitk:2165
+#: gitk:2230
 msgid "Find"
 msgstr "Sök"
 
-#: gitk:2166
+#: gitk:2231
 msgid "next"
 msgstr "nästa"
 
-#: gitk:2167
+#: gitk:2232
 msgid "prev"
 msgstr "föreg"
 
-#: gitk:2168
+#: gitk:2233
 msgid "commit"
 msgstr "incheckning"
 
-#: gitk:2171 gitk:2173 gitk:4432 gitk:4455 gitk:4479 gitk:6420 gitk:6492
-#: gitk:6576
+#: gitk:2236 gitk:2238 gitk:4514 gitk:4537 gitk:4561 gitk:6528 gitk:6600
+#: gitk:6685
 msgid "containing:"
 msgstr "som innehåller:"
 
-#: gitk:2174 gitk:3298 gitk:3303 gitk:4507
+#: gitk:2239 gitk:3381 gitk:3386 gitk:4590
 msgid "touching paths:"
 msgstr "som rör sökväg:"
 
-#: gitk:2175 gitk:4512
+#: gitk:2240 gitk:4604
 msgid "adding/removing string:"
 msgstr "som lägger/till tar bort sträng:"
 
-#: gitk:2184 gitk:2186
+#: gitk:2249 gitk:2251 gitk:4593
 msgid "Exact"
 msgstr "Exakt"
 
-#: gitk:2186 gitk:4587 gitk:6388
+#: gitk:2251 gitk:4679 gitk:6496
 msgid "IgnCase"
 msgstr "IgnVersaler"
 
-#: gitk:2186 gitk:4481 gitk:4585 gitk:6384
+#: gitk:2251 gitk:4563 gitk:4677 gitk:6492
 msgid "Regexp"
 msgstr "Reg.uttr."
 
-#: gitk:2188 gitk:2189 gitk:4606 gitk:4636 gitk:4643 gitk:6512 gitk:6580
+#: gitk:2253 gitk:2254 gitk:4699 gitk:4729 gitk:4736 gitk:6621 gitk:6689
 msgid "All fields"
 msgstr "Alla fält"
 
-#: gitk:2189 gitk:4604 gitk:4636 gitk:6451
+#: gitk:2254 gitk:4696 gitk:4729 gitk:6559
 msgid "Headline"
 msgstr "Rubrik"
 
-#: gitk:2190 gitk:4604 gitk:6451 gitk:6580 gitk:7013
+#: gitk:2255 gitk:4696 gitk:6559 gitk:6689 gitk:7126
 msgid "Comments"
 msgstr "Kommentarer"
 
-#: gitk:2190 gitk:4604 gitk:4608 gitk:4643 gitk:6451 gitk:6948 gitk:8307
-#: gitk:8322
+#: gitk:2255 gitk:4696 gitk:4701 gitk:4736 gitk:6559 gitk:7061 gitk:8505
+#: gitk:8520
 msgid "Author"
 msgstr "Författare"
 
-#: gitk:2190 gitk:4604 gitk:6451 gitk:6950
+#: gitk:2255 gitk:4696 gitk:6559 gitk:7063
 msgid "Committer"
 msgstr "Incheckare"
 
-#: gitk:2221
+#: gitk:2286
 msgid "Search"
 msgstr "Sök"
 
-#: gitk:2229
+#: gitk:2294
 msgid "Diff"
 msgstr "Diff"
 
-#: gitk:2231
+#: gitk:2296
 msgid "Old version"
 msgstr "Gammal version"
 
-#: gitk:2233
+#: gitk:2298
 msgid "New version"
 msgstr "Ny version"
 
-#: gitk:2235
+#: gitk:2300
 msgid "Lines of context"
 msgstr "Rader sammanhang"
 
-#: gitk:2245
+#: gitk:2310
 msgid "Ignore space change"
 msgstr "Ignorera ändringar i blanksteg"
 
-#: gitk:2304
+#: gitk:2314 gitk:2316 gitk:7646 gitk:7874
+msgid "Line diff"
+msgstr "Rad-diff"
+
+#: gitk:2379
 msgid "Patch"
 msgstr "Patch"
 
-#: gitk:2306
+#: gitk:2381
 msgid "Tree"
 msgstr "Träd"
 
-#: gitk:2463 gitk:2480
+#: gitk:2540 gitk:2559
 msgid "Diff this -> selected"
 msgstr "Diff denna -> markerad"
 
-#: gitk:2464 gitk:2481
+#: gitk:2541 gitk:2560
 msgid "Diff selected -> this"
 msgstr "Diff markerad -> denna"
 
-#: gitk:2465 gitk:2482
+#: gitk:2542 gitk:2561
 msgid "Make patch"
 msgstr "Skapa patch"
 
-#: gitk:2466 gitk:8715
+#: gitk:2543 gitk:8929
 msgid "Create tag"
 msgstr "Skapa tagg"
 
-#: gitk:2467 gitk:8831
+#: gitk:2544 gitk:9045
 msgid "Write commit to file"
 msgstr "Skriv incheckning till fil"
 
-#: gitk:2468 gitk:8888
+#: gitk:2545 gitk:9102
 msgid "Create new branch"
 msgstr "Skapa ny gren"
 
-#: gitk:2469
+#: gitk:2546
 msgid "Cherry-pick this commit"
 msgstr "Plocka denna incheckning"
 
-#: gitk:2470
+#: gitk:2547
 msgid "Reset HEAD branch to here"
 msgstr "Återställ HEAD-grenen hit"
 
-#: gitk:2471
+#: gitk:2548
 msgid "Mark this commit"
 msgstr "Markera denna incheckning"
 
-#: gitk:2472
+#: gitk:2549
 msgid "Return to mark"
 msgstr "Återgå till markering"
 
-#: gitk:2473
+#: gitk:2550
 msgid "Find descendant of this and mark"
 msgstr "Hitta efterföljare till denna och markera"
 
-#: gitk:2474
+#: gitk:2551
 msgid "Compare with marked commit"
 msgstr "Jämför med markerad incheckning"
 
-#: gitk:2488
+#: gitk:2552 gitk:2562
+msgid "Diff this -> marked commit"
+msgstr "Diff denna -> markerad incheckning"
+
+#: gitk:2553 gitk:2563
+msgid "Diff marked commit -> this"
+msgstr "Diff markerad incheckning -> denna"
+
+#: gitk:2569
 msgid "Check out this branch"
 msgstr "Checka ut denna gren"
 
-#: gitk:2489
+#: gitk:2570
 msgid "Remove this branch"
 msgstr "Ta bort denna gren"
 
-#: gitk:2496
+#: gitk:2577
 msgid "Highlight this too"
 msgstr "Markera även detta"
 
-#: gitk:2497
+#: gitk:2578
 msgid "Highlight this only"
 msgstr "Markera bara detta"
 
-#: gitk:2498
+#: gitk:2579
 msgid "External diff"
 msgstr "Extern diff"
 
-#: gitk:2499
+#: gitk:2580
 msgid "Blame parent commit"
 msgstr "Klandra föräldraincheckning"
 
-#: gitk:2506
+#: gitk:2587
 msgid "Show origin of this line"
 msgstr "Visa ursprunget för den här raden"
 
-#: gitk:2507
+#: gitk:2588
 msgid "Run git gui blame on this line"
 msgstr "Kör git gui blame på den här raden"
 
-#: gitk:2789
+#: gitk:2871
 msgid ""
 "\n"
 "Gitk - a commit viewer for git\n"
 "\n"
-"Copyright ©9 2005-2010 Paul Mackerras\n"
+"Copyright ©9 2005-2011 Paul Mackerras\n"
 "\n"
 "Use and redistribute under the terms of the GNU General Public License"
 msgstr ""
 "\n"
 "Gitk - en incheckningsvisare för git\n"
 "\n"
-"Copyright ©9 2005-2010 Paul Mackerras\n"
+"Copyright ©9 2005-2011 Paul Mackerras\n"
 "\n"
 "Använd och vidareförmedla enligt villkoren i GNU General Public License"
 
-#: gitk:2797 gitk:2862 gitk:9253
+#: gitk:2879 gitk:2944 gitk:9468
 msgid "Close"
 msgstr "Stäng"
 
-#: gitk:2818
+#: gitk:2900
 msgid "Gitk key bindings"
 msgstr "Tangentbordsbindningar för Gitk"
 
-#: gitk:2821
+#: gitk:2903
 msgid "Gitk key bindings:"
 msgstr "Tangentbordsbindningar för Gitk:"
 
-#: gitk:2823
+#: gitk:2905
 #, tcl-format
 msgid "<%s-Q>\t\tQuit"
 msgstr "<%s-Q>\t\tAvsluta"
 
-#: gitk:2824
+#: gitk:2906
 #, tcl-format
 msgid "<%s-W>\t\tClose window"
 msgstr "<%s-W>\t\tStäng fönster"
 
-#: gitk:2825
+#: gitk:2907
 msgid "\t\tMove to first commit"
 msgstr "\t\tGå till första incheckning"
 
-#: gitk:2826
+#: gitk:2908
 msgid "\t\tMove to last commit"
 msgstr "\t\tGÃ¥ till sista incheckning"
 
-#: gitk:2827
-msgid ", p, i\tMove up one commit"
-msgstr ", p, i\tGÃ¥ en incheckning upp"
+#: gitk:2909
+msgid ", p, k\tMove up one commit"
+msgstr ", p, k\tGÃ¥ en incheckning upp"
 
-#: gitk:2828
-msgid ", n, k\tMove down one commit"
-msgstr ", n, k\tGÃ¥ en incheckning ned"
+#: gitk:2910
+msgid ", n, j\tMove down one commit"
+msgstr ", n, j\tGÃ¥ en incheckning ned"
 
-#: gitk:2829
-msgid ", z, j\tGo back in history list"
-msgstr ", z, j\tGå bakåt i historiken"
+#: gitk:2911
+msgid ", z, h\tGo back in history list"
+msgstr ", z, h\tGå bakåt i historiken"
 
-#: gitk:2830
+#: gitk:2912
 msgid ", x, l\tGo forward in history list"
 msgstr ", x, l\tGå framåt i historiken"
 
-#: gitk:2831
+#: gitk:2913
 msgid "\tMove up one page in commit list"
 msgstr "\tGÃ¥ upp en sida i incheckningslistan"
 
-#: gitk:2832
+#: gitk:2914
 msgid "\tMove down one page in commit list"
 msgstr "\tGÃ¥ ned en sida i incheckningslistan"
 
-#: gitk:2833
+#: gitk:2915
 #, tcl-format
 msgid "<%s-Home>\tScroll to top of commit list"
 msgstr "<%s-Home>\tRulla till början av incheckningslistan"
 
-#: gitk:2834
+#: gitk:2916
 #, tcl-format
 msgid "<%s-End>\tScroll to bottom of commit list"
 msgstr "<%s-End>\tRulla till slutet av incheckningslistan"
 
-#: gitk:2835
+#: gitk:2917
 #, tcl-format
 msgid "<%s-Up>\tScroll commit list up one line"
 msgstr "<%s-Upp>\tRulla incheckningslistan upp ett steg"
 
-#: gitk:2836
+#: gitk:2918
 #, tcl-format
 msgid "<%s-Down>\tScroll commit list down one line"
 msgstr "<%s-Ned>\tRulla incheckningslistan ned ett steg"
 
-#: gitk:2837
+#: gitk:2919
 #, tcl-format
 msgid "<%s-PageUp>\tScroll commit list up one page"
 msgstr "<%s-PageUp>\tRulla incheckningslistan upp en sida"
 
-#: gitk:2838
+#: gitk:2920
 #, tcl-format
 msgid "<%s-PageDown>\tScroll commit list down one page"
 msgstr "<%s-PageDown>\tRulla incheckningslistan ned en sida"
 
-#: gitk:2839
+#: gitk:2921
 msgid "\tFind backwards (upwards, later commits)"
 msgstr "\tSök bakåt (uppåt, senare incheckningar)"
 
-#: gitk:2840
+#: gitk:2922
 msgid "\tFind forwards (downwards, earlier commits)"
 msgstr "\tSök framåt (nedåt, tidigare incheckningar)"
 
-#: gitk:2841
+#: gitk:2923
 msgid ", b\tScroll diff view up one page"
 msgstr ", b\tRulla diffvisningen upp en sida"
 
-#: gitk:2842
+#: gitk:2924
 msgid "\tScroll diff view up one page"
 msgstr "\tRulla diffvisningen upp en sida"
 
-#: gitk:2843
+#: gitk:2925
 msgid "\t\tScroll diff view down one page"
 msgstr "\tRulla diffvisningen ned en sida"
 
-#: gitk:2844
+#: gitk:2926
 msgid "u\t\tScroll diff view up 18 lines"
 msgstr "u\t\tRulla diffvisningen upp 18 rader"
 
-#: gitk:2845
+#: gitk:2927
 msgid "d\t\tScroll diff view down 18 lines"
 msgstr "d\t\tRulla diffvisningen ned 18 rader"
 
-#: gitk:2846
+#: gitk:2928
 #, tcl-format
 msgid "<%s-F>\t\tFind"
 msgstr "<%s-F>\t\tSök"
 
-#: gitk:2847
+#: gitk:2929
 #, tcl-format
 msgid "<%s-G>\t\tMove to next find hit"
 msgstr "<%s-G>\t\tGå till nästa sökträff"
 
-#: gitk:2848
+#: gitk:2930
 msgid "\tMove to next find hit"
 msgstr "\t\tGå till nästa sökträff"
 
-#: gitk:2849
+#: gitk:2931
 msgid "/\t\tFocus the search box"
 msgstr "/\t\tFokusera sökrutan"
 
-#: gitk:2850
+#: gitk:2932
 msgid "?\t\tMove to previous find hit"
 msgstr "?\t\tGå till föregående sökträff"
 
-#: gitk:2851
+#: gitk:2933
 msgid "f\t\tScroll diff view to next file"
 msgstr "f\t\tRulla diffvisningen till nästa fil"
 
-#: gitk:2852
+#: gitk:2934
 #, tcl-format
 msgid "<%s-S>\t\tSearch for next hit in diff view"
 msgstr "<%s-S>\t\tGå till nästa sökträff i diffvisningen"
 
-#: gitk:2853
+#: gitk:2935
 #, tcl-format
 msgid "<%s-R>\t\tSearch for previous hit in diff view"
 msgstr "<%s-R>\t\tGå till föregående sökträff i diffvisningen"
 
-#: gitk:2854
+#: gitk:2936
 #, tcl-format
 msgid "<%s-KP+>\tIncrease font size"
 msgstr "<%s-Num+>\tÖka teckenstorlek"
 
-#: gitk:2855
+#: gitk:2937
 #, tcl-format
 msgid "<%s-plus>\tIncrease font size"
 msgstr "<%s-plus>\tÖka teckenstorlek"
 
-#: gitk:2856
+#: gitk:2938
 #, tcl-format
 msgid "<%s-KP->\tDecrease font size"
 msgstr "<%s-Num->\tMinska teckenstorlek"
 
-#: gitk:2857
+#: gitk:2939
 #, tcl-format
 msgid "<%s-minus>\tDecrease font size"
 msgstr "<%s-minus>\tMinska teckenstorlek"
 
-#: gitk:2858
+#: gitk:2940
 msgid "\t\tUpdate"
 msgstr "\t\tUppdatera"
 
-#: gitk:3313 gitk:3322
+#: gitk:3395 gitk:3404
 #, tcl-format
 msgid "Error creating temporary directory %s:"
 msgstr "Fel vid skapande av temporär katalog %s:"
 
-#: gitk:3335
+#: gitk:3417
 #, tcl-format
 msgid "Error getting \"%s\" from %s:"
 msgstr "Fel vid hämtning av  \"%s\" från %s:"
 
-#: gitk:3398
+#: gitk:3480
 msgid "command failed:"
 msgstr "kommando misslyckades:"
 
-#: gitk:3547
+#: gitk:3629
 msgid "No such commit"
 msgstr "Incheckning saknas"
 
-#: gitk:3561
+#: gitk:3643
 msgid "git gui blame: command failed:"
 msgstr "git gui blame: kommando misslyckades:"
 
-#: gitk:3592
+#: gitk:3674
 #, tcl-format
 msgid "Couldn't read merge head: %s"
 msgstr "Kunde inte läsa sammanslagningshuvud: %s"
 
-#: gitk:3600
+#: gitk:3682
 #, tcl-format
 msgid "Error reading index: %s"
 msgstr "Fel vid läsning av index: %s"
 
-#: gitk:3625
+#: gitk:3707
 #, tcl-format
 msgid "Couldn't start git blame: %s"
 msgstr "Kunde inte starta git blame: %s"
 
-#: gitk:3628 gitk:6419
+#: gitk:3710 gitk:6527
 msgid "Searching"
 msgstr "Söker"
 
-#: gitk:3660
+#: gitk:3742
 #, tcl-format
 msgid "Error running git blame: %s"
 msgstr "Fel vid körning av git blame: %s"
 
-#: gitk:3688
+#: gitk:3770
 #, tcl-format
 msgid "That line comes from commit %s,  which is not in this view"
 msgstr "Raden kommer från incheckningen %s, som inte finns i denna vy"
 
-#: gitk:3702
+#: gitk:3784
 msgid "External diff viewer failed:"
 msgstr "Externt diff-verktyg misslyckades:"
 
-#: gitk:3820
+#: gitk:3902
 msgid "Gitk view definition"
 msgstr "Definition av Gitk-vy"
 
-#: gitk:3824
+#: gitk:3906
 msgid "Remember this view"
 msgstr "Spara denna vy"
 
-#: gitk:3825
+#: gitk:3907
 msgid "References (space separated list):"
 msgstr "Referenser (blankstegsavdelad lista):"
 
-#: gitk:3826
+#: gitk:3908
 msgid "Branches & tags:"
 msgstr "Grenar & taggar:"
 
-#: gitk:3827
+#: gitk:3909
 msgid "All refs"
 msgstr "Alla referenser"
 
-#: gitk:3828
+#: gitk:3910
 msgid "All (local) branches"
 msgstr "Alla (lokala) grenar"
 
-#: gitk:3829
+#: gitk:3911
 msgid "All tags"
 msgstr "Alla taggar"
 
-#: gitk:3830
+#: gitk:3912
 msgid "All remote-tracking branches"
 msgstr "Alla fjärrspårande grenar"
 
-#: gitk:3831
+#: gitk:3913
 msgid "Commit Info (regular expressions):"
 msgstr "Incheckningsinfo (reguljära uttryck):"
 
-#: gitk:3832
+#: gitk:3914
 msgid "Author:"
 msgstr "Författare:"
 
-#: gitk:3833
+#: gitk:3915
 msgid "Committer:"
 msgstr "Incheckare:"
 
-#: gitk:3834
+#: gitk:3916
 msgid "Commit Message:"
 msgstr "Incheckningsmeddelande:"
 
-#: gitk:3835
+#: gitk:3917
 msgid "Matches all Commit Info criteria"
 msgstr "Motsvarar alla kriterier för incheckningsinfo"
 
-#: gitk:3836
+#: gitk:3918
 msgid "Changes to Files:"
 msgstr "Ändringar av filer:"
 
-#: gitk:3837
+#: gitk:3919
 msgid "Fixed String"
 msgstr "Fast sträng"
 
-#: gitk:3838
+#: gitk:3920
 msgid "Regular Expression"
 msgstr "Reguljärt uttryck"
 
-#: gitk:3839
+#: gitk:3921
 msgid "Search string:"
 msgstr "Söksträng:"
 
-#: gitk:3840
+#: gitk:3922
 msgid ""
 "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
 "15:27:38\"):"
@@ -648,201 +670,197 @@ msgstr ""
 "Incheckingsdatum (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
 "15:27:38\"):"
 
-#: gitk:3841
+#: gitk:3923
 msgid "Since:"
 msgstr "Från:"
 
-#: gitk:3842
+#: gitk:3924
 msgid "Until:"
 msgstr "Till:"
 
-#: gitk:3843
+#: gitk:3925
 msgid "Limit and/or skip a number of revisions (positive integer):"
 msgstr "Begränsa och/eller hoppa över ett antal revisioner (positivt heltal):"
 
-#: gitk:3844
+#: gitk:3926
 msgid "Number to show:"
 msgstr "Antal att visa:"
 
-#: gitk:3845
+#: gitk:3927
 msgid "Number to skip:"
 msgstr "Antal att hoppa över:"
 
-#: gitk:3846
+#: gitk:3928
 msgid "Miscellaneous options:"
 msgstr "Diverse alternativ:"
 
-#: gitk:3847
+#: gitk:3929
 msgid "Strictly sort by date"
 msgstr "Strikt datumsortering"
 
-#: gitk:3848
+#: gitk:3930
 msgid "Mark branch sides"
 msgstr "Markera sidogrenar"
 
-#: gitk:3849
+#: gitk:3931
 msgid "Limit to first parent"
 msgstr "Begränsa till första förälder"
 
-#: gitk:3850
+#: gitk:3932
 msgid "Simple history"
 msgstr "Enkel historik"
 
-#: gitk:3851
+#: gitk:3933
 msgid "Additional arguments to git log:"
 msgstr "Ytterligare argument till git log:"
 
-#: gitk:3852
+#: gitk:3934
 msgid "Enter files and directories to include, one per line:"
 msgstr "Ange filer och kataloger att ta med, en per rad:"
 
-#: gitk:3853
+#: gitk:3935
 msgid "Command to generate more commits to include:"
 msgstr "Kommando för att generera fler incheckningar att ta med:"
 
-#: gitk:3977
+#: gitk:4059
 msgid "Gitk: edit view"
 msgstr "Gitk: redigera vy"
 
-#: gitk:3985
+#: gitk:4067
 msgid "-- criteria for selecting revisions"
 msgstr " - kriterier för val av revisioner"
 
-#: gitk:3990
+#: gitk:4072
 msgid "View Name"
 msgstr "Namn på vy"
 
-#: gitk:4065
+#: gitk:4147
 msgid "Apply (F5)"
 msgstr "Använd (F5)"
 
-#: gitk:4103
+#: gitk:4185
 msgid "Error in commit selection arguments:"
 msgstr "Fel i argument för val av incheckningar:"
 
-#: gitk:4156 gitk:4208 gitk:4656 gitk:4670 gitk:5931 gitk:11551 gitk:11552
+#: gitk:4238 gitk:4290 gitk:4749 gitk:4763 gitk:6027 gitk:11849 gitk:11850
 msgid "None"
 msgstr "Inget"
 
-#: gitk:4604 gitk:6451 gitk:8309 gitk:8324
-msgid "Date"
-msgstr "Datum"
-
-#: gitk:4604 gitk:6451
-msgid "CDate"
-msgstr "Skapat datum"
-
-#: gitk:4753 gitk:4758
+#: gitk:4846 gitk:4851
 msgid "Descendant"
 msgstr "Avkomling"
 
-#: gitk:4754
+#: gitk:4847
 msgid "Not descendant"
 msgstr "Inte avkomling"
 
-#: gitk:4761 gitk:4766
+#: gitk:4854 gitk:4859
 msgid "Ancestor"
 msgstr "Förfader"
 
-#: gitk:4762
+#: gitk:4855
 msgid "Not ancestor"
 msgstr "Inte förfader"
 
-#: gitk:5052
+#: gitk:5145
 msgid "Local changes checked in to index but not committed"
 msgstr "Lokala ändringar sparade i indexet men inte incheckade"
 
-#: gitk:5088
+#: gitk:5181
 msgid "Local uncommitted changes, not checked in to index"
 msgstr "Lokala ändringar, ej sparade i indexet"
 
-#: gitk:6769
+#: gitk:6882
 msgid "many"
 msgstr "många"
 
-#: gitk:6952
+#: gitk:7065
 msgid "Tags:"
 msgstr "Taggar:"
 
-#: gitk:6969 gitk:6975 gitk:8302
+#: gitk:7082 gitk:7088 gitk:8500
 msgid "Parent"
 msgstr "Förälder"
 
-#: gitk:6980
+#: gitk:7093
 msgid "Child"
 msgstr "Barn"
 
-#: gitk:6989
+#: gitk:7102
 msgid "Branch"
 msgstr "Gren"
 
-#: gitk:6992
+#: gitk:7105
 msgid "Follows"
 msgstr "Följer"
 
-#: gitk:6995
+#: gitk:7108
 msgid "Precedes"
 msgstr "Föregår"
 
-#: gitk:7532
+#: gitk:7653
 #, tcl-format
 msgid "Error getting diffs: %s"
 msgstr "Fel vid hämtning av diff: %s"
 
-#: gitk:8130
+#: gitk:8328
 msgid "Goto:"
 msgstr "GÃ¥ till:"
 
-#: gitk:8151
+#: gitk:8349
 #, tcl-format
 msgid "Short SHA1 id %s is ambiguous"
 msgstr "Förkortat SHA1-id %s är tvetydigt"
 
-#: gitk:8158
+#: gitk:8356
 #, tcl-format
 msgid "Revision %s is not known"
 msgstr "Revisionen %s är inte känd"
 
-#: gitk:8168
+#: gitk:8366
 #, tcl-format
 msgid "SHA1 id %s is not known"
 msgstr "SHA-id:t %s är inte känt"
 
-#: gitk:8170
+#: gitk:8368
 #, tcl-format
 msgid "Revision %s is not in the current view"
 msgstr "Revisionen %s finns inte i den nuvarande vyn"
 
-#: gitk:8312
+#: gitk:8507 gitk:8522
+msgid "Date"
+msgstr "Datum"
+
+#: gitk:8510
 msgid "Children"
 msgstr "Barn"
 
-#: gitk:8370
+#: gitk:8573
 #, tcl-format
 msgid "Reset %s branch to here"
 msgstr "Återställ grenen %s hit"
 
-#: gitk:8372
+#: gitk:8575
 msgid "Detached head: can't reset"
 msgstr "Frånkopplad head: kan inte återställa"
 
-#: gitk:8481 gitk:8487
+#: gitk:8680 gitk:8686
 msgid "Skipping merge commit "
 msgstr "Hoppar över sammanslagningsincheckning "
 
-#: gitk:8496 gitk:8501
+#: gitk:8695 gitk:8700
 msgid "Error getting patch ID for "
 msgstr "Fel vid hämtning av patch-id för "
 
-#: gitk:8497 gitk:8502
+#: gitk:8696 gitk:8701
 msgid " - stopping\n"
 msgstr " - stannar\n"
 
-#: gitk:8507 gitk:8510 gitk:8518 gitk:8532 gitk:8541
+#: gitk:8706 gitk:8709 gitk:8717 gitk:8731 gitk:8740
 msgid "Commit "
 msgstr "Incheckning "
 
-#: gitk:8511
+#: gitk:8710
 msgid ""
 " is the same patch as\n"
 "       "
@@ -850,7 +868,7 @@ msgstr ""
 " är samma patch som\n"
 "       "
 
-#: gitk:8519
+#: gitk:8718
 msgid ""
 " differs from\n"
 "       "
@@ -858,7 +876,7 @@ msgstr ""
 " skiljer sig från\n"
 "       "
 
-#: gitk:8521
+#: gitk:8720
 msgid ""
 "Diff of commits:\n"
 "\n"
@@ -866,131 +884,131 @@ msgstr ""
 "Skillnad mellan incheckningar:\n"
 "\n"
 
-#: gitk:8533 gitk:8542
+#: gitk:8732 gitk:8741
 #, tcl-format
 msgid " has %s children - stopping\n"
 msgstr " har %s barn - stannar\n"
 
-#: gitk:8561
+#: gitk:8760
 #, tcl-format
 msgid "Error writing commit to file: %s"
 msgstr "Fel vid skrivning av incheckning till fil: %s"
 
-#: gitk:8567
+#: gitk:8766
 #, tcl-format
 msgid "Error diffing commits: %s"
 msgstr "Fel vid jämförelse av incheckningar: %s"
 
-#: gitk:8598
+#: gitk:8812
 msgid "Top"
 msgstr "Topp"
 
-#: gitk:8599
+#: gitk:8813
 msgid "From"
 msgstr "Från"
 
-#: gitk:8604
+#: gitk:8818
 msgid "To"
 msgstr "Till"
 
-#: gitk:8628
+#: gitk:8842
 msgid "Generate patch"
 msgstr "Generera patch"
 
-#: gitk:8630
+#: gitk:8844
 msgid "From:"
 msgstr "Från:"
 
-#: gitk:8639
+#: gitk:8853
 msgid "To:"
 msgstr "Till:"
 
-#: gitk:8648
+#: gitk:8862
 msgid "Reverse"
 msgstr "Vänd"
 
-#: gitk:8650 gitk:8845
+#: gitk:8864 gitk:9059
 msgid "Output file:"
 msgstr "Utdatafil:"
 
-#: gitk:8656
+#: gitk:8870
 msgid "Generate"
 msgstr "Generera"
 
-#: gitk:8694
+#: gitk:8908
 msgid "Error creating patch:"
 msgstr "Fel vid generering av patch:"
 
-#: gitk:8717 gitk:8833 gitk:8890
+#: gitk:8931 gitk:9047 gitk:9104
 msgid "ID:"
 msgstr "Id:"
 
-#: gitk:8726
+#: gitk:8940
 msgid "Tag name:"
 msgstr "Taggnamn:"
 
-#: gitk:8729
+#: gitk:8943
 msgid "Tag message is optional"
 msgstr "Taggmeddelandet är valfritt"
 
-#: gitk:8731
+#: gitk:8945
 msgid "Tag message:"
 msgstr "Taggmeddelande:"
 
-#: gitk:8735 gitk:8899
+#: gitk:8949 gitk:9113
 msgid "Create"
 msgstr "Skapa"
 
-#: gitk:8753
+#: gitk:8967
 msgid "No tag name specified"
 msgstr "Inget taggnamn angavs"
 
-#: gitk:8757
+#: gitk:8971
 #, tcl-format
 msgid "Tag \"%s\" already exists"
 msgstr "Taggen \"%s\" finns redan"
 
-#: gitk:8767
+#: gitk:8981
 msgid "Error creating tag:"
 msgstr "Fel vid skapande av tagg:"
 
-#: gitk:8842
+#: gitk:9056
 msgid "Command:"
 msgstr "Kommando:"
 
-#: gitk:8850
+#: gitk:9064
 msgid "Write"
 msgstr "Skriv"
 
-#: gitk:8868
+#: gitk:9082
 msgid "Error writing commit:"
 msgstr "Fel vid skrivning av incheckning:"
 
-#: gitk:8895
+#: gitk:9109
 msgid "Name:"
 msgstr "Namn:"
 
-#: gitk:8918
+#: gitk:9132
 msgid "Please specify a name for the new branch"
 msgstr "Ange ett namn för den nya grenen"
 
-#: gitk:8923
+#: gitk:9137
 #, tcl-format
 msgid "Branch '%s' already exists. Overwrite?"
 msgstr "Grenen \"%s\" finns redan. Skriva över?"
 
-#: gitk:8989
+#: gitk:9204
 #, tcl-format
 msgid "Commit %s is already included in branch %s -- really re-apply it?"
 msgstr ""
 "Incheckningen %s finns redan på grenen %s -- skall den verkligen appliceras "
 "på nytt?"
 
-#: gitk:8994
+#: gitk:9209
 msgid "Cherry-picking"
 msgstr "Plockar"
 
-#: gitk:9003
+#: gitk:9218
 #, tcl-format
 msgid ""
 "Cherry-pick failed because of local changes to file '%s'.\n"
@@ -1000,7 +1018,7 @@ msgstr ""
 "Checka in, återställ eller spara undan (stash) dina ändringar och försök "
 "igen."
 
-#: gitk:9009
+#: gitk:9224
 msgid ""
 "Cherry-pick failed because of merge conflict.\n"
 "Do you wish to run git citool to resolve it?"
@@ -1008,32 +1026,32 @@ msgstr ""
 "Cherry-pick misslyckades på grund av en sammanslagningskonflikt.\n"
 "Vill du köra git citool för att lösa den?"
 
-#: gitk:9025
+#: gitk:9240
 msgid "No changes committed"
 msgstr "Inga ändringar incheckade"
 
-#: gitk:9051
+#: gitk:9266
 msgid "Confirm reset"
 msgstr "Bekräfta återställning"
 
-#: gitk:9053
+#: gitk:9268
 #, tcl-format
 msgid "Reset branch %s to %s?"
 msgstr "Återställa grenen %s till %s?"
 
-#: gitk:9055
+#: gitk:9270
 msgid "Reset type:"
 msgstr "Typ av återställning:"
 
-#: gitk:9058
+#: gitk:9273
 msgid "Soft: Leave working tree and index untouched"
 msgstr "Mjuk: Rör inte utcheckning och index"
 
-#: gitk:9061
+#: gitk:9276
 msgid "Mixed: Leave working tree untouched, reset index"
 msgstr "Blandad: Rör inte utcheckning, återställ index"
 
-#: gitk:9064
+#: gitk:9279
 msgid ""
 "Hard: Reset working tree and index\n"
 "(discard ALL local changes)"
@@ -1041,19 +1059,19 @@ msgstr ""
 "Hård: Återställ utcheckning och index\n"
 "(förkastar ALLA lokala ändringar)"
 
-#: gitk:9081
+#: gitk:9296
 msgid "Resetting"
 msgstr "Återställer"
 
-#: gitk:9141
+#: gitk:9356
 msgid "Checking out"
 msgstr "Checkar ut"
 
-#: gitk:9194
+#: gitk:9409
 msgid "Cannot delete the currently checked-out branch"
 msgstr "Kan inte ta bort den just nu utcheckade grenen"
 
-#: gitk:9200
+#: gitk:9415
 #, tcl-format
 msgid ""
 "The commits on branch %s aren't on any other branch.\n"
@@ -1062,16 +1080,16 @@ msgstr ""
 "Incheckningarna på grenen %s existerar inte på någon annan gren.\n"
 "Vill du verkligen ta bort grenen %s?"
 
-#: gitk:9231
+#: gitk:9446
 #, tcl-format
 msgid "Tags and heads: %s"
 msgstr "Taggar och huvuden: %s"
 
-#: gitk:9246
+#: gitk:9461
 msgid "Filter"
 msgstr "Filter"
 
-#: gitk:9541
+#: gitk:9757
 msgid ""
 "Error reading commit topology information; branch and preceding/following "
 "tag information will be incomplete."
@@ -1079,206 +1097,219 @@ msgstr ""
 "Fel vid läsning av information om incheckningstopologi; information om "
 "grenar och föregående/senare taggar kommer inte vara komplett."
 
-#: gitk:10527
+#: gitk:10744
 msgid "Tag"
 msgstr "Tagg"
 
-#: gitk:10527
+#: gitk:10744
 msgid "Id"
 msgstr "Id"
 
-#: gitk:10576
+#: gitk:10793
 msgid "Gitk font chooser"
 msgstr "Teckensnittsväljare för Gitk"
 
-#: gitk:10593
+#: gitk:10810
 msgid "B"
 msgstr "F"
 
-#: gitk:10596
+#: gitk:10813
 msgid "I"
 msgstr "K"
 
-#: gitk:10714
-msgid "Gitk preferences"
-msgstr "Inställningar för Gitk"
-
-#: gitk:10716
+#: gitk:10931
 msgid "Commit list display options"
 msgstr "Alternativ för incheckningslistvy"
 
-#: gitk:10719
+#: gitk:10934
 msgid "Maximum graph width (lines)"
 msgstr "Maximal grafbredd (rader)"
 
-#: gitk:10722
+#: gitk:10937
 #, tcl-format
 msgid "Maximum graph width (% of pane)"
 msgstr "Maximal grafbredd (% av ruta)"
 
-#: gitk:10725
+#: gitk:10940
 msgid "Show local changes"
 msgstr "Visa lokala ändringar"
 
-#: gitk:10728
-msgid "Auto-select SHA1"
-msgstr "Välj SHA1 automatiskt"
+#: gitk:10943
+msgid "Auto-select SHA1 (length)"
+msgstr "Välj SHA1 (längd) automatiskt"
 
-#: gitk:10731
+#: gitk:10947
 msgid "Hide remote refs"
 msgstr "Dölj fjärr-referenser"
 
-#: gitk:10735
+#: gitk:10951
 msgid "Diff display options"
 msgstr "Alternativ för diffvy"
 
-#: gitk:10737
+#: gitk:10953
 msgid "Tab spacing"
 msgstr "Blanksteg för tabulatortecken"
 
-#: gitk:10740
+#: gitk:10956
 msgid "Display nearby tags"
 msgstr "Visa närliggande taggar"
 
-#: gitk:10743
+#: gitk:10959
 msgid "Limit diffs to listed paths"
 msgstr "Begränsa diff till listade sökvägar"
 
-#: gitk:10746
+#: gitk:10962
 msgid "Support per-file encodings"
 msgstr "Stöd för filspecifika teckenkodningar"
 
-#: gitk:10752 gitk:10832
+#: gitk:10968 gitk:11115
 msgid "External diff tool"
 msgstr "Externt diff-verktyg"
 
-#: gitk:10753
+#: gitk:10969
 msgid "Choose..."
 msgstr "Välj..."
 
-#: gitk:10758
+#: gitk:10974
 msgid "General options"
 msgstr "Allmänna inställningar"
 
-#: gitk:10761
+#: gitk:10977
 msgid "Use themed widgets"
 msgstr "Använd tema på fönsterelement"
 
-#: gitk:10763
+#: gitk:10979
 msgid "(change requires restart)"
 msgstr "(ändringen kräver omstart)"
 
-#: gitk:10765
+#: gitk:10981
 msgid "(currently unavailable)"
 msgstr "(för närvarande inte tillgängligt)"
 
-#: gitk:10769
+#: gitk:10992
 msgid "Colors: press to choose"
 msgstr "Färger: tryck för att välja"
 
-#: gitk:10772
+#: gitk:10995
 msgid "Interface"
 msgstr "Gränssnitt"
 
-#: gitk:10773
+#: gitk:10996
 msgid "interface"
 msgstr "gränssnitt"
 
-#: gitk:10776
+#: gitk:10999
 msgid "Background"
 msgstr "Bakgrund"
 
-#: gitk:10777 gitk:10807
+#: gitk:11000 gitk:11030
 msgid "background"
 msgstr "bakgrund"
 
-#: gitk:10780
+#: gitk:11003
 msgid "Foreground"
 msgstr "Förgrund"
 
-#: gitk:10781
+#: gitk:11004
 msgid "foreground"
 msgstr "förgrund"
 
-#: gitk:10784
+#: gitk:11007
 msgid "Diff: old lines"
 msgstr "Diff: gamla rader"
 
-#: gitk:10785
+#: gitk:11008
 msgid "diff old lines"
 msgstr "diff gamla rader"
 
-#: gitk:10789
+#: gitk:11012
 msgid "Diff: new lines"
 msgstr "Diff: nya rader"
 
-#: gitk:10790
+#: gitk:11013
 msgid "diff new lines"
 msgstr "diff nya rader"
 
-#: gitk:10794
+#: gitk:11017
 msgid "Diff: hunk header"
 msgstr "Diff: delhuvud"
 
-#: gitk:10796
+#: gitk:11019
 msgid "diff hunk header"
 msgstr "diff delhuvud"
 
-#: gitk:10800
+#: gitk:11023
 msgid "Marked line bg"
 msgstr "Markerad rad bakgrund"
 
-#: gitk:10802
+#: gitk:11025
 msgid "marked line background"
 msgstr "markerad rad bakgrund"
 
-#: gitk:10806
+#: gitk:11029
 msgid "Select bg"
 msgstr "Markerad bakgrund"
 
-#: gitk:10810
+#: gitk:11038
 msgid "Fonts: press to choose"
 msgstr "Teckensnitt: tryck för att välja"
 
-#: gitk:10812
+#: gitk:11040
 msgid "Main font"
 msgstr "Huvudteckensnitt"
 
-#: gitk:10813
+#: gitk:11041
 msgid "Diff display font"
 msgstr "Teckensnitt för diffvisning"
 
-#: gitk:10814
+#: gitk:11042
 msgid "User interface font"
 msgstr "Teckensnitt för användargränssnitt"
 
-#: gitk:10842
+#: gitk:11064
+msgid "Gitk preferences"
+msgstr "Inställningar för Gitk"
+
+#: gitk:11073
+msgid "General"
+msgstr "Allmänt"
+
+#: gitk:11074
+msgid "Colors"
+msgstr "Färger"
+
+#: gitk:11075
+msgid "Fonts"
+msgstr "Teckensnitt"
+
+#: gitk:11125
 #, tcl-format
 msgid "Gitk: choose color for %s"
 msgstr "Gitk: välj färg för %s"
 
-#: gitk:11445
+#: gitk:11745
 msgid "Cannot find a git repository here."
 msgstr "Hittar inget git-arkiv här."
 
-#: gitk:11449
-#, tcl-format
-msgid "Cannot find the git directory \"%s\"."
-msgstr "Hittar inte git-katalogen \"%s\"."
-
-#: gitk:11496
+#: gitk:11792
 #, tcl-format
 msgid "Ambiguous argument '%s': both revision and filename"
 msgstr "Tvetydigt argument \"%s\": både revision och filnamn"
 
-#: gitk:11508
+#: gitk:11804
 msgid "Bad arguments to gitk:"
 msgstr "Felaktiga argument till gitk:"
 
-#: gitk:11604
+#: gitk:11907
 msgid "Command line"
 msgstr "Kommandorad"
 
+#~ msgid "CDate"
+#~ msgstr "Skapat datum"
+
+#~ msgid "Cannot find the git directory \"%s\"."
+#~ msgstr "Hittar inte git-katalogen \"%s\"."
+
 #~ msgid "SHA1 ID: "
 #~ msgstr "SHA1-id: "
 
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index a51a8babee..c6bafe6ead 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,
@@ -1557,7 +1556,7 @@ sub sanitize {
 	return undef unless defined $str;
 
 	$str = to_utf8($str);
-	$str =~ s|([[:cntrl:]])|($1 =~ /[\t\n\r]/ ? $1 : quot_cec($1))|eg;
+	$str =~ s|([[:cntrl:]])|(index("\t\n\r", $1) != -1 ? $1 : quot_cec($1))|eg;
 	return $str;
 }
 
@@ -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) {
@@ -5526,23 +5528,30 @@ sub fill_project_list_info {
 
 sub sort_projects_list {
 	my ($projlist, $order) = @_;
-	my @projects;
 
-	my %order_info = (
-		project => { key => 'path', type => 'str' },
-		descr => { key => 'descr_long', type => 'str' },
-		owner => { key => 'owner', type => 'str' },
-		age => { key => 'age', type => 'num' }
-	);
-	my $oi = $order_info{$order};
-	return @$projlist unless defined $oi;
-	if ($oi->{'type'} eq 'str') {
-		@projects = sort {$a->{$oi->{'key'}} cmp $b->{$oi->{'key'}}} @$projlist;
-	} else {
-		@projects = sort {$a->{$oi->{'key'}} <=> $b->{$oi->{'key'}}} @$projlist;
+	sub order_str {
+		my $key = shift;
+		return sub { $a->{$key} cmp $b->{$key} };
 	}
 
-	return @projects;
+	sub order_num_then_undef {
+		my $key = shift;
+		return sub {
+			defined $a->{$key} ?
+				(defined $b->{$key} ? $a->{$key} <=> $b->{$key} : -1) :
+				(defined $b->{$key} ? 1 : 0)
+		};
+	}
+
+	my %orderings = (
+		project => order_str('path'),
+		descr => order_str('descr_long'),
+		owner => order_str('owner'),
+		age => order_num_then_undef('age'),
+	);
+
+	my $ordering = $orderings{$order};
+	return defined $ordering ? sort $ordering @$projlist : @$projlist;
 }
 
 # returns a hash of categories, containing the list of project
diff --git a/graph.c b/graph.c
index e864fe2c6a..391a712e5e 100644
--- a/graph.c
+++ b/graph.c
@@ -1227,7 +1227,7 @@ void graph_show_commit(struct git_graph *graph)
 	if (!graph)
 		return;
 
-	while (!shown_commit_line) {
+	while (!shown_commit_line && !graph_is_commit_finished(graph)) {
 		shown_commit_line = graph_next_line(graph, &msgbuf);
 		fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout);
 		if (!shown_commit_line)
diff --git a/http-push.c b/http-push.c
index 8701c1215d..9923441a4e 100644
--- a/http-push.c
+++ b/http-push.c
@@ -172,28 +172,7 @@ enum dav_header_flag {
 static char *xml_entities(const char *s)
 {
 	struct strbuf buf = STRBUF_INIT;
-	while (*s) {
-		size_t len = strcspn(s, "\"<>&");
-		strbuf_add(&buf, s, len);
-		s += len;
-		switch (*s) {
-		case '"':
-			strbuf_addstr(&buf, """);
-			break;
-		case '<':
-			strbuf_addstr(&buf, "<");
-			break;
-		case '>':
-			strbuf_addstr(&buf, ">");
-			break;
-		case '&':
-			strbuf_addstr(&buf, "&");
-			break;
-		case 0:
-			return strbuf_detach(&buf, NULL);
-		}
-		s++;
-	}
+	strbuf_addstr_xml_quoted(&buf, s);
 	return strbuf_detach(&buf, NULL);
 }
 
diff --git a/http.c b/http.c
index f9a9de1416..44f35256e4 100644
--- a/http.c
+++ b/http.c
@@ -236,6 +236,7 @@ static int has_cert_password(void)
 		return 0;
 	if (!cert_auth.password) {
 		cert_auth.protocol = xstrdup("cert");
+		cert_auth.username = xstrdup("");
 		cert_auth.path = xstrdup(ssl_cert);
 		credential_fill(&cert_auth);
 	}
@@ -757,8 +758,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);
@@ -771,7 +771,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 {
@@ -833,7 +832,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/imap-send.c b/imap-send.c
index d42e471297..21dc20b57d 100644
--- a/imap-send.c
+++ b/imap-send.c
@@ -33,47 +33,6 @@ typedef void *SSL;
 #include 
 #endif
 
-struct store_conf {
-	char *name;
-	const char *path; /* should this be here? its interpretation is driver-specific */
-	char *map_inbox;
-	char *trash;
-	unsigned max_size; /* off_t is overkill */
-	unsigned trash_remote_new:1, trash_only_new:1;
-};
-
-/* For message->status */
-#define M_RECENT       (1<<0) /* unsyncable flag; maildir_* depend on this being 1<<0 */
-#define M_DEAD         (1<<1) /* expunged */
-#define M_FLAGS        (1<<2) /* flags fetched */
-
-struct message {
-	struct message *next;
-	size_t size; /* zero implies "not fetched" */
-	int uid;
-	unsigned char flags, status;
-};
-
-struct store {
-	struct store_conf *conf; /* foreign */
-
-	/* currently open mailbox */
-	const char *name; /* foreign! maybe preset? */
-	char *path; /* own */
-	struct message *msgs; /* own */
-	int uidvalidity;
-	unsigned char opts; /* maybe preset? */
-	/* note that the following do _not_ reflect stats from msgs, but mailbox totals */
-	int count; /* # of messages */
-	int recent; /* # of recent messages - don't trust this beyond the initial read */
-};
-
-struct msg_data {
-	char *data;
-	int len;
-	unsigned char flags;
-};
-
 static const char imap_send_usage[] = "git imap-send < ";
 
 #undef DRV_OK
@@ -91,8 +50,6 @@ static void imap_warn(const char *, ...);
 
 static char *next_arg(char **);
 
-static void free_generic_messages(struct message *);
-
 __attribute__((format (printf, 3, 4)))
 static int nfsnprintf(char *buf, int blen, const char *fmt, ...);
 
@@ -136,20 +93,6 @@ static struct imap_server_conf server = {
 	NULL,	/* auth_method */
 };
 
-struct imap_store_conf {
-	struct store_conf gen;
-	struct imap_server_conf *server;
-};
-
-#define NIL	(void *)0x1
-#define LIST	(void *)0x2
-
-struct imap_list {
-	struct imap_list *next, *child;
-	char *val;
-	int len;
-};
-
 struct imap_socket {
 	int fd[2];
 	SSL *ssl;
@@ -166,7 +109,6 @@ struct imap_cmd;
 
 struct imap {
 	int uidnext; /* from SELECT responses */
-	struct imap_list *ns_personal, *ns_other, *ns_shared; /* NAMESPACE info */
 	unsigned caps, rcaps; /* CAPABILITY results */
 	/* command queue */
 	int nexttag, num_in_progress, literal_pending;
@@ -175,11 +117,11 @@ struct imap {
 };
 
 struct imap_store {
-	struct store gen;
+	/* currently open mailbox */
+	const char *name; /* foreign! maybe preset? */
 	int uidvalidity;
 	struct imap *imap;
 	const char *prefix;
-	unsigned /*currentnc:1,*/ trashnc:1;
 };
 
 struct imap_cmd_cb {
@@ -226,14 +168,6 @@ static const char *cap_list[] = {
 static int get_cmd_result(struct imap_store *ctx, struct imap_cmd *tcmd);
 
 
-static const char *Flags[] = {
-	"Draft",
-	"Flagged",
-	"Answered",
-	"Seen",
-	"Deleted",
-};
-
 #ifndef NO_OPENSSL
 static void ssl_socket_perror(const char *func)
 {
@@ -477,16 +411,6 @@ static char *next_arg(char **s)
 	return ret;
 }
 
-static void free_generic_messages(struct message *msgs)
-{
-	struct message *tmsg;
-
-	for (; msgs; msgs = tmsg) {
-		tmsg = msgs->next;
-		free(msgs);
-	}
-}
-
 static int nfsnprintf(char *buf, int blen, const char *fmt, ...)
 {
 	int ret;
@@ -614,35 +538,9 @@ static int imap_exec_m(struct imap_store *ctx, struct imap_cmd_cb *cb,
 	}
 }
 
-static int is_atom(struct imap_list *list)
-{
-	return list && list->val && list->val != NIL && list->val != LIST;
-}
-
-static int is_list(struct imap_list *list)
+static int skip_imap_list_l(char **sp, int level)
 {
-	return list && list->val == LIST;
-}
-
-static void free_list(struct imap_list *list)
-{
-	struct imap_list *tmp;
-
-	for (; list; list = tmp) {
-		tmp = list->next;
-		if (is_list(list))
-			free_list(list->child);
-		else if (is_atom(list))
-			free(list->val);
-		free(list);
-	}
-}
-
-static int parse_imap_list_l(struct imap *imap, char **sp, struct imap_list **curp, int level)
-{
-	struct imap_list *cur;
-	char *s = *sp, *p;
-	int n, bytes;
+	char *s = *sp;
 
 	for (;;) {
 		while (isspace((unsigned char)*s))
@@ -651,68 +549,23 @@ static int parse_imap_list_l(struct imap *imap, char **sp, struct imap_list **cu
 			s++;
 			break;
 		}
-		*curp = cur = xmalloc(sizeof(*cur));
-		curp = &cur->next;
-		cur->val = NULL; /* for clean bail */
 		if (*s == '(') {
 			/* sublist */
 			s++;
-			cur->val = LIST;
-			if (parse_imap_list_l(imap, &s, &cur->child, level + 1))
-				goto bail;
-		} else if (imap && *s == '{') {
-			/* literal */
-			bytes = cur->len = strtol(s + 1, &s, 10);
-			if (*s != '}')
-				goto bail;
-
-			s = cur->val = xmalloc(cur->len);
-
-			/* dump whats left over in the input buffer */
-			n = imap->buf.bytes - imap->buf.offset;
-
-			if (n > bytes)
-				/* the entire message fit in the buffer */
-				n = bytes;
-
-			memcpy(s, imap->buf.buf + imap->buf.offset, n);
-			s += n;
-			bytes -= n;
-
-			/* mark that we used part of the buffer */
-			imap->buf.offset += n;
-
-			/* now read the rest of the message */
-			while (bytes > 0) {
-				if ((n = socket_read(&imap->buf.sock, s, bytes)) <= 0)
-					goto bail;
-				s += n;
-				bytes -= n;
-			}
-
-			if (buffer_gets(&imap->buf, &s))
+			if (skip_imap_list_l(&s, level + 1))
 				goto bail;
 		} else if (*s == '"') {
 			/* quoted string */
 			s++;
-			p = s;
 			for (; *s != '"'; s++)
 				if (!*s)
 					goto bail;
-			cur->len = s - p;
 			s++;
-			cur->val = xmemdupz(p, cur->len);
 		} else {
 			/* atom */
-			p = s;
 			for (; *s && !isspace((unsigned char)*s); s++)
 				if (level && *s == ')')
 					break;
-			cur->len = s - p;
-			if (cur->len == 3 && !memcmp("NIL", p, 3))
-				cur->val = NIL;
-			else
-				cur->val = xmemdupz(p, cur->len);
 		}
 
 		if (!level)
@@ -721,27 +574,15 @@ static int parse_imap_list_l(struct imap *imap, char **sp, struct imap_list **cu
 			goto bail;
 	}
 	*sp = s;
-	*curp = NULL;
 	return 0;
 
 bail:
-	*curp = NULL;
 	return -1;
 }
 
-static struct imap_list *parse_imap_list(struct imap *imap, char **sp)
+static void skip_list(char **sp)
 {
-	struct imap_list *head;
-
-	if (!parse_imap_list_l(imap, sp, &head, 0))
-		return head;
-	free_list(head);
-	return NULL;
-}
-
-static struct imap_list *parse_list(char **sp)
-{
-	return parse_imap_list(NULL, sp);
+	skip_imap_list_l(sp, 0);
 }
 
 static void parse_capability(struct imap *imap, char *cmd)
@@ -773,7 +614,7 @@ static int parse_response_code(struct imap_store *ctx, struct imap_cmd_cb *cb,
 	*p++ = 0;
 	arg = next_arg(&s);
 	if (!strcmp("UIDVALIDITY", arg)) {
-		if (!(arg = next_arg(&s)) || !(ctx->gen.uidvalidity = atoi(arg))) {
+		if (!(arg = next_arg(&s)) || !(ctx->uidvalidity = atoi(arg))) {
 			fprintf(stderr, "IMAP error: malformed UIDVALIDITY status\n");
 			return RESP_BAD;
 		}
@@ -791,7 +632,7 @@ static int parse_response_code(struct imap_store *ctx, struct imap_cmd_cb *cb,
 		for (; isspace((unsigned char)*p); p++);
 		fprintf(stderr, "*** IMAP ALERT *** %s\n", p);
 	} else if (cb && cb->ctx && !strcmp("APPENDUID", arg)) {
-		if (!(arg = next_arg(&s)) || !(ctx->gen.uidvalidity = atoi(arg)) ||
+		if (!(arg = next_arg(&s)) || !(ctx->uidvalidity = atoi(arg)) ||
 		    !(arg = next_arg(&s)) || !(*(int *)cb->ctx = atoi(arg))) {
 			fprintf(stderr, "IMAP error: malformed APPENDUID status\n");
 			return RESP_BAD;
@@ -820,20 +661,28 @@ static int get_cmd_result(struct imap_store *ctx, struct imap_cmd *tcmd)
 			}
 
 			if (!strcmp("NAMESPACE", arg)) {
-				imap->ns_personal = parse_list(&cmd);
-				imap->ns_other = parse_list(&cmd);
-				imap->ns_shared = parse_list(&cmd);
+				/* rfc2342 NAMESPACE response. */
+				skip_list(&cmd); /* Personal mailboxes */
+				skip_list(&cmd); /* Others' mailboxes */
+				skip_list(&cmd); /* Shared mailboxes */
 			} else if (!strcmp("OK", arg) || !strcmp("BAD", arg) ||
 				   !strcmp("NO", arg) || !strcmp("BYE", arg)) {
 				if ((resp = parse_response_code(ctx, NULL, cmd)) != RESP_OK)
 					return resp;
-			} else if (!strcmp("CAPABILITY", arg))
+			} else if (!strcmp("CAPABILITY", arg)) {
 				parse_capability(imap, cmd);
-			else if ((arg1 = next_arg(&cmd))) {
-				if (!strcmp("EXISTS", arg1))
-					ctx->gen.count = atoi(arg);
-				else if (!strcmp("RECENT", arg1))
-					ctx->gen.recent = atoi(arg);
+			} else if ((arg1 = next_arg(&cmd))) {
+				; /*
+				   * Unhandled response-data with at least two words.
+				   * Ignore it.
+				   *
+				   * NEEDSWORK: Previously this case handled ' EXISTS'
+				   * and ' RECENT' but as a probably-unintended side
+				   * effect it ignores other unrecognized two-word
+				   * responses.  imap-send doesn't ever try to read
+				   * messages or mailboxes these days, so consider
+				   * eliminating this case.
+				   */
 			} else {
 				fprintf(stderr, "IMAP error: unable to parse untagged response\n");
 				return RESP_BAD;
@@ -935,16 +784,12 @@ static void imap_close_server(struct imap_store *ictx)
 		imap_exec(ictx, NULL, "LOGOUT");
 		socket_shutdown(&imap->buf.sock);
 	}
-	free_list(imap->ns_personal);
-	free_list(imap->ns_other);
-	free_list(imap->ns_shared);
 	free(imap);
 }
 
-static void imap_close_store(struct store *ctx)
+static void imap_close_store(struct imap_store *ctx)
 {
-	imap_close_server((struct imap_store *)ctx);
-	free_generic_messages(ctx->msgs);
+	imap_close_server(ctx);
 	free(ctx);
 }
 
@@ -1029,7 +874,7 @@ static int auth_cram_md5(struct imap_store *ctx, struct imap_cmd *cmd, const cha
 	return 0;
 }
 
-static struct store *imap_open_store(struct imap_server_conf *srvc)
+static struct imap_store *imap_open_store(struct imap_server_conf *srvc)
 {
 	struct imap_store *ctx;
 	struct imap *imap;
@@ -1239,173 +1084,113 @@ static struct store *imap_open_store(struct imap_server_conf *srvc)
 	} /* !preauth */
 
 	ctx->prefix = "";
-	ctx->trashnc = 1;
-	return (struct store *)ctx;
+	return ctx;
 
 bail:
-	imap_close_store(&ctx->gen);
+	imap_close_store(ctx);
 	return NULL;
 }
 
-static int imap_make_flags(int flags, char *buf)
-{
-	const char *s;
-	unsigned i, d;
-
-	for (i = d = 0; i < ARRAY_SIZE(Flags); i++)
-		if (flags & (1 << i)) {
-			buf[d++] = ' ';
-			buf[d++] = '\\';
-			for (s = Flags[i]; *s; s++)
-				buf[d++] = *s;
-		}
-	buf[0] = '(';
-	buf[d++] = ')';
-	return d;
-}
-
-static void lf_to_crlf(struct msg_data *msg)
+/*
+ * Insert CR characters as necessary in *msg to ensure that every LF
+ * character in *msg is preceded by a CR.
+ */
+static void lf_to_crlf(struct strbuf *msg)
 {
 	char *new;
-	int i, j, lfnum = 0;
-
-	if (msg->data[0] == '\n')
-		lfnum++;
-	for (i = 1; i < msg->len; i++) {
-		if (msg->data[i - 1] != '\r' && msg->data[i] == '\n')
-			lfnum++;
+	size_t i, j;
+	char lastc;
+
+	/* First pass: tally, in j, the size of the new string: */
+	for (i = j = 0, lastc = '\0'; i < msg->len; i++) {
+		if (msg->buf[i] == '\n' && lastc != '\r')
+			j++; /* a CR will need to be added here */
+		lastc = msg->buf[i];
+		j++;
 	}
 
-	new = xmalloc(msg->len + lfnum);
-	if (msg->data[0] == '\n') {
-		new[0] = '\r';
-		new[1] = '\n';
-		i = 1;
-		j = 2;
-	} else {
-		new[0] = msg->data[0];
-		i = 1;
-		j = 1;
-	}
-	for ( ; i < msg->len; i++) {
-		if (msg->data[i] != '\n') {
-			new[j++] = msg->data[i];
-			continue;
-		}
-		if (msg->data[i - 1] != '\r')
+	new = xmalloc(j + 1);
+
+	/*
+	 * Second pass: write the new string.  Note that this loop is
+	 * otherwise identical to the first pass.
+	 */
+	for (i = j = 0, lastc = '\0'; i < msg->len; i++) {
+		if (msg->buf[i] == '\n' && lastc != '\r')
 			new[j++] = '\r';
-		/* otherwise it already had CR before */
-		new[j++] = '\n';
+		lastc = new[j++] = msg->buf[i];
 	}
-	msg->len += lfnum;
-	free(msg->data);
-	msg->data = new;
+	strbuf_attach(msg, new, j, j + 1);
 }
 
-static int imap_store_msg(struct store *gctx, struct msg_data *data)
+/*
+ * Store msg to IMAP.  Also detach and free the data from msg->data,
+ * leaving msg->data empty.
+ */
+static int imap_store_msg(struct imap_store *ctx, struct strbuf *msg)
 {
-	struct imap_store *ctx = (struct imap_store *)gctx;
 	struct imap *imap = ctx->imap;
 	struct imap_cmd_cb cb;
 	const char *prefix, *box;
-	int ret, d;
-	char flagstr[128];
+	int ret;
 
-	lf_to_crlf(data);
+	lf_to_crlf(msg);
 	memset(&cb, 0, sizeof(cb));
 
-	cb.dlen = data->len;
-	cb.data = xmalloc(cb.dlen);
-	memcpy(cb.data, data->data, data->len);
-
-	d = 0;
-	if (data->flags) {
-		d = imap_make_flags(data->flags, flagstr);
-		flagstr[d++] = ' ';
-	}
-	flagstr[d] = 0;
+	cb.dlen = msg->len;
+	cb.data = strbuf_detach(msg, NULL);
 
-	box = gctx->name;
+	box = ctx->name;
 	prefix = !strcmp(box, "INBOX") ? "" : ctx->prefix;
 	cb.create = 0;
-	ret = imap_exec_m(ctx, &cb, "APPEND \"%s%s\" %s", prefix, box, flagstr);
+	ret = imap_exec_m(ctx, &cb, "APPEND \"%s%s\" ", prefix, box);
 	imap->caps = imap->rcaps;
 	if (ret != DRV_OK)
 		return ret;
-	gctx->count++;
 
 	return DRV_OK;
 }
 
-static void encode_html_chars(struct strbuf *p)
-{
-	int i;
-	for (i = 0; i < p->len; i++) {
-		if (p->buf[i] == '&')
-			strbuf_splice(p, i, 1, "&", 5);
-		if (p->buf[i] == '<')
-			strbuf_splice(p, i, 1, "<", 4);
-		if (p->buf[i] == '>')
-			strbuf_splice(p, i, 1, ">", 4);
-		if (p->buf[i] == '"')
-			strbuf_splice(p, i, 1, """, 6);
-	}
-}
-static void wrap_in_html(struct msg_data *msg)
+static void wrap_in_html(struct strbuf *msg)
 {
 	struct strbuf buf = STRBUF_INIT;
-	struct strbuf **lines;
-	struct strbuf **p;
 	static char *content_type = "Content-Type: text/html;\n";
 	static char *pre_open = "
\n";
 	static char *pre_close = "
\n"; - int added_header = 0; - - strbuf_attach(&buf, msg->data, msg->len, msg->len); - lines = strbuf_split(&buf, '\n'); - strbuf_release(&buf); - for (p = lines; *p; p++) { - if (! added_header) { - if ((*p)->len == 1 && *((*p)->buf) == '\n') { - strbuf_addstr(&buf, content_type); - strbuf_addbuf(&buf, *p); - strbuf_addstr(&buf, pre_open); - added_header = 1; - continue; - } - } - else - encode_html_chars(*p); - strbuf_addbuf(&buf, *p); - } + const char *body = strstr(msg->buf, "\n\n"); + + if (!body) + return; /* Headers but no body; no wrapping needed */ + + body += 2; + + strbuf_add(&buf, msg->buf, body - msg->buf - 1); + strbuf_addstr(&buf, content_type); + strbuf_addch(&buf, '\n'); + strbuf_addstr(&buf, pre_open); + strbuf_addstr_xml_quoted(&buf, body); strbuf_addstr(&buf, pre_close); - strbuf_list_free(lines); - msg->len = buf.len; - msg->data = strbuf_detach(&buf, NULL); + + strbuf_release(msg); + *msg = buf; } #define CHUNKSIZE 0x1000 -static int read_message(FILE *f, struct msg_data *msg) +static int read_message(FILE *f, struct strbuf *all_msgs) { - struct strbuf buf = STRBUF_INIT; - - memset(msg, 0, sizeof(*msg)); - do { - if (strbuf_fread(&buf, CHUNKSIZE, f) <= 0) + if (strbuf_fread(all_msgs, CHUNKSIZE, f) <= 0) break; } while (!feof(f)); - msg->len = buf.len; - msg->data = strbuf_detach(&buf, NULL); - return msg->len; + return ferror(f) ? -1 : 0; } -static int count_messages(struct msg_data *msg) +static int count_messages(struct strbuf *all_msgs) { int count = 0; - char *p = msg->data; + char *p = all_msgs->buf; while (1) { if (!prefixcmp(p, "From ")) { @@ -1426,34 +1211,39 @@ static int count_messages(struct msg_data *msg) return count; } -static int split_msg(struct msg_data *all_msgs, struct msg_data *msg, int *ofs) +/* + * Copy the next message from all_msgs, starting at offset *ofs, to + * msg. Update *ofs to the start of the following message. Return + * true iff a message was successfully copied. + */ +static int split_msg(struct strbuf *all_msgs, struct strbuf *msg, int *ofs) { char *p, *data; + size_t len; - memset(msg, 0, sizeof *msg); if (*ofs >= all_msgs->len) return 0; - data = &all_msgs->data[*ofs]; - msg->len = all_msgs->len - *ofs; + data = &all_msgs->buf[*ofs]; + len = all_msgs->len - *ofs; - if (msg->len < 5 || prefixcmp(data, "From ")) + if (len < 5 || prefixcmp(data, "From ")) return 0; p = strchr(data, '\n'); if (p) { - p = &p[1]; - msg->len -= p-data; - *ofs += p-data; + p++; + len -= p - data; + *ofs += p - data; data = p; } p = strstr(data, "\nFrom "); if (p) - msg->len = &p[1] - data; + len = &p[1] - data; - msg->data = xmemdupz(data, msg->len); - *ofs += msg->len; + strbuf_add(msg, data, len); + *ofs += len; return 1; } @@ -1504,8 +1294,9 @@ static int git_imap_config(const char *key, const char *val, void *cb) int main(int argc, char **argv) { - struct msg_data all_msgs, msg; - struct store *ctx = NULL; + struct strbuf all_msgs = STRBUF_INIT; + struct strbuf msg = STRBUF_INIT; + struct imap_store *ctx = NULL; int ofs = 0; int r; int total, n = 0; @@ -1537,7 +1328,12 @@ int main(int argc, char **argv) } /* read the messages */ - if (!read_message(stdin, &all_msgs)) { + if (read_message(stdin, &all_msgs)) { + fprintf(stderr, "error reading input\n"); + return 1; + } + + if (all_msgs.len == 0) { fprintf(stderr, "nothing to send\n"); return 1; } @@ -1559,6 +1355,7 @@ int main(int argc, char **argv) ctx->name = imap_folder; while (1) { unsigned percent = n * 100 / total; + fprintf(stderr, "%4u%% (%d/%d) done\r", percent, n, total); if (!split_msg(&all_msgs, &msg, &ofs)) break; diff --git a/log-tree.c b/log-tree.c index c894930c18..5dc45c4812 100644 --- a/log-tree.c +++ b/log-tree.c @@ -299,26 +299,34 @@ static unsigned int digits_in_number(unsigned int number) return result; } -void get_patch_filename(struct commit *commit, const char *subject, int nr, - const char *suffix, struct strbuf *buf) +void fmt_output_subject(struct strbuf *filename, + const char *subject, + struct rev_info *info) { - int suffix_len = strlen(suffix) + 1; - int start_len = buf->len; - - strbuf_addf(buf, commit || subject ? "%04d-" : "%d", nr); - if (commit || subject) { - int max_len = start_len + FORMAT_PATCH_NAME_MAX - suffix_len; - struct pretty_print_context ctx = {0}; - - if (subject) - strbuf_addstr(buf, subject); - else if (commit) - format_commit_message(commit, "%f", buf, &ctx); - - if (max_len < buf->len) - strbuf_setlen(buf, max_len); - strbuf_addstr(buf, suffix); - } + const char *suffix = info->patch_suffix; + int nr = info->nr; + int start_len = filename->len; + int max_len = start_len + FORMAT_PATCH_NAME_MAX - (strlen(suffix) + 1); + + if (0 < info->reroll_count) + strbuf_addf(filename, "v%d-", info->reroll_count); + strbuf_addf(filename, "%04d-%s", nr, subject); + + if (max_len < filename->len) + strbuf_setlen(filename, max_len); + strbuf_addstr(filename, suffix); +} + +void fmt_output_commit(struct strbuf *filename, + struct commit *commit, + struct rev_info *info) +{ + struct pretty_print_context ctx = {0}; + struct strbuf subject = STRBUF_INIT; + + format_commit_message(commit, "%f", &subject, &ctx); + fmt_output_subject(filename, subject.buf, info); + strbuf_release(&subject); } void log_write_email_headers(struct rev_info *opt, struct commit *commit, @@ -387,8 +395,10 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit, mime_boundary_leader, opt->mime_boundary); extra_headers = subject_buffer; - get_patch_filename(opt->numbered_files ? NULL : commit, NULL, - opt->nr, opt->patch_suffix, &filename); + if (opt->numbered_files) + strbuf_addf(&filename, "%d", opt->nr); + else + fmt_output_commit(&filename, commit, opt); snprintf(buffer, sizeof(buffer) - 1, "\n--%s%s\n" "Content-Type: text/x-patch;" @@ -540,7 +550,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 +657,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 */ @@ -660,10 +681,22 @@ void show_log(struct rev_info *opt) ctx.preserve_subject = opt->preserve_subject; ctx.reflog_info = opt->reflog_info; ctx.fmt = opt->commit_format; + ctx.mailmap = opt->mailmap; + ctx.color = opt->diffopt.use_color; pretty_print_commit(&ctx, commit, &msgbuf); 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 +722,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 +739,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 +756,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/log-tree.h b/log-tree.h index f5ac238bba..9140f48216 100644 --- a/log-tree.h +++ b/log-tree.h @@ -21,7 +21,7 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit, void load_ref_decorations(int flags); #define FORMAT_PATCH_NAME_MAX 64 -void get_patch_filename(struct commit *commit, const char *subject, int nr, - const char *suffix, struct strbuf *buf); +void fmt_output_commit(struct strbuf *, struct commit *, struct rev_info *); +void fmt_output_subject(struct strbuf *, const char *subject, struct rev_info *); #endif diff --git a/mailmap.c b/mailmap.c index ea4b471ede..2a7b36628c 100644 --- a/mailmap.c +++ b/mailmap.c @@ -10,6 +10,7 @@ static inline void debug_mm(const char *format, ...) {} #endif const char *git_mailmap_file; +const char *git_mailmap_blob; struct mailmap_info { char *name; @@ -129,54 +130,120 @@ static char *parse_name_and_email(char *buffer, char **name, return (*right == '\0' ? NULL : right); } -static int read_single_mailmap(struct string_list *map, const char *filename, char **repo_abbrev) +static void read_mailmap_line(struct string_list *map, char *buffer, + char **repo_abbrev) +{ + char *name1 = NULL, *email1 = NULL, *name2 = NULL, *email2 = NULL; + if (buffer[0] == '#') { + static const char abbrev[] = "# repo-abbrev:"; + int abblen = sizeof(abbrev) - 1; + int len = strlen(buffer); + + if (!repo_abbrev) + return; + + if (len && buffer[len - 1] == '\n') + buffer[--len] = 0; + if (!strncmp(buffer, abbrev, abblen)) { + char *cp; + + if (repo_abbrev) + free(*repo_abbrev); + *repo_abbrev = xmalloc(len); + + for (cp = buffer + abblen; isspace(*cp); cp++) + ; /* nothing */ + strcpy(*repo_abbrev, cp); + } + return; + } + if ((name2 = parse_name_and_email(buffer, &name1, &email1, 0)) != NULL) + parse_name_and_email(name2, &name2, &email2, 1); + + if (email1) + add_mapping(map, name1, email1, name2, email2); +} + +static int read_mailmap_file(struct string_list *map, const char *filename, + char **repo_abbrev) { char buffer[1024]; - FILE *f = (filename == NULL ? NULL : fopen(filename, "r")); + FILE *f; - if (f == NULL) - return 1; - while (fgets(buffer, sizeof(buffer), f) != NULL) { - char *name1 = NULL, *email1 = NULL, *name2 = NULL, *email2 = NULL; - if (buffer[0] == '#') { - static const char abbrev[] = "# repo-abbrev:"; - int abblen = sizeof(abbrev) - 1; - int len = strlen(buffer); - - if (!repo_abbrev) - continue; - - if (len && buffer[len - 1] == '\n') - buffer[--len] = 0; - if (!strncmp(buffer, abbrev, abblen)) { - char *cp; - - if (repo_abbrev) - free(*repo_abbrev); - *repo_abbrev = xmalloc(len); - - for (cp = buffer + abblen; isspace(*cp); cp++) - ; /* nothing */ - strcpy(*repo_abbrev, cp); - } - continue; - } - if ((name2 = parse_name_and_email(buffer, &name1, &email1, 0)) != NULL) - parse_name_and_email(name2, &name2, &email2, 1); + if (!filename) + return 0; - if (email1) - add_mapping(map, name1, email1, name2, email2); + f = fopen(filename, "r"); + if (!f) { + if (errno == ENOENT) + return 0; + return error("unable to open mailmap at %s: %s", + filename, strerror(errno)); } + + while (fgets(buffer, sizeof(buffer), f) != NULL) + read_mailmap_line(map, buffer, repo_abbrev); fclose(f); return 0; } +static void read_mailmap_buf(struct string_list *map, + const char *buf, unsigned long len, + char **repo_abbrev) +{ + while (len) { + const char *end = strchrnul(buf, '\n'); + unsigned long linelen = end - buf + 1; + char *line = xmemdupz(buf, linelen); + + read_mailmap_line(map, line, repo_abbrev); + + free(line); + buf += linelen; + len -= linelen; + } +} + +static int read_mailmap_blob(struct string_list *map, + const char *name, + char **repo_abbrev) +{ + unsigned char sha1[20]; + char *buf; + unsigned long size; + enum object_type type; + + if (!name) + return 0; + if (get_sha1(name, sha1) < 0) + return 0; + + buf = read_sha1_file(sha1, &type, &size); + if (!buf) + return error("unable to read mailmap object at %s", name); + if (type != OBJ_BLOB) + return error("mailmap is not a blob: %s", name); + + read_mailmap_buf(map, buf, size, repo_abbrev); + + free(buf); + return 0; +} + int read_mailmap(struct string_list *map, char **repo_abbrev) { + int err = 0; + map->strdup_strings = 1; - /* each failure returns 1, so >1 means both calls failed */ - return read_single_mailmap(map, ".mailmap", repo_abbrev) + - read_single_mailmap(map, git_mailmap_file, repo_abbrev) > 1; + map->cmp = strcasecmp; + + if (!git_mailmap_blob && is_bare_repository()) + git_mailmap_blob = "HEAD:.mailmap"; + + err |= read_mailmap_file(map, ".mailmap", repo_abbrev); + err |= read_mailmap_blob(map, git_mailmap_blob, repo_abbrev); + err |= read_mailmap_file(map, git_mailmap_file, repo_abbrev); + return err; } void clear_mailmap(struct string_list *map) @@ -187,60 +254,95 @@ void clear_mailmap(struct string_list *map) debug_mm("mailmap: cleared\n"); } +/* + * Look for an entry in map that match string[0:len]; string[len] + * does not have to be NUL (but it could be). + */ +static struct string_list_item *lookup_prefix(struct string_list *map, + const char *string, size_t len) +{ + int i = string_list_find_insert_index(map, string, 1); + if (i < 0) { + /* exact match */ + i = -1 - i; + if (!string[len]) + return &map->items[i]; + /* + * that map entry matches exactly to the string, including + * the cruft at the end beyond "len". That is not a match + * with string[0:len] that we are looking for. + */ + } else if (!string[len]) { + /* + * asked with the whole string, and got nothing. No + * matching entry can exist in the map. + */ + return NULL; + } + + /* + * i is at the exact match to an overlong key, or location the + * overlong key would be inserted, which must come after the + * real location of the key if one exists. + */ + while (0 <= --i && i < map->nr) { + int cmp = strncasecmp(map->items[i].string, string, len); + if (cmp < 0) + /* + * "i" points at a key definitely below the prefix; + * the map does not have string[0:len] in it. + */ + break; + else if (!cmp && !map->items[i].string[len]) + /* found it */ + return &map->items[i]; + /* + * otherwise, the string at "i" may be string[0:len] + * followed by a string that sorts later than string[len:]; + * keep trying. + */ + } + return NULL; +} + int map_user(struct string_list *map, - char *email, int maxlen_email, char *name, int maxlen_name) + const char **email, size_t *emaillen, + const char **name, size_t *namelen) { - char *end_of_email; struct string_list_item *item; struct mailmap_entry *me; - char buf[1024], *mailbuf; - int i; - - /* figure out space requirement for email */ - end_of_email = strchr(email, '>'); - if (!end_of_email) { - /* email passed in might not be wrapped in <>, but end with a \0 */ - end_of_email = memchr(email, '\0', maxlen_email); - if (!end_of_email) - return 0; - } - if (end_of_email - email + 1 < sizeof(buf)) - mailbuf = buf; - else - mailbuf = xmalloc(end_of_email - email + 1); - - /* downcase the email address */ - for (i = 0; i < end_of_email - email; i++) - mailbuf[i] = tolower(email[i]); - mailbuf[i] = 0; - - debug_mm("map_user: map '%s' <%s>\n", name, mailbuf); - item = string_list_lookup(map, mailbuf); + + debug_mm("map_user: map '%.*s' <%.*s>\n", + *name, *namelen, *emaillen, *email); + + item = lookup_prefix(map, *email, *emaillen); if (item != NULL) { me = (struct mailmap_entry *)item->util; if (me->namemap.nr) { /* The item has multiple items, so we'll look up on name too */ /* If the name is not found, we choose the simple entry */ - struct string_list_item *subitem = string_list_lookup(&me->namemap, name); + struct string_list_item *subitem; + subitem = lookup_prefix(&me->namemap, *name, *namelen); if (subitem) item = subitem; } } - if (mailbuf != buf) - free(mailbuf); if (item != NULL) { struct mailmap_info *mi = (struct mailmap_info *)item->util; - if (mi->name == NULL && (mi->email == NULL || maxlen_email == 0)) { + if (mi->name == NULL && mi->email == NULL) { debug_mm("map_user: -- (no simple mapping)\n"); return 0; } - if (maxlen_email && mi->email) - strlcpy(email, mi->email, maxlen_email); - else - *end_of_email = '\0'; - if (maxlen_name && mi->name) - strlcpy(name, mi->name, maxlen_name); - debug_mm("map_user: to '%s' <%s>\n", name, mi->email ? mi->email : ""); + if (mi->email) { + *email = mi->email; + *emaillen = strlen(*email); + } + if (mi->name) { + *name = mi->name; + *namelen = strlen(*name); + } + debug_mm("map_user: to '%.*s' <.*%s>\n", *namelen, *name, + *emaillen, *email); return 1; } debug_mm("map_user: --\n"); diff --git a/mailmap.h b/mailmap.h index d5c3664322..ed7c93b05c 100644 --- a/mailmap.h +++ b/mailmap.h @@ -4,7 +4,7 @@ int read_mailmap(struct string_list *map, char **repo_abbrev); void clear_mailmap(struct string_list *map); -int map_user(struct string_list *mailmap, - char *email, int maxlen_email, char *name, int maxlen_name); +int map_user(struct string_list *map, + const char **email, size_t *emaillen, const char **name, size_t *namelen); #endif diff --git a/merge-blobs.c b/merge-blobs.c new file mode 100644 index 0000000000..57211bccb7 --- /dev/null +++ b/merge-blobs.c @@ -0,0 +1,124 @@ +#include "cache.h" +#include "run-command.h" +#include "xdiff-interface.h" +#include "ll-merge.h" +#include "blob.h" +#include "merge-blobs.h" + +static int fill_mmfile_blob(mmfile_t *f, struct blob *obj) +{ + void *buf; + unsigned long size; + enum object_type type; + + buf = read_sha1_file(obj->object.sha1, &type, &size); + if (!buf) + return -1; + if (type != OBJ_BLOB) + return -1; + f->ptr = buf; + f->size = size; + return 0; +} + +static void free_mmfile(mmfile_t *f) +{ + free(f->ptr); +} + +static void *three_way_filemerge(const char *path, mmfile_t *base, mmfile_t *our, mmfile_t *their, unsigned long *size) +{ + int merge_status; + mmbuffer_t res; + + /* + * This function is only used by cmd_merge_tree, which + * does not respect the merge.conflictstyle option. + * There is no need to worry about a label for the + * common ancestor. + */ + merge_status = ll_merge(&res, path, base, NULL, + our, ".our", their, ".their", NULL); + if (merge_status < 0) + return NULL; + + *size = res.size; + return res.ptr; +} + +static int common_outf(void *priv_, mmbuffer_t *mb, int nbuf) +{ + int i; + mmfile_t *dst = priv_; + + for (i = 0; i < nbuf; i++) { + memcpy(dst->ptr + dst->size, mb[i].ptr, mb[i].size); + dst->size += mb[i].size; + } + return 0; +} + +static int generate_common_file(mmfile_t *res, mmfile_t *f1, mmfile_t *f2) +{ + unsigned long size = f1->size < f2->size ? f1->size : f2->size; + void *ptr = xmalloc(size); + xpparam_t xpp; + xdemitconf_t xecfg; + xdemitcb_t ecb; + + memset(&xpp, 0, sizeof(xpp)); + xpp.flags = 0; + memset(&xecfg, 0, sizeof(xecfg)); + xecfg.ctxlen = 3; + xecfg.flags = XDL_EMIT_COMMON; + ecb.outf = common_outf; + + res->ptr = ptr; + res->size = 0; + + ecb.priv = res; + return xdi_diff(f1, f2, &xpp, &xecfg, &ecb); +} + +void *merge_blobs(const char *path, struct blob *base, struct blob *our, struct blob *their, unsigned long *size) +{ + void *res = NULL; + mmfile_t f1, f2, common; + + /* + * Removed in either branch? + * + * NOTE! This depends on the caller having done the + * proper warning about removing a file that got + * modified in the other branch! + */ + if (!our || !their) { + enum object_type type; + if (base) + return NULL; + if (!our) + our = their; + return read_sha1_file(our->object.sha1, &type, size); + } + + if (fill_mmfile_blob(&f1, our) < 0) + goto out_no_mmfile; + if (fill_mmfile_blob(&f2, their) < 0) + goto out_free_f1; + + if (base) { + if (fill_mmfile_blob(&common, base) < 0) + goto out_free_f2_f1; + } else { + if (generate_common_file(&common, &f1, &f2) < 0) + goto out_free_f2_f1; + } + res = three_way_filemerge(path, &common, &f1, &f2, size); + free_mmfile(&common); +out_free_f2_f1: + free_mmfile(&f2); +out_free_f1: + free_mmfile(&f1); +out_no_mmfile: + return res; +} diff --git a/merge-blobs.h b/merge-blobs.h new file mode 100644 index 0000000000..62b569e472 --- /dev/null +++ b/merge-blobs.h @@ -0,0 +1,8 @@ +#ifndef MERGE_BLOBS_H +#define MERGE_BLOBS_H + +#include "blob.h" + +extern void *merge_blobs(const char *, struct blob *, struct blob *, struct blob *, unsigned long *); + +#endif /* MERGE_BLOBS_H */ diff --git a/merge-file.c b/merge-file.c deleted file mode 100644 index 7845528e88..0000000000 --- a/merge-file.c +++ /dev/null @@ -1,124 +0,0 @@ -#include "cache.h" -#include "run-command.h" -#include "xdiff-interface.h" -#include "ll-merge.h" -#include "blob.h" -#include "merge-file.h" - -static int fill_mmfile_blob(mmfile_t *f, struct blob *obj) -{ - void *buf; - unsigned long size; - enum object_type type; - - buf = read_sha1_file(obj->object.sha1, &type, &size); - if (!buf) - return -1; - if (type != OBJ_BLOB) - return -1; - f->ptr = buf; - f->size = size; - return 0; -} - -static void free_mmfile(mmfile_t *f) -{ - free(f->ptr); -} - -static void *three_way_filemerge(const char *path, mmfile_t *base, mmfile_t *our, mmfile_t *their, unsigned long *size) -{ - int merge_status; - mmbuffer_t res; - - /* - * This function is only used by cmd_merge_tree, which - * does not respect the merge.conflictstyle option. - * There is no need to worry about a label for the - * common ancestor. - */ - merge_status = ll_merge(&res, path, base, NULL, - our, ".our", their, ".their", NULL); - if (merge_status < 0) - return NULL; - - *size = res.size; - return res.ptr; -} - -static int common_outf(void *priv_, mmbuffer_t *mb, int nbuf) -{ - int i; - mmfile_t *dst = priv_; - - for (i = 0; i < nbuf; i++) { - memcpy(dst->ptr + dst->size, mb[i].ptr, mb[i].size); - dst->size += mb[i].size; - } - return 0; -} - -static int generate_common_file(mmfile_t *res, mmfile_t *f1, mmfile_t *f2) -{ - unsigned long size = f1->size < f2->size ? f1->size : f2->size; - void *ptr = xmalloc(size); - xpparam_t xpp; - xdemitconf_t xecfg; - xdemitcb_t ecb; - - memset(&xpp, 0, sizeof(xpp)); - xpp.flags = 0; - memset(&xecfg, 0, sizeof(xecfg)); - xecfg.ctxlen = 3; - xecfg.flags = XDL_EMIT_COMMON; - ecb.outf = common_outf; - - res->ptr = ptr; - res->size = 0; - - ecb.priv = res; - return xdi_diff(f1, f2, &xpp, &xecfg, &ecb); -} - -void *merge_file(const char *path, struct blob *base, struct blob *our, struct blob *their, unsigned long *size) -{ - void *res = NULL; - mmfile_t f1, f2, common; - - /* - * Removed in either branch? - * - * NOTE! This depends on the caller having done the - * proper warning about removing a file that got - * modified in the other branch! - */ - if (!our || !their) { - enum object_type type; - if (base) - return NULL; - if (!our) - our = their; - return read_sha1_file(our->object.sha1, &type, size); - } - - if (fill_mmfile_blob(&f1, our) < 0) - goto out_no_mmfile; - if (fill_mmfile_blob(&f2, their) < 0) - goto out_free_f1; - - if (base) { - if (fill_mmfile_blob(&common, base) < 0) - goto out_free_f2_f1; - } else { - if (generate_common_file(&common, &f1, &f2) < 0) - goto out_free_f2_f1; - } - res = three_way_filemerge(path, &common, &f1, &f2, size); - free_mmfile(&common); -out_free_f2_f1: - free_mmfile(&f2); -out_free_f1: - free_mmfile(&f1); -out_no_mmfile: - return res; -} diff --git a/merge-file.h b/merge-file.h deleted file mode 100644 index 9b3b83ac6c..0000000000 --- a/merge-file.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef MERGE_FILE_H -#define MERGE_FILE_H - -extern void *merge_file(const char *path, struct blob *base, struct blob *our, - struct blob *their, unsigned long *size); - -#endif diff --git a/merge-recursive.c b/merge-recursive.c index d8820604ca..33ba5dc07c 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -976,7 +976,7 @@ merge_file_special_markers(struct merge_options *o, return mfi; } -static struct merge_file_info merge_file(struct merge_options *o, +static struct merge_file_info merge_file_one(struct merge_options *o, const char *path, const unsigned char *o_sha, int o_mode, const unsigned char *a_sha, int a_mode, @@ -1166,7 +1166,7 @@ static void conflict_rename_rename_1to2(struct merge_options *o, struct merge_file_info mfi; struct diff_filespec other; struct diff_filespec *add; - mfi = merge_file(o, one->path, + mfi = merge_file_one(o, one->path, one->sha1, one->mode, a->sha1, a->mode, b->sha1, b->mode, @@ -1450,7 +1450,7 @@ static int process_renames(struct merge_options *o, ren1_dst, branch2); if (o->call_depth) { struct merge_file_info mfi; - mfi = merge_file(o, ren1_dst, null_sha1, 0, + mfi = merge_file_one(o, ren1_dst, null_sha1, 0, ren1->pair->two->sha1, ren1->pair->two->mode, dst_other.sha1, dst_other.mode, branch1, branch2); 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 295361a8aa..52f7c8f705 100644 --- a/mergetools/p4merge +++ b/mergetools/p4merge @@ -1,29 +1,21 @@ diff_cmd () { + empty_file= + # 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 + LOCAL="$(create_empty_file)" fi if test "/dev/null" = "$REMOTE" then - REMOTE="./p4merge-dev-null.REMOTE.$$" - >"$REMOTE" - rm_remote=true + REMOTE="$(create_empty_file)" fi "$merge_tool_path" "$LOCAL" "$REMOTE" - if test -n "$rm_local" - then - rm -f "$LOCAL" - fi - if test -n "$rm_remote" + if test -n "$empty_file" then - rm -f "$REMOTE" + rm -f "$empty_file" fi } @@ -33,3 +25,10 @@ merge_cmd () { "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" "$MERGED" check_unchanged } + +create_empty_file () { + empty_file="${TMPDIR:-/tmp}/git-difftool-p4merge-empty-file.$$" + >"$empty_file" + + printf "$empty_file" +} 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/parse-options.c b/parse-options.c index c1c66bd408..67e98a6323 100644 --- a/parse-options.c +++ b/parse-options.c @@ -18,15 +18,6 @@ int optbug(const struct option *opt, const char *reason) return error("BUG: switch '%c' %s", opt->short_name, reason); } -int opterror(const struct option *opt, const char *reason, int flags) -{ - if (flags & OPT_SHORT) - return error("switch `%c' %s", opt->short_name, reason); - if (flags & OPT_UNSET) - return error("option `no-%s' %s", opt->long_name, reason); - return error("option `%s' %s", opt->long_name, reason); -} - static int get_arg(struct parse_opt_ctx_t *p, const struct option *opt, int flags, const char **arg) { @@ -594,3 +585,12 @@ static int parse_options_usage(struct parse_opt_ctx_t *ctx, return usage_with_options_internal(ctx, usagestr, opts, 0, err); } +#undef opterror +int opterror(const struct option *opt, const char *reason, int flags) +{ + if (flags & OPT_SHORT) + return error("switch `%c' %s", opt->short_name, reason); + if (flags & OPT_UNSET) + return error("option `no-%s' %s", opt->long_name, reason); + return error("option `%s' %s", opt->long_name, reason); +} diff --git a/parse-options.h b/parse-options.h index 71a39c60d9..1c8bd8d5a0 100644 --- a/parse-options.h +++ b/parse-options.h @@ -177,6 +177,10 @@ extern NORETURN void usage_msg_opt(const char *msg, extern int optbug(const struct option *opt, const char *reason); extern int opterror(const struct option *opt, const char *reason, int flags); +#if defined(__GNUC__) && ! defined(clang) +#define opterror(o,r,f) (opterror((o),(r),(f)), -1) +#endif + /*----- incremental advanced APIs -----*/ enum { diff --git a/path.c b/path.c index cbbdf7d6ba..d3d3f8b8ad 100644 --- a/path.c +++ b/path.c @@ -12,6 +12,7 @@ */ #include "cache.h" #include "strbuf.h" +#include "string-list.h" static char bad_path[] = "/bad-path/"; @@ -569,43 +570,38 @@ int normalize_path_copy(char *dst, const char *src) /* * path = Canonical absolute path - * prefix_list = Colon-separated list of absolute paths + * prefixes = string_list containing normalized, absolute paths without + * trailing slashes (except for the root directory, which is denoted by "/"). * - * Determines, for each path in prefix_list, whether the "prefix" really + * Determines, for each path in prefixes, whether the "prefix" * is an ancestor directory of path. Returns the length of the longest * ancestor directory, excluding any trailing slashes, or -1 if no prefix - * is an ancestor. (Note that this means 0 is returned if prefix_list is - * "/".) "/foo" is not considered an ancestor of "/foobar". Directories + * is an ancestor. (Note that this means 0 is returned if prefixes is + * ["/"].) "/foo" is not considered an ancestor of "/foobar". Directories * are not considered to be their own ancestors. path must be in a * canonical form: empty components, or "." or ".." components are not - * allowed. prefix_list may be null, which is like "". + * allowed. */ -int longest_ancestor_length(const char *path, const char *prefix_list) +int longest_ancestor_length(const char *path, struct string_list *prefixes) { - char buf[PATH_MAX+1]; - const char *ceil, *colon; - int len, max_len = -1; + int i, max_len = -1; - if (prefix_list == NULL || !strcmp(path, "/")) + if (!strcmp(path, "/")) return -1; - for (colon = ceil = prefix_list; *colon; ceil = colon+1) { - for (colon = ceil; *colon && *colon != PATH_SEP; colon++); - len = colon - ceil; - if (len == 0 || len > PATH_MAX || !is_absolute_path(ceil)) - continue; - strlcpy(buf, ceil, len+1); - if (normalize_path_copy(buf, buf) < 0) - continue; - len = strlen(buf); - if (len > 0 && buf[len-1] == '/') - buf[--len] = '\0'; + for (i = 0; i < prefixes->nr; i++) { + const char *ceil = prefixes->items[i].string; + int len = strlen(ceil); - if (!strncmp(path, buf, len) && - path[len] == '/' && - len > max_len) { + if (len == 1 && ceil[0] == '/') + len = 0; /* root matches anything, with length 0 */ + else if (!strncmp(path, ceil, len) && path[len] == '/') + ; /* match of length len */ + else + continue; /* no match */ + + if (len > max_len) max_len = len; - } } return max_len; diff --git a/pathspec.c b/pathspec.c new file mode 100644 index 0000000000..284f3970a3 --- /dev/null +++ b/pathspec.c @@ -0,0 +1,101 @@ +#include "cache.h" +#include "dir.h" +#include "pathspec.h" + +/* + * Finds which of the given pathspecs match items in the index. + * + * For each pathspec, sets the corresponding entry in the seen[] array + * (which should be specs items long, i.e. the same size as pathspec) + * to the nature of the "closest" (i.e. most specific) match found for + * that pathspec in the index, if it was a closer type of match than + * the existing entry. As an optimization, matching is skipped + * altogether if seen[] already only contains non-zero entries. + * + * If seen[] has not already been written to, it may make sense + * to use find_pathspecs_matching_against_index() instead. + */ +void add_pathspec_matches_against_index(const char **pathspec, + char *seen, int specs) +{ + int num_unmatched = 0, i; + + /* + * Since we are walking the index as if we were walking the directory, + * we have to mark the matched pathspec as seen; otherwise we will + * mistakenly think that the user gave a pathspec that did not match + * anything. + */ + for (i = 0; i < specs; i++) + if (!seen[i]) + num_unmatched++; + if (!num_unmatched) + return; + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen); + } +} + +/* + * Finds which of the given pathspecs match items in the index. + * + * This is a one-shot wrapper around add_pathspec_matches_against_index() + * which allocates, populates, and returns a seen[] array indicating the + * nature of the "closest" (i.e. most specific) matches which each of the + * given pathspecs achieves against all items in the index. + */ +char *find_pathspecs_matching_against_index(const char **pathspec) +{ + char *seen; + int i; + + for (i = 0; pathspec[i]; i++) + ; /* just counting */ + seen = xcalloc(i, 1); + add_pathspec_matches_against_index(pathspec, seen, i); + return seen; +} + +/* + * Check the index to see whether path refers to a submodule, or + * something inside a submodule. If the former, returns the path with + * any trailing slash stripped. If the latter, dies with an error + * message. + */ +const char *check_path_for_gitlink(const char *path) +{ + int i, path_len = strlen(path); + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + if (S_ISGITLINK(ce->ce_mode)) { + int ce_len = ce_namelen(ce); + if (path_len <= ce_len || path[ce_len] != '/' || + memcmp(ce->name, path, ce_len)) + /* path does not refer to this + * submodule or anything inside it */ + continue; + if (path_len == ce_len + 1) { + /* path refers to submodule; + * strip trailing slash */ + return xstrndup(ce->name, ce_len); + } else { + die (_("Path '%s' is in submodule '%.*s'"), + path, ce_len, ce->name); + } + } + } + return path; +} + +/* + * Dies if the given path refers to a file inside a symlinked + * directory in the index. + */ +void die_if_path_beyond_symlink(const char *path, const char *prefix) +{ + if (has_symlink_leading_path(path, strlen(path))) { + int len = prefix ? strlen(prefix) : 0; + die(_("'%s' is beyond a symbolic link"), path + len); + } +} diff --git a/pathspec.h b/pathspec.h new file mode 100644 index 0000000000..db0184a1ac --- /dev/null +++ b/pathspec.h @@ -0,0 +1,9 @@ +#ifndef PATHSPEC_H +#define PATHSPEC_H + +extern char *find_pathspecs_matching_against_index(const char **pathspec); +extern void add_pathspec_matches_against_index(const char **pathspec, char *seen, int specs); +extern const char *check_path_for_gitlink(const char *path); +extern void die_if_path_beyond_symlink(const char *path, const char *prefix); + +#endif /* PATHSPEC_H */ diff --git a/perl/Git.pm b/perl/Git.pm index 497f420178..931047c51d 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -58,7 +58,7 @@ =head1 SYNOPSIS command_output_pipe command_input_pipe command_close_pipe command_bidi_pipe command_close_bidi_pipe version exec_path html_path hash_object git_cmd_try - remote_refs + remote_refs prompt temp_acquire temp_release temp_reset temp_path); @@ -511,6 +511,58 @@ sub version { sub html_path { command_oneline('--html-path') } +=item prompt ( PROMPT , ISPASSWORD ) + +Query user C and return answer from user. + +Honours GIT_ASKPASS and SSH_ASKPASS environment variables for querying +the user. If no *_ASKPASS variable is set or an error occoured, +the terminal is tried as a fallback. +If C is set and true, the terminal disables echo. + +=cut + +sub prompt { + my ($prompt, $isPassword) = @_; + my $ret; + if (exists $ENV{'GIT_ASKPASS'}) { + $ret = _prompt($ENV{'GIT_ASKPASS'}, $prompt); + } + if (!defined $ret && exists $ENV{'SSH_ASKPASS'}) { + $ret = _prompt($ENV{'SSH_ASKPASS'}, $prompt); + } + if (!defined $ret) { + print STDERR $prompt; + STDERR->flush; + if (defined $isPassword && $isPassword) { + require Term::ReadKey; + Term::ReadKey::ReadMode('noecho'); + $ret = ''; + while (defined(my $key = Term::ReadKey::ReadKey(0))) { + last if $key =~ /[\012\015]/; # \n\r + $ret .= $key; + } + Term::ReadKey::ReadMode('restore'); + print STDERR "\n"; + STDERR->flush; + } else { + chomp($ret = ); + } + } + return $ret; +} + +sub _prompt { + my ($askpass, $prompt) = @_; + return unless length $askpass; + $prompt =~ s/\n/ /g; + my $ret; + open my $fh, "-|", $askpass, $prompt or return; + $ret = <$fh>; + $ret =~ s/[\015\012]//g; # strip \r\n, chomp does not work on all systems (i.e. windows) as expected + close ($fh); + return $ret; +} =item repo_path () diff --git a/perl/Git/SVN/Editor.pm b/perl/Git/SVN/Editor.pm index 3bbc20a054..3db152155c 100644 --- a/perl/Git/SVN/Editor.pm +++ b/perl/Git/SVN/Editor.pm @@ -145,7 +145,8 @@ sub repo_path { sub url_path { my ($self, $path) = @_; if ($self->{url} =~ m#^https?://#) { - $path =~ s!([^~a-zA-Z0-9_./-])!uc sprintf("%%%02x",ord($1))!eg; + # characters are taken from subversion/libsvn_subr/path.c + $path =~ s#([^~a-zA-Z0-9_./!$&'()*+,-])#uc sprintf("%%%02x",ord($1))#eg; } $self->{url} . '/' . $self->repo_path($path); } @@ -358,12 +359,12 @@ sub T { mode_a => $m->{mode_a}, mode_b => '000000', sha1_a => $m->{sha1_a}, sha1_b => '0' x 40, chg => 'D', file_b => $m->{file_b} - }); + }, $deletions); $self->A({ mode_a => '000000', mode_b => $m->{mode_b}, sha1_a => '0' x 40, sha1_b => $m->{sha1_b}, chg => 'A', file_b => $m->{file_b} - }); + }, $deletions); return; } diff --git a/perl/Git/SVN/Prompt.pm b/perl/Git/SVN/Prompt.pm index 3a6f8af0d9..74daa7a597 100644 --- a/perl/Git/SVN/Prompt.pm +++ b/perl/Git/SVN/Prompt.pm @@ -62,16 +62,16 @@ sub ssl_server_trust { issuer_dname fingerprint); my $choice; prompt: - print STDERR $may_save ? + my $options = $may_save ? "(R)eject, accept (t)emporarily or accept (p)ermanently? " : "(R)eject or accept (t)emporarily? "; STDERR->flush; - $choice = lc(substr( || 'R', 0, 1)); - if ($choice =~ /^t$/i) { + $choice = lc(substr(Git::prompt("Certificate problem.\n" . $options) || 'R', 0, 1)); + if ($choice eq 't') { $cred->may_save(undef); - } elsif ($choice =~ /^r$/i) { + } elsif ($choice eq 'r') { return -1; - } elsif ($may_save && $choice =~ /^p$/i) { + } elsif ($may_save && $choice eq 'p') { $cred->may_save($may_save); } else { goto prompt; @@ -109,9 +109,7 @@ sub username { if (defined $_username) { $username = $_username; } else { - print STDERR "Username: "; - STDERR->flush; - chomp($username = ); + $username = Git::prompt("Username: "); } $cred->username($username); $cred->may_save($may_save); @@ -120,25 +118,7 @@ sub username { sub _read_password { my ($prompt, $realm) = @_; - my $password = ''; - if (exists $ENV{GIT_ASKPASS}) { - open(PH, "-|", $ENV{GIT_ASKPASS}, $prompt); - $password = ; - $password =~ s/[\012\015]//; # \n\r - close(PH); - } else { - print STDERR $prompt; - STDERR->flush; - require Term::ReadKey; - Term::ReadKey::ReadMode('noecho'); - while (defined(my $key = Term::ReadKey::ReadKey(0))) { - last if $key =~ /[\012\015]/; # \n\r - $password .= $key; - } - Term::ReadKey::ReadMode('restore'); - print STDERR "\n"; - STDERR->flush; - } + my $password = Git::prompt($prompt, 1); $password; } diff --git a/perl/Git/SVN/Utils.pm b/perl/Git/SVN/Utils.pm index 8b8cf3755c..3d1a0933a2 100644 --- a/perl/Git/SVN/Utils.pm +++ b/perl/Git/SVN/Utils.pm @@ -155,7 +155,7 @@ sub _canonicalize_url_path { my @parts; foreach my $part (split m{/+}, $uri_path) { - $part =~ s/([^~\w.%+-]|%(?![a-fA-F0-9]{2}))/sprintf("%%%02X",ord($1))/eg; + $part =~ s/([^!\$%&'()*+,.\/\w:=\@_`~-]|%(?![a-fA-F0-9]{2}))/sprintf("%%%02X",ord($1))/eg; push @parts, $part; } diff --git a/po/de.po b/po/de.po index 0db9a9b2ab..c8ad2f01c9 100644 --- a/po/de.po +++ b/po/de.po @@ -1,21 +1,21 @@ # German translations for Git. -# Copyright (C) 2010-2012 Ralf Thielow +# Copyright (C) 2010-2012 Ralf Thielow # This file is distributed under the same license as the Git package. -# Ralf Thielow , 2010, 2011, 2012. +# Ralf Thielow , 2010, 2011, 2012. # msgid "" msgstr "" -"Project-Id-Version: git 1.7.12\n" +"Project-Id-Version: git 1.8.1\n" "Report-Msgid-Bugs-To: Git Mailing List \n" -"POT-Creation-Date: 2012-09-05 06:20+0800\n" -"PO-Revision-Date: 2012-03-28 18:46+0200\n" -"Last-Translator: Ralf Thielow \n" -"Language-Team: German\n" +"POT-Creation-Date: 2012-11-30 12:40+0800\n" +"PO-Revision-Date: 2012-10-02 19:35+0200\n" +"Last-Translator: Ralf Thielow \n" +"Language-Team: German <>\n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=2; plural=(n!=1);\n" #: advice.c:40 #, c-format @@ -33,10 +33,105 @@ msgid "" "appropriate to mark resolution and make a commit,\n" "or use 'git commit -a'." msgstr "" -"Korrigiere dies im Arbeitsbaum,\n" -"und benutze dann 'git add/rm '\n" +"Korrigieren Sie dies im Arbeitsbaum,\n" +"und benutzen Sie dann 'git add/rm '\n" "um die Auflösung entsprechend zu markieren und einzutragen,\n" -"oder benutze 'git commit -a'." +"oder benutzen Sie 'git commit -a'." + +#: archive.c:10 +msgid "git archive [options] [...]" +msgstr "git archive [Optionen] [...]" + +#: archive.c:11 +msgid "git archive --list" +msgstr "git archive --list" + +#: archive.c:12 +msgid "" +"git archive --remote [--exec ] [options] [...]" +msgstr "" +"git archive --remote [--exec ] [Optionen] " +" [Pfad...]" + +#: archive.c:13 +msgid "git archive --remote [--exec ] --list" +msgstr "git archive --remote [--exec ] --list" + +#: archive.c:322 +msgid "fmt" +msgstr "Format" + +#: archive.c:322 +msgid "archive format" +msgstr "Ausgabeformat" + +#: archive.c:323 builtin/log.c:1084 +msgid "prefix" +msgstr "Prefix" + +#: archive.c:324 +msgid "prepend prefix to each pathname in the archive" +msgstr "stellt einen Präfix vor jeden Pfadnamen in der Ausgabe" + +#: 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" +msgstr "Datei" + +#: archive.c:326 builtin/archive.c:92 +msgid "write the archive to this file" +msgstr "schreibt die Ausgabe in diese Datei" + +#: archive.c:328 +msgid "read .gitattributes in working directory" +msgstr "liest .gitattributes aus dem Arbeitsverzeichnis" + +#: archive.c:329 +msgid "report archived files on stderr" +msgstr "gibt archivierte Dateien in der Standard-Fehlerausgabe aus" + +#: archive.c:330 +msgid "store only" +msgstr "nur speichern" + +#: archive.c:331 +msgid "compress faster" +msgstr "schneller komprimieren" + +#: archive.c:339 +msgid "compress better" +msgstr "besser komprimieren" + +#: archive.c:342 +msgid "list supported archive formats" +msgstr "listet unterstützte Ausgabeformate auf" + +#: archive.c:344 builtin/archive.c:93 builtin/clone.c:85 +msgid "repo" +msgstr "Projektarchiv" + +#: archive.c:345 builtin/archive.c:94 +msgid "retrieve the archive from remote repository " +msgstr "ruft das Archiv von externem Projektarchiv ab" + +#: archive.c:346 builtin/archive.c:95 builtin/notes.c:619 +msgid "command" +msgstr "Programm" + +#: archive.c:347 builtin/archive.c:96 +msgid "path to the remote git-upload-archive command" +msgstr "Pfad zum externen \"git-upload-archive\"-Programm" + +#: attr.c:259 +msgid "" +"Negative patterns are forbidden in git attributes\n" +"Use '\\!' for literal leading exclamation." +msgstr "" +"Verneinende Muster sind in Git-Attributen verboten.\n" +"Benutzen Sie '\\!' für führende Ausrufezeichen." #: bundle.c:36 #, c-format @@ -48,7 +143,7 @@ msgstr "'%s' sieht nicht wie eine v2 Paketdatei aus" msgid "unrecognized header: %s%s (%d)" msgstr "nicht erkannter Kopfbereich: %s%s (%d)" -#: bundle.c:89 builtin/commit.c:699 +#: bundle.c:89 builtin/commit.c:674 #, c-format msgid "could not open '%s'" msgstr "Konnte '%s' nicht öffnen" @@ -57,8 +152,8 @@ msgstr "Konnte '%s' nicht öffnen" msgid "Repository lacks these prerequisite commits:" msgstr "Dem Projektarchiv fehlen folgende vorausgesetzte Versionen:" -#: bundle.c:164 sequencer.c:550 sequencer.c:982 builtin/log.c:290 -#: builtin/log.c:727 builtin/log.c:1313 builtin/log.c:1529 builtin/merge.c:347 +#: bundle.c:164 sequencer.c:562 sequencer.c:994 builtin/log.c:290 +#: 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 "Einrichtung des Revisionsgangs fehlgeschlagen" @@ -85,7 +180,7 @@ msgstr[1] "Das Paket benötigt diese %d Referenzen" msgid "rev-list died" msgstr "\"rev-list\" abgebrochen" -#: 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 "nicht erkanntes Argument: %s" @@ -116,12 +211,12 @@ msgstr "kann '%s' nicht erstellen" msgid "index-pack died" msgstr "Erstellung der Paketindexdatei abgebrochen" -#: commit.c:48 +#: commit.c:50 #, c-format msgid "could not parse %s" msgstr "konnte %s nicht parsen" -#: commit.c:50 +#: commit.c:52 #, c-format msgid "%s %s is not a commit!" msgstr "%s %s ist keine Version!" @@ -211,18 +306,24 @@ msgid_plural "%lu years ago" msgstr[0] "vor %lu Jahr" msgstr[1] "vor %lu Jahren" -#: 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 "" -" Fehler beim Parsen des abgeschnittenen \"dirstat\" Prozentsatzes '%.*s'\n" +" Fehler beim Parsen des abgeschnittenen \"dirstat\" Prozentsatzes '%s'\n" + +#: diff.c:116 +#, c-format +msgid " Unknown dirstat parameter '%s'\n" +msgstr " Unbekannter \"dirstat\" Parameter '%s'\n" -#: diff.c:110 +#: diff.c:194 #, c-format -msgid " Unknown dirstat parameter '%.*s'\n" -msgstr " Unbekannter \"dirstat\" Parameter '%.*s'\n" +msgid "Unknown value for 'diff.submodule' config variable: '%s'" +msgstr "" +"Unbekannter Wert in Konfigurationsvariable 'diff.dirstat': '%s'" -#: diff.c:210 +#: diff.c:237 #, c-format msgid "" "Found errors in 'diff.dirstat' config variable:\n" @@ -231,32 +332,7 @@ msgstr "" "Fehler in 'diff.dirstat' Konfigurationsvariable gefunden:\n" "%s" -#: diff.c:1401 -msgid " 0 files changed" -msgstr " 0 Dateien geändert" - -#: diff.c:1405 -#, c-format -msgid " %d file changed" -msgid_plural " %d files changed" -msgstr[0] " %d Datei geändert" -msgstr[1] " %d Dateien geändert" - -#: diff.c:1422 -#, c-format -msgid ", %d insertion(+)" -msgid_plural ", %d insertions(+)" -msgstr[0] ", %d Zeile hinzugefügt(+)" -msgstr[1] ", %d Zeilen hinzugefügt(+)" - -#: diff.c:1433 -#, c-format -msgid ", %d deletion(-)" -msgid_plural ", %d deletions(-)" -msgstr[0] ", %d Zeile entfernt(-)" -msgstr[1] ", %d Zeilen entfernt(-)" - -#: diff.c:3460 +#: diff.c:3494 #, c-format msgid "" "Failed to parse --dirstat/-X option parameter:\n" @@ -265,6 +341,12 @@ msgstr "" "Fehler beim Parsen des --dirstat/-X Optionsparameters:\n" "%s" +#: diff.c:3508 +#, c-format +msgid "Failed to parse --submodule option parameter: '%s'" +msgstr "" +"Fehler beim Parsen des --submodule Optionsparameters: '%s'" + #: gpg-interface.c:59 msgid "could not run gpg." msgstr "konnte gpg nicht ausführen" @@ -277,17 +359,17 @@ msgstr "gpg hat die Daten nicht akzeptiert" msgid "gpg failed to sign the data" msgstr "gpg beim Signieren der Daten fehlgeschlagen" -#: grep.c:1320 +#: grep.c:1622 #, c-format msgid "'%s': unable to read %s" msgstr "'%s': konnte nicht lesen %s" -#: grep.c:1337 +#: grep.c:1639 #, c-format msgid "'%s': %s" msgstr "'%s': %s" -#: grep.c:1348 +#: grep.c:1650 #, c-format msgid "'%s': short read %s" msgstr "'%s': read() zu kurz %s" @@ -299,7 +381,7 @@ msgstr "Vorhandene Git-Kommandos in '%s'" #: help.c:219 msgid "git commands available from elsewhere on your $PATH" -msgstr "Vorhandene Git-Kommandos irgendwo in deinem $PATH" +msgstr "Vorhandene Git-Kommandos irgendwo in Ihrem $PATH" #: help.c:275 #, c-format @@ -312,7 +394,7 @@ msgstr "" #: help.c:332 msgid "Uh oh. Your system reports no Git commands at all." -msgstr "Uh oh. Keine Git-Kommandos auf deinem System vorhanden." +msgstr "Uh oh. Keine Git-Kommandos auf Ihrem System vorhanden." #: help.c:354 #, c-format @@ -320,8 +402,8 @@ msgid "" "WARNING: You called a Git command named '%s', which does not exist.\n" "Continuing under the assumption that you meant '%s'" msgstr "" -"Warnung: Du hast das nicht existierende Git-Kommando '%s' ausgeführt.\n" -"Setze fort unter der Annahme das du '%s' gemeint hast" +"Warnung: Sie haben das nicht existierende Git-Kommando '%s' ausgeführt.\n" +"Setze fort unter der Annahme, dass Sie '%s' gemeint haben" #: help.c:359 #, c-format @@ -342,10 +424,19 @@ msgid_plural "" "Did you mean one of these?" msgstr[0] "" "\n" -"Hast du das gemeint?" +"Haben Sie das gemeint?" msgstr[1] "" "\n" -"Hast du eines von diesen gemeint?" +"Haben Sie eines von diesen gemeint?" + +#: merge.c:56 +msgid "failed to read the cache" +msgstr "Lesen des Zwischenspeichers fehlgeschlagen" + +#: merge.c:110 builtin/checkout.c:333 builtin/checkout.c:534 +#: builtin/clone.c:586 +msgid "unable to write new index file" +msgstr "Konnte neue Bereitstellungsdatei nicht schreiben." #: merge-recursive.c:190 #, c-format @@ -407,20 +498,20 @@ msgstr "Fehler beim Erstellen einer symbolischen Verknüpfung für '%s'" msgid "do not know what to do with %06o %s '%s'" msgstr "weiß nicht was mit %06o %s '%s' zu machen ist" -#: merge-recursive.c:921 +#: merge-recursive.c:922 msgid "Failed to execute internal merge" msgstr "Fehler bei Ausführung der internen Zusammenführung" -#: merge-recursive.c:925 +#: merge-recursive.c:926 #, c-format msgid "Unable to add %s to database" msgstr "Konnte %s nicht zur Datenbank hinzufügen" -#: merge-recursive.c:941 +#: merge-recursive.c:942 msgid "unsupported object type in the tree" msgstr "nicht unterstützter Objekttyp im Baum" -#: merge-recursive.c:1020 merge-recursive.c:1034 +#: merge-recursive.c:1021 merge-recursive.c:1035 #, c-format msgid "" "CONFLICT (%s/delete): %s deleted in %s and %s in %s. Version %s of %s left " @@ -429,7 +520,7 @@ msgstr "" "KONFLIKT (%s/löschen): %s gelöscht in %s und %s in %s. Stand %s von %s wurde " "im Arbeitsbereich gelassen." -#: merge-recursive.c:1026 merge-recursive.c:1039 +#: merge-recursive.c:1027 merge-recursive.c:1040 #, c-format msgid "" "CONFLICT (%s/delete): %s deleted in %s and %s in %s. Version %s of %s left " @@ -438,20 +529,20 @@ msgstr "" "KONFLIKT (%s/löschen): %s gelöscht in %s und %s in %s. Stand %s von %s wurde " "im Arbeitsbereich bei %s gelassen." -#: merge-recursive.c:1080 +#: merge-recursive.c:1081 msgid "rename" msgstr "umbenennen" -#: merge-recursive.c:1080 +#: merge-recursive.c:1081 msgid "renamed" msgstr "umbenannt" -#: merge-recursive.c:1136 +#: merge-recursive.c:1137 #, c-format msgid "%s is a directory in %s adding as %s instead" msgstr "%s ist ein Verzeichnis in %s, füge es stattdessen als %s hinzu" -#: merge-recursive.c:1158 +#: merge-recursive.c:1159 #, c-format msgid "" "CONFLICT (rename/rename): Rename \"%s\"->\"%s\" in branch \"%s\" rename \"%s" @@ -460,148 +551,148 @@ msgstr "" "KONFLIKT (umbenennen/umbenennen): Benenne um \"%s\"->\"%s\" in Zweig \"%s\" " "und \"%s\"->\"%s\" in Zweig \"%s\"%s" -#: merge-recursive.c:1163 +#: merge-recursive.c:1164 msgid " (left unresolved)" msgstr " (bleibt unaufgelöst)" -#: merge-recursive.c:1217 +#: merge-recursive.c:1218 #, c-format msgid "CONFLICT (rename/rename): Rename %s->%s in %s. Rename %s->%s in %s" msgstr "" "KONFLIKT (umbenennen/umbenennen): Benenne um %s->%s in %s. Benenne um %s->%s " "in %s" -#: merge-recursive.c:1247 +#: merge-recursive.c:1248 #, c-format msgid "Renaming %s to %s and %s to %s instead" msgstr "Benenne stattdessen %s nach %s und %s nach %s um" -#: merge-recursive.c:1446 +#: merge-recursive.c:1447 #, c-format msgid "CONFLICT (rename/add): Rename %s->%s in %s. %s added in %s" msgstr "" "KONFLIKT (umbenennen/hinzufügen): Benenne um %s->%s in %s. %s hinzugefügt in " "%s" -#: merge-recursive.c:1456 +#: merge-recursive.c:1457 #, c-format msgid "Adding merged %s" msgstr "Füge zusammengeführte Datei %s hinzu" -#: merge-recursive.c:1461 merge-recursive.c:1659 +#: merge-recursive.c:1462 merge-recursive.c:1660 #, c-format msgid "Adding as %s instead" msgstr "Füge stattdessen als %s hinzu" -#: merge-recursive.c:1512 +#: merge-recursive.c:1513 #, c-format msgid "cannot read object %s" msgstr "kann Objekt %s nicht lesen" -#: merge-recursive.c:1515 +#: merge-recursive.c:1516 #, c-format msgid "object %s is not a blob" msgstr "Objekt %s ist kein Blob" -#: merge-recursive.c:1563 +#: merge-recursive.c:1564 msgid "modify" msgstr "ändern" -#: merge-recursive.c:1563 +#: merge-recursive.c:1564 msgid "modified" msgstr "geändert" -#: merge-recursive.c:1573 +#: merge-recursive.c:1574 msgid "content" msgstr "Inhalt" -#: merge-recursive.c:1580 +#: merge-recursive.c:1581 msgid "add/add" msgstr "hinzufügen/hinzufügen" -#: merge-recursive.c:1614 +#: merge-recursive.c:1615 #, c-format msgid "Skipped %s (merged same as existing)" msgstr "%s ausgelassen (Ergebnis der Zusammenführung existiert bereits)" -#: merge-recursive.c:1628 +#: merge-recursive.c:1629 #, c-format msgid "Auto-merging %s" msgstr "automatische Zusammenführung von %s" -#: merge-recursive.c:1632 git-submodule.sh:869 +#: merge-recursive.c:1633 git-submodule.sh:893 msgid "submodule" msgstr "Unterprojekt" -#: merge-recursive.c:1633 +#: merge-recursive.c:1634 #, c-format msgid "CONFLICT (%s): Merge conflict in %s" msgstr "KONFLIKT (%s): Zusammenführungskonflikt in %s" -#: merge-recursive.c:1723 +#: merge-recursive.c:1724 #, c-format msgid "Removing %s" msgstr "Entferne %s" -#: merge-recursive.c:1748 +#: merge-recursive.c:1749 msgid "file/directory" msgstr "Datei/Verzeichnis" -#: merge-recursive.c:1754 +#: merge-recursive.c:1755 msgid "directory/file" msgstr "Verzeichnis/Datei" -#: merge-recursive.c:1759 +#: merge-recursive.c:1760 #, c-format msgid "CONFLICT (%s): There is a directory with name %s in %s. Adding %s as %s" msgstr "" "KONFLIKT (%s): Es existiert bereits ein Verzeichnis %s in %s. Füge %s als %s " "hinzu." -#: merge-recursive.c:1769 +#: merge-recursive.c:1770 #, c-format msgid "Adding %s" msgstr "Füge %s hinzu" -#: merge-recursive.c:1786 +#: merge-recursive.c:1787 msgid "Fatal merge failure, shouldn't happen." msgstr "Fataler Fehler bei der Zusammenführung. Sollte nicht passieren." -#: merge-recursive.c:1805 +#: merge-recursive.c:1806 msgid "Already up-to-date!" msgstr "Bereits aktuell!" -#: merge-recursive.c:1814 +#: merge-recursive.c:1815 #, c-format msgid "merging of trees %s and %s failed" msgstr "Zusammenführen der Bäume %s und %s fehlgeschlagen" -#: merge-recursive.c:1844 +#: merge-recursive.c:1845 #, c-format msgid "Unprocessed path??? %s" msgstr "unverarbeiteter Pfad??? %s" -#: merge-recursive.c:1889 +#: merge-recursive.c:1890 msgid "Merging:" msgstr "Zusammenführung:" -#: merge-recursive.c:1902 +#: merge-recursive.c:1903 #, c-format msgid "found %u common ancestor:" msgid_plural "found %u common ancestors:" msgstr[0] "%u gemeinsamen Vorfahren gefunden" msgstr[1] "%u gemeinsame Vorfahren gefunden" -#: merge-recursive.c:1939 +#: merge-recursive.c:1940 msgid "merge returned no commit" msgstr "Zusammenführung hat keine Version zurückgegeben" -#: merge-recursive.c:1996 +#: merge-recursive.c:1997 #, c-format msgid "Could not parse object '%s'" msgstr "Konnte Objekt '%s' nicht parsen." -#: merge-recursive.c:2008 builtin/merge.c:696 +#: merge-recursive.c:2009 builtin/merge.c:643 msgid "Unable to write index." msgstr "Konnte Bereitstellung nicht schreiben." @@ -630,21 +721,30 @@ msgstr " %s" #, c-format msgid "Your branch is ahead of '%s' by %d commit.\n" msgid_plural "Your branch is ahead of '%s' by %d commits.\n" -msgstr[0] "Dein Zweig ist vor '%s' um %d Version.\n" -msgstr[1] "Dein Zweig ist vor '%s' um %d Versionen.\n" +msgstr[0] "Ihr Zweig ist vor '%s' um %d Version.\n" +msgstr[1] "Ihr Zweig ist vor '%s' um %d Versionen.\n" + +#: remote.c:1637 +msgid " (use \"git push\" to publish your local commits)\n" +msgstr " (benutzen Sie \"git push\" um lokalen Versionen herauszubringen)\n" -#: remote.c:1638 +#: remote.c:1640 #, c-format msgid "Your branch is behind '%s' by %d commit, and can be fast-forwarded.\n" msgid_plural "" "Your branch is behind '%s' by %d commits, and can be fast-forwarded.\n" msgstr[0] "" -"Dein Zweig ist zu '%s' um %d Version hinterher, und kann vorgespult werden.\n" +"Ihr Zweig ist zu '%s' um %d Version hinterher, und kann vorgespult werden.\n" msgstr[1] "" -"Dein Zweig ist zu '%s' um %d Versionen hinterher, und kann vorgespult " +"Ihr Zweig ist zu '%s' um %d Versionen hinterher, und kann vorgespult " "werden.\n" -#: remote.c:1646 +#: remote.c:1647 +msgid " (use \"git pull\" to update your local branch)\n" +msgstr "" +" (benutzen Sie \"git pull\" um Ihren lokalen Zweig zu aktualisieren)\n" + +#: remote.c:1650 #, c-format msgid "" "Your branch and '%s' have diverged,\n" @@ -653,110 +753,115 @@ msgid_plural "" "Your branch and '%s' have diverged,\n" "and have %d and %d different commits each, respectively.\n" msgstr[0] "" -"Dein Zweig und '%s' sind divergiert,\n" +"Ihr Zweig und '%s' sind divergiert,\n" "und haben jeweils %d und %d unterschiedliche Versionen.\n" msgstr[1] "" -"Dein Zweig und '%s' sind divergiert,\n" +"Ihr Zweig und '%s' sind divergiert,\n" "und haben jeweils %d und %d unterschiedliche Versionen.\n" -#: sequencer.c:121 builtin/merge.c:864 builtin/merge.c:977 -#: builtin/merge.c:1087 builtin/merge.c:1097 +#: remote.c:1659 +msgid " (use \"git pull\" to merge the remote branch into yours)\n" +msgstr "" +" (benutzen Sie \"git pull\" um Ihren Zweig mit dem externen zusammenzuführen)\n" + +#: 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 "Konnte '%s' nicht zum Schreiben öffnen." -#: sequencer.c:123 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 "Konnte nicht nach '%s' schreiben." -#: sequencer.c:144 +#: sequencer.c:146 msgid "" "after resolving the conflicts, mark the corrected paths\n" "with 'git add ' or 'git rm '" msgstr "" -"nach Auflösung der Konflikte, markiere die korrigierten Pfade\n" +"nach Auflösung der Konflikte, markieren Sie die korrigierten Pfade\n" "mit 'git add ' oder 'git rm '" -#: sequencer.c:147 +#: sequencer.c:149 msgid "" "after resolving the conflicts, mark the corrected paths\n" "with 'git add ' or 'git rm '\n" "and commit the result with 'git commit'" msgstr "" -"nach Auflösung der Konflikte, markiere die korrigierten Pfade\n" -"mit 'git add ' oder 'git rm 'und trage das Ergebnis ein mit " -"'git commit'" +"nach Auflösung der Konflikte, markieren Sie die korrigierten Pfade\n" +"mit 'git add ' oder 'git rm 'und tragen Sie das Ergebnis mit\n" +"'git commit' ein" -#: sequencer.c:160 sequencer.c:758 sequencer.c:841 +#: sequencer.c:162 sequencer.c:770 sequencer.c:853 #, c-format msgid "Could not write to %s" msgstr "Konnte nicht nach %s schreiben" -#: sequencer.c:163 +#: sequencer.c:165 #, c-format msgid "Error wrapping up %s" msgstr "Fehler bei Nachbereitung von %s" -#: sequencer.c:178 +#: sequencer.c:180 msgid "Your local changes would be overwritten by cherry-pick." msgstr "" -"Deine lokalen Änderungen würden von \"cherry-pick\" überschrieben werden." +"Ihre lokalen Änderungen würden von \"cherry-pick\" überschrieben werden." -#: sequencer.c:180 +#: sequencer.c:182 msgid "Your local changes would be overwritten by revert." -msgstr "Deine lokalen Änderungen würden von \"revert\" überschrieben werden." +msgstr "Ihre lokalen Änderungen würden von \"revert\" überschrieben werden." -#: sequencer.c:183 +#: sequencer.c:185 msgid "Commit your changes or stash them to proceed." -msgstr "Trage deine Änderungen ein oder benutze \"stash\" um fortzufahren." +msgstr "Tragen Sie Ihre Änderungen ein oder benutzen Sie \"stash\" um fortzufahren." #. TRANSLATORS: %s will be "revert" or "cherry-pick" -#: sequencer.c:233 +#: sequencer.c:235 #, c-format msgid "%s: Unable to write new index file" msgstr "%s: Konnte neue Bereitstellungsdatei nicht schreiben" -#: sequencer.c:261 +#: sequencer.c:266 msgid "Could not resolve HEAD commit\n" msgstr "Konnte Version der Zweigspitze (HEAD) nicht auflösen\n" -#: sequencer.c:282 +#: sequencer.c:287 msgid "Unable to update cache tree\n" msgstr "Konnte zwischengespeicherten Baum nicht aktualisieren\n" -#: sequencer.c:324 +#: sequencer.c:332 #, c-format msgid "Could not parse commit %s\n" msgstr "Konnte Version %s nicht parsen\n" -#: sequencer.c:329 +#: sequencer.c:337 #, c-format msgid "Could not parse parent commit %s\n" msgstr "Konnte Elternversion %s nicht parsen\n" -#: sequencer.c:395 +#: sequencer.c:403 msgid "Your index file is unmerged." -msgstr "Deine Bereitstellungsdatei ist nicht zusammengeführt." +msgstr "Ihre Bereitstellungsdatei ist nicht zusammengeführt." -#: sequencer.c:398 +#: sequencer.c:406 msgid "You do not have a valid HEAD" -msgstr "Du hast keine gültige Zweigspitze (HEAD)" +msgstr "Sie haben keine gültige Zweigspitze (HEAD)" -#: sequencer.c:413 +#: sequencer.c:421 #, c-format msgid "Commit %s is a merge but no -m option was given." msgstr "" "Version %s ist eine Zusammenführung, aber die Option -m wurde nicht " "angegeben." -#: sequencer.c:421 +#: sequencer.c:429 #, c-format msgid "Commit %s does not have parent %d" msgstr "Version %s hat keinen Elternteil %d" -#: sequencer.c:425 +#: sequencer.c:433 #, c-format msgid "Mainline was specified but commit %s is not a merge." msgstr "" @@ -764,145 +869,145 @@ msgstr "" #. TRANSLATORS: The first %s will be "revert" or #. "cherry-pick", the second %s a SHA1 -#: sequencer.c:436 +#: sequencer.c:444 #, c-format msgid "%s: cannot parse parent commit %s" msgstr "%s: kann Elternversion %s nicht parsen" -#: sequencer.c:440 +#: sequencer.c:448 #, c-format msgid "Cannot get commit message for %s" msgstr "Kann keine Versionsbeschreibung für %s bekommen" -#: sequencer.c:524 +#: sequencer.c:532 #, c-format msgid "could not revert %s... %s" msgstr "Konnte %s nicht zurücksetzen... %s" -#: sequencer.c:525 +#: sequencer.c:533 #, c-format msgid "could not apply %s... %s" msgstr "Konnte %s nicht anwenden... %s" -#: sequencer.c:553 +#: sequencer.c:565 msgid "empty commit set passed" msgstr "leere Menge von Versionen übergeben" -#: sequencer.c:561 +#: sequencer.c:573 #, c-format msgid "git %s: failed to read the index" msgstr "git %s: Fehler beim Lesen der Bereitstellung" -#: sequencer.c:566 +#: sequencer.c:578 #, c-format msgid "git %s: failed to refresh the index" msgstr "git %s: Fehler beim Aktualisieren der Bereitstellung" -#: sequencer.c:624 +#: sequencer.c:636 #, c-format msgid "Cannot %s during a %s" msgstr "Kann %s nicht während eines %s durchführen" -#: sequencer.c:646 +#: sequencer.c:658 #, c-format msgid "Could not parse line %d." msgstr "Konnte Zeile %d nicht parsen." -#: sequencer.c:651 +#: sequencer.c:663 msgid "No commits parsed." msgstr "Keine Versionen geparst." -#: sequencer.c:664 +#: sequencer.c:676 #, c-format msgid "Could not open %s" msgstr "Konnte %s nicht öffnen" -#: sequencer.c:668 +#: sequencer.c:680 #, c-format msgid "Could not read %s." msgstr "Konnte %s nicht lesen." -#: sequencer.c:675 +#: sequencer.c:687 #, c-format msgid "Unusable instruction sheet: %s" msgstr "Unbenutzbares Instruktionsblatt: %s" -#: sequencer.c:703 +#: sequencer.c:715 #, c-format msgid "Invalid key: %s" msgstr "Ungültiger Schlüssel: %s" -#: sequencer.c:706 +#: sequencer.c:718 #, c-format msgid "Invalid value for %s: %s" msgstr "Ungültiger Wert für %s: %s" -#: sequencer.c:718 +#: sequencer.c:730 #, c-format msgid "Malformed options sheet: %s" msgstr "Fehlerhaftes Optionsblatt: %s" -#: sequencer.c:739 +#: sequencer.c:751 msgid "a cherry-pick or revert is already in progress" msgstr "\"cherry-pick\" oder \"revert\" ist bereits im Gang" -#: sequencer.c:740 +#: sequencer.c:752 msgid "try \"git cherry-pick (--continue | --quit | --abort)\"" -msgstr "versuche \"git cherry-pick (--continue | --quit | --abort)\"" +msgstr "versuchen Sie \"git cherry-pick (--continue | --quit | --abort)\"" -#: sequencer.c:744 +#: sequencer.c:756 #, c-format msgid "Could not create sequencer directory %s" msgstr "Konnte \"sequencer\"-Verzeichnis %s nicht erstellen" -#: sequencer.c:760 sequencer.c:845 +#: sequencer.c:772 sequencer.c:857 #, c-format msgid "Error wrapping up %s." msgstr "Fehler beim Einpacken von %s." -#: sequencer.c:779 sequencer.c:913 +#: sequencer.c:791 sequencer.c:925 msgid "no cherry-pick or revert in progress" msgstr "kein \"cherry-pick\" oder \"revert\" im Gang" -#: sequencer.c:781 +#: sequencer.c:793 msgid "cannot resolve HEAD" msgstr "kann Zweigspitze (HEAD) nicht auflösen" -#: sequencer.c:783 +#: sequencer.c:795 msgid "cannot abort from a branch yet to be born" msgstr "kann nicht abbrechen: bin auf einem Zweig, der noch geboren wird" -#: sequencer.c:805 builtin/apply.c:3987 +#: sequencer.c:817 builtin/apply.c:4005 #, c-format msgid "cannot open %s: %s" msgstr "Kann %s nicht öffnen: %s" -#: sequencer.c:808 +#: sequencer.c:820 #, c-format msgid "cannot read %s: %s" msgstr "Kann %s nicht lesen: %s" -#: sequencer.c:809 +#: sequencer.c:821 msgid "unexpected end of file" msgstr "Unerwartetes Dateiende" -#: sequencer.c:815 +#: sequencer.c:827 #, c-format msgid "stored pre-cherry-pick HEAD file '%s' is corrupt" msgstr "" "gespeicherte \"pre-cherry-pick\" Datei der Zweigspitze (HEAD) '%s' ist " "beschädigt" -#: sequencer.c:838 +#: sequencer.c:850 #, c-format msgid "Could not format %s." msgstr "Konnte %s nicht formatieren." -#: sequencer.c:1000 +#: sequencer.c:1012 msgid "Can't revert as initial commit" msgstr "Kann nicht zu initialer Version zurücksetzen." -#: sequencer.c:1001 +#: sequencer.c:1013 msgid "Can't cherry-pick into empty head" msgstr "Kann \"cherry-pick\" nicht in einem leerem Kopf ausführen." @@ -926,12 +1031,17 @@ msgid "Upstream branch '%s' not stored as a remote-tracking branch" msgstr "" "Zweig '%s' des entfernten Projektarchivs ist kein gefolgter Übernahmezweig" -#: wrapper.c:413 +#: wrapper.c:408 +#, c-format +msgid "unable to access '%s': %s" +msgstr "konnte nicht auf '%s' zugreifen: %s" + +#: wrapper.c:426 #, c-format msgid "unable to look up current user in the passwd file: %s" msgstr "konnte aktuellen Benutzer nicht in Passwort-Datei finden: %s" -#: wrapper.c:414 +#: wrapper.c:427 msgid "no such user" msgstr "kein solcher Benutzer" @@ -943,28 +1053,28 @@ msgstr "Nicht zusammengeführte Pfade:" #, c-format msgid " (use \"git reset %s ...\" to unstage)" msgstr "" -" (benutze \"git reset %s ...\" zum Herausnehmen aus der " +" (benutzen Sie \"git reset %s ...\" zum Herausnehmen aus der " "Bereitstellung)" #: wt-status.c:169 wt-status.c:196 msgid " (use \"git rm --cached ...\" to unstage)" msgstr "" -" (benutze \"git rm --cached ...\" zum Herausnehmen aus der " +" (benutzen Sie \"git rm --cached ...\" zum Herausnehmen aus der " "Bereitstellung)" #: wt-status.c:173 msgid " (use \"git add ...\" to mark resolution)" -msgstr " (benutze \"git add/rm ...\" um die Auflösung zu markieren)" +msgstr " (benutzen Sie \"git add/rm ...\" um die Auflösung zu markieren)" #: wt-status.c:175 wt-status.c:179 msgid " (use \"git add/rm ...\" as appropriate to mark resolution)" msgstr "" -" (benutze \"git add/rm ...\" um die Auflösung entsprechend zu " +" (benutzen Sie \"git add/rm ...\" um die Auflösung entsprechend zu " "markieren)" #: wt-status.c:177 msgid " (use \"git rm ...\" to mark resolution)" -msgstr " (benutze \"git add/rm ...\" um die Auflösung zu markieren)" +msgstr " (benutzen Sie \"git add/rm ...\" um die Auflösung zu markieren)" #: wt-status.c:188 msgid "Changes to be committed:" @@ -976,34 +1086,29 @@ msgstr "Änderungen, die nicht zum Eintragen bereitgestellt sind:" #: wt-status.c:210 msgid " (use \"git add ...\" to update what will be committed)" -msgstr " (benutze \"git add ...\" zum Bereitstellen)" +msgstr " (benutzen Sie \"git add ...\" zum Bereitstellen)" #: wt-status.c:212 msgid " (use \"git add/rm ...\" to update what will be committed)" -msgstr " (benutze \"git add/rm ...\" zum Bereitstellen)" +msgstr " (benutzen Sie \"git add/rm ...\" zum Bereitstellen)" #: wt-status.c:213 msgid "" " (use \"git checkout -- ...\" to discard changes in working directory)" msgstr "" -" (benutze \"git checkout -- ...\" um die Änderungen im " +" (benutzen Sie \"git checkout -- ...\" um die Änderungen im " "Arbeitsverzeichnis zu verwerfen)" #: wt-status.c:215 msgid " (commit or discard the untracked or modified content in submodules)" msgstr "" -" (trage ein oder verwerfe den unbeobachteten oder geänderten Inhalt in den " +" (tragen Sie ein oder verwerfen Sie den unbeobachteten oder geänderten Inhalt in den " "Unterprojekten)" -#: wt-status.c:224 -#, c-format -msgid "%s files:" -msgstr "%s Dateien:" - #: wt-status.c:227 #, c-format msgid " (use \"git %s ...\" to include in what will be committed)" -msgstr " (benutze \"git %s ...\" zum Einfügen in die Eintragung)" +msgstr " (benutzen Sie \"git %s ...\" zum Einfügen in die Eintragung)" #: wt-status.c:244 msgid "bug" @@ -1096,20 +1201,20 @@ msgstr "Fehler: unbehandelter Differenz-Status %c" #: wt-status.c:785 msgid "You have unmerged paths." -msgstr "Du hast nicht zusammengeführte Pfade." +msgstr "Sie haben nicht zusammengeführte Pfade." #: wt-status.c:788 wt-status.c:912 msgid " (fix conflicts and run \"git commit\")" -msgstr " (behebe die Konflikte und führe \"git commit\" aus)" +msgstr " (beheben Sie die Konflikte und führen Sie \"git commit\" aus)" #: wt-status.c:791 msgid "All conflicts fixed but you are still merging." msgstr "" -"Alle Konflikte sind behoben, aber du bist immer noch beim Zusammenführen." +"Alle Konflikte sind behoben, aber Sie sind immer noch beim Zusammenführen." #: wt-status.c:794 msgid " (use \"git commit\" to conclude merge)" -msgstr " (benutze \"git commit\" um die Zusammenführung abzuschließen)" +msgstr " (benutzen Sie \"git commit\" um die Zusammenführung abzuschließen)" #: wt-status.c:804 msgid "You are in the middle of an am session." @@ -1121,80 +1226,80 @@ msgstr "Der aktuelle Patch ist leer." #: wt-status.c:811 msgid " (fix conflicts and then run \"git am --resolved\")" -msgstr " (behebe die Konflikte und führe dann \"git am --resolved\" aus)" +msgstr " (beheben Sie die Konflikte und führen Sie dann \"git am --resolved\" aus)" #: wt-status.c:813 msgid " (use \"git am --skip\" to skip this patch)" -msgstr " (benutze \"git am --skip\" um diesen Patch auszulassen)" +msgstr " (benutzen Sie \"git am --skip\" um diesen Patch auszulassen)" #: wt-status.c:815 msgid " (use \"git am --abort\" to restore the original branch)" msgstr "" -" (benutze \"git am --abort\" um den ursprünglichen Zweig wiederherzustellen)" +" (benutzen Sie \"git am --abort\" um den ursprünglichen Zweig wiederherzustellen)" #: wt-status.c:873 wt-status.c:883 msgid "You are currently rebasing." -msgstr "Du bist gerade beim Neuaufbau." +msgstr "Sie sind gerade beim Neuaufbau." #: wt-status.c:876 msgid " (fix conflicts and then run \"git rebase --continue\")" -msgstr " (behebe die Konflikte und führe dann \"git rebase --continue\" aus)" +msgstr " (beheben Sie die Konflikte und führen Sie dann \"git rebase --continue\" aus)" #: wt-status.c:878 msgid " (use \"git rebase --skip\" to skip this patch)" -msgstr " (benutze \"git rebase --skip\" um diesen Patch auszulassen)" +msgstr " (benutzen Sie \"git rebase --skip\" um diesen Patch auszulassen)" #: wt-status.c:880 msgid " (use \"git rebase --abort\" to check out the original branch)" msgstr "" -" (benutze \"git rebase --abort\" um den ursprünglichen Zweig auszuchecken)" +" (benutzen Sie \"git rebase --abort\" um den ursprünglichen Zweig auszuchecken)" #: wt-status.c:886 msgid " (all conflicts fixed: run \"git rebase --continue\")" -msgstr " (alle Konflikte behoben: führe \"git rebase --continue\" aus)" +msgstr " (alle Konflikte behoben: führen Sie \"git rebase --continue\" aus)" #: wt-status.c:888 msgid "You are currently splitting a commit during a rebase." -msgstr "Du teilst gerade eine Version während eines Neuaufbaus auf." +msgstr "Sie teilen gerade eine Version während eines Neuaufbaus auf." #: wt-status.c:891 msgid " (Once your working directory is clean, run \"git rebase --continue\")" msgstr "" -" (Sobald dein Arbeitsverzeichnis sauber ist, führe \"git rebase --continue" +" (Sobald Ihr Arbeitsverzeichnis sauber ist, führen Sie \"git rebase --continue" "\" aus)" #: wt-status.c:893 msgid "You are currently editing a commit during a rebase." -msgstr "Du editierst gerade eine Version während eines Neuaufbaus." +msgstr "Sie editieren gerade eine Version während eines Neuaufbaus." #: wt-status.c:896 msgid " (use \"git commit --amend\" to amend the current commit)" msgstr "" -" (benutze \"git commit --amend\" um die aktuelle Version nachzubessern)" +" (benutzen Sie \"git commit --amend\" um die aktuelle Version nachzubessern)" #: wt-status.c:898 msgid "" " (use \"git rebase --continue\" once you are satisfied with your changes)" msgstr "" -" (benutze \"git rebase --continue\" sobald deine Änderungen abgeschlossen " +" (benutzen Sie \"git rebase --continue\" sobald Ihre Änderungen abgeschlossen " "sind)" #: wt-status.c:908 msgid "You are currently cherry-picking." -msgstr "Du führst gerade \"cherry-pick\" aus." +msgstr "Sie führen gerade \"cherry-pick\" aus." #: wt-status.c:915 msgid " (all conflicts fixed: run \"git commit\")" -msgstr " (alle Konflikte behoben: führe \"git commit\" aus)" +msgstr " (alle Konflikte behoben: führen Sie \"git commit\" aus)" #: wt-status.c:924 msgid "You are currently bisecting." -msgstr "Du bist gerade beim Halbieren." +msgstr "Sie sind gerade beim Halbieren." #: wt-status.c:927 msgid " (use \"git bisect reset\" to get back to the original branch)" msgstr "" -" (benutze \"git bisect reset\" um zum ursprünglichen Zweig zurückzukehren)" +" (benutzen Sie \"git bisect reset\" um zum ursprünglichen Zweig zurückzukehren)" #: wt-status.c:978 msgid "On branch " @@ -1209,12 +1314,12 @@ msgid "Initial commit" msgstr "Initiale Version" #: wt-status.c:1011 -msgid "Untracked" -msgstr "Unbeobachtete" +msgid "Untracked files" +msgstr "Unbeobachtete Dateien" #: wt-status.c:1013 -msgid "Ignored" -msgstr "Ignorierte" +msgid "Ignored files" +msgstr "Ignorierte Dateien" #: wt-status.c:1015 #, c-format @@ -1223,65 +1328,79 @@ msgstr "Unbeobachtete Dateien nicht aufgelistet%s" #: wt-status.c:1017 msgid " (use -u option to show untracked files)" -msgstr " (benutze die Option -u um unbeobachteten Dateien anzuzeigen)" +msgstr " (benutzen Sie die Option -u um unbeobachteten Dateien anzuzeigen)" #: wt-status.c:1023 msgid "No changes" msgstr "Keine Änderungen" -#: wt-status.c:1027 +#: wt-status.c:1028 #, c-format -msgid "no changes added to commit%s\n" -msgstr "keine Änderungen zum Eintragen hinzugefügt%s\n" - -#: wt-status.c:1029 -msgid " (use \"git add\" and/or \"git commit -a\")" -msgstr " (benutze \"git add\" und/oder \"git commit -a\")" +msgid "no changes added to commit (use \"git add\" and/or \"git commit -a\")\n" +msgstr "" +"keine Änderungen zum Eintragen hinzugefügt (benutzen Sie \"git add\" und/oder " +"\"git commit -a\")\n" #: wt-status.c:1031 #, c-format -msgid "nothing added to commit but untracked files present%s\n" +msgid "no changes added to commit\n" +msgstr "keine Änderungen zum Eintragen hinzugefügt\n" + +#: wt-status.c:1034 +#, c-format +msgid "" +"nothing added to commit but untracked files present (use \"git add\" to " +"track)\n" msgstr "" -"nichts zum Eintragen hinzugefügt, aber es gibt unbeobachtete Dateien%s\n" +"nichts zum Eintragen hinzugefügt, aber es gibt unbeobachtete Dateien " +"(benutzen Sie \"git add\" zum Beobachten)\n" -#: wt-status.c:1033 -msgid " (use \"git add\" to track)" -msgstr " (benutze \"git add\" zum Beobachten)" +#: wt-status.c:1037 +#, c-format +msgid "nothing added to commit but untracked files present\n" +msgstr "nichts zum Eintragen hinzugefügt, aber es gibt unbeobachtete Dateien\n" -#: wt-status.c:1035 wt-status.c:1038 wt-status.c:1041 +#: wt-status.c:1040 #, c-format -msgid "nothing to commit%s\n" -msgstr "nichts zum Eintragen%s\n" +msgid "nothing to commit (create/copy files and use \"git add\" to track)\n" +msgstr "" +"nichts einzutragen (Erstellen/Kopieren Sie Dateien und benutzen Sie \"git add\" " +"zum Beobachten)\n" -#: wt-status.c:1036 -msgid " (create/copy files and use \"git add\" to track)" -msgstr " (Erstelle/Kopiere Dateien und benutze \"git add\" zum Beobachten)" +#: wt-status.c:1043 wt-status.c:1048 +#, c-format +msgid "nothing to commit\n" +msgstr "nichts einzutragen\n" -#: wt-status.c:1039 -msgid " (use -u to show untracked files)" -msgstr " (benutze die Option -u um unbeobachtete Dateien anzuzeigen)" +#: wt-status.c:1046 +#, c-format +msgid "nothing to commit (use -u to show untracked files)\n" +msgstr "" +"nichts einzutragen (benutzen Sie die Option -u, um unbeobachtete Dateien " +"anzuzeigen)\n" -#: wt-status.c:1042 -msgid " (working directory clean)" -msgstr " (Arbeitsverzeichnis sauber)" +#: wt-status.c:1050 +#, c-format +msgid "nothing to commit, working directory clean\n" +msgstr "nichts einzutragen, Arbeitsverzeichnis sauber\n" -#: wt-status.c:1150 +#: wt-status.c:1158 msgid "HEAD (no branch)" msgstr "HEAD (kein Zweig)" -#: wt-status.c:1156 +#: wt-status.c:1164 msgid "Initial commit on " msgstr "Initiale Version auf " -#: wt-status.c:1171 +#: wt-status.c:1179 msgid "behind " msgstr "hinterher " -#: wt-status.c:1174 wt-status.c:1177 +#: wt-status.c:1182 wt-status.c:1185 msgid "ahead " msgstr "voraus " -#: wt-status.c:1179 +#: wt-status.c:1187 msgid ", behind " msgstr ", hinterher " @@ -1290,19 +1409,23 @@ msgstr ", hinterher " msgid "failed to unlink '%s'" msgstr "Konnte '%s' nicht entfernen" +#: builtin/add.c:19 +msgid "git add [options] [--] ..." +msgstr "git add [Optionen] [--] [...]" + #: builtin/add.c:62 #, c-format msgid "unexpected diff status %c" msgstr "unerwarteter Differenz-Status %c" -#: builtin/add.c:67 builtin/commit.c:229 +#: builtin/add.c:67 builtin/commit.c:231 msgid "updating files failed" msgstr "Aktualisierung der Dateien fehlgeschlagen" #: builtin/add.c:77 #, c-format msgid "remove '%s'\n" -msgstr "entferne '%s'\n" +msgstr "lösche '%s'\n" #: builtin/add.c:176 #, c-format @@ -1314,7 +1437,7 @@ msgid "Unstaged changes after refreshing the index:" msgstr "" "Nicht bereitgestellte Änderungen nach Aktualisierung der Bereitstellung:" -#: builtin/add.c:195 builtin/add.c:459 builtin/rm.c:186 +#: builtin/add.c:195 builtin/add.c:460 builtin/rm.c:275 #, c-format msgid "pathspec '%s' did not match any files" msgstr "Pfadspezifikation '%s' stimmt mit keinen Dateien überein" @@ -1351,50 +1474,105 @@ msgstr "Leerer Patch. Abgebrochen." msgid "Could not apply '%s'" msgstr "Konnte '%s' nicht anwenden." -#: builtin/add.c:312 +#: builtin/add.c:313 msgid "The following paths are ignored by one of your .gitignore files:\n" msgstr "" -"Die folgenden Pfade werden durch eine deiner \".gitignore\" Dateien " +"Die folgenden Pfade werden durch eine Ihrer \".gitignore\" Dateien " "ignoriert:\n" -#: builtin/add.c:352 +#: 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:206 +msgid "dry run" +msgstr "Probelauf" + +#: 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 "erweiterte Ausgaben" + +#: builtin/add.c:322 +msgid "interactive picking" +msgstr "interaktives Auswählen" + +#: builtin/add.c:323 builtin/checkout.c:1031 builtin/reset.c:248 +msgid "select hunks interactively" +msgstr "interaktiv Bereiche auswählen" + +#: builtin/add.c:324 +msgid "edit current diff and apply" +msgstr "aktuelle Unterschiede editieren und anwenden" + +#: builtin/add.c:325 +msgid "allow adding otherwise ignored files" +msgstr "erlaubt das Hinzufügen andernfalls ignorierter Dateien" + +#: builtin/add.c:326 +msgid "update tracked files" +msgstr "aktualisiert beobachtete Dateien" + +#: builtin/add.c:327 +msgid "record only the fact that the path will be added later" +msgstr "speichert nur, dass der Pfad später hinzugefügt werden soll" + +#: builtin/add.c:328 +msgid "add changes from all tracked and untracked files" +msgstr "" +"fügt Änderungen von allen beobachteten und unbeobachteten Dateien hinzu" + +#: builtin/add.c:329 +msgid "don't add, only refresh the index" +msgstr "fügt nichts hinzu, aktualisiert nur die Bereitstellung" + +#: builtin/add.c:330 +msgid "just skip files which cannot be added because of errors" +msgstr "" +"überspringt Dateien, die aufgrund von Fehlern nicht hinzugefügt werden " +"konnten" + +#: builtin/add.c:331 +msgid "check if - even missing - files are ignored in dry run" +msgstr "prüft ob - auch fehlende - Dateien im Probelauf ignoriert werden" + +#: builtin/add.c:353 #, c-format msgid "Use -f if you really want to add them.\n" -msgstr "Verwende -f wenn du diese wirklich hinzufügen möchtest.\n" +msgstr "Verwenden Sie -f wenn Sie diese wirklich hinzufügen möchten.\n" -#: builtin/add.c:353 +#: builtin/add.c:354 msgid "no files added" msgstr "keine Dateien hinzugefügt" -#: builtin/add.c:359 +#: builtin/add.c:360 msgid "adding files failed" msgstr "Hinzufügen von Dateien fehlgeschlagen" -#: builtin/add.c:391 +#: builtin/add.c:392 msgid "-A and -u are mutually incompatible" msgstr "-A und -u sind zueinander inkompatibel" -#: builtin/add.c:393 +#: builtin/add.c:394 msgid "Option --ignore-missing can only be used together with --dry-run" msgstr "" "Die Option --ignore-missing kann nur zusammen mit --dry-run benutzt werden." -#: builtin/add.c:413 +#: builtin/add.c:414 #, c-format msgid "Nothing specified, nothing added.\n" msgstr "Nichts spezifiziert, nichts hinzugefügt.\n" -#: builtin/add.c:414 +#: builtin/add.c:415 #, c-format msgid "Maybe you wanted to say 'git add .'?\n" -msgstr "Wolltest du vielleicht 'git add .' sagen?\n" +msgstr "Wollten Sie vielleicht 'git add .' sagen?\n" -#: builtin/add.c:420 builtin/clean.c:95 builtin/commit.c:289 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 "Bereitstellungsdatei beschädigt" -#: builtin/add.c:480 builtin/apply.c:4432 builtin/mv.c:229 builtin/rm.c:260 +#: builtin/add.c:481 builtin/apply.c:4450 builtin/mv.c:229 builtin/rm.c:370 msgid "Unable to write new index file" msgstr "Konnte neue Bereitstellungsdatei nicht schreiben." @@ -1451,17 +1629,17 @@ msgstr "" msgid "git apply: bad git-diff - expected /dev/null on line %d" msgstr "git apply: ungültiges 'git-diff' - erwartete /dev/null in Zeile %d" -#: builtin/apply.c:1402 +#: builtin/apply.c:1420 #, c-format msgid "recount: unexpected line: %.*s" msgstr "recount: unerwartete Zeile: %.*s" -#: builtin/apply.c:1459 +#: builtin/apply.c:1477 #, c-format msgid "patch fragment without header at line %d: %.*s" msgstr "Patch-Fragment ohne Kopfbereich bei Zeile %d: %.*s" -#: builtin/apply.c:1476 +#: builtin/apply.c:1494 #, c-format msgid "" "git diff header lacks filename information when removing %d leading pathname " @@ -1476,70 +1654,70 @@ msgstr[1] "" "Dem Kopfbereich von \"git diff\" fehlen Informationen zum Dateinamen, wenn " "%d vorangestellte Teile des Pfades entfernt werden (Zeile %d)" -#: builtin/apply.c:1636 +#: builtin/apply.c:1654 msgid "new file depends on old contents" msgstr "neue Datei hängt von alten Inhalten ab" -#: builtin/apply.c:1638 +#: builtin/apply.c:1656 msgid "deleted file still has contents" msgstr "entfernte Datei hat noch Inhalte" -#: builtin/apply.c:1664 +#: builtin/apply.c:1682 #, c-format msgid "corrupt patch at line %d" msgstr "fehlerhafter Patch bei Zeile %d" -#: builtin/apply.c:1700 +#: builtin/apply.c:1718 #, c-format msgid "new file %s depends on old contents" msgstr "neue Datei %s hängt von alten Inhalten ab" -#: builtin/apply.c:1702 +#: builtin/apply.c:1720 #, c-format msgid "deleted file %s still has contents" msgstr "entfernte Datei %s hat noch Inhalte" -#: builtin/apply.c:1705 +#: builtin/apply.c:1723 #, c-format msgid "** warning: file %s becomes empty but is not deleted" msgstr "** Warnung: Datei %s wird leer, aber nicht entfernt." -#: builtin/apply.c:1851 +#: builtin/apply.c:1869 #, c-format msgid "corrupt binary patch at line %d: %.*s" msgstr "fehlerhafter Binär-Patch bei Zeile %d: %.*s" #. there has to be one hunk (forward hunk) -#: builtin/apply.c:1880 +#: builtin/apply.c:1898 #, c-format msgid "unrecognized binary patch at line %d" msgstr "nicht erkannter Binär-Patch bei Zeile %d" -#: builtin/apply.c:1966 +#: builtin/apply.c:1984 #, c-format msgid "patch with only garbage at line %d" msgstr "Patch mit nutzlosen Informationen bei Zeile %d" -#: builtin/apply.c:2056 +#: builtin/apply.c:2074 #, c-format msgid "unable to read symlink %s" msgstr "konnte symbolische Verknüpfung %s nicht lesen" -#: builtin/apply.c:2060 +#: builtin/apply.c:2078 #, c-format msgid "unable to open or read %s" msgstr "konnte %s nicht öffnen oder lesen" -#: builtin/apply.c:2131 +#: builtin/apply.c:2149 msgid "oops" msgstr "Ups" -#: builtin/apply.c:2653 +#: builtin/apply.c:2671 #, c-format msgid "invalid start of line: '%c'" msgstr "Ungültiger Zeilenanfang: '%c'" -#: builtin/apply.c:2771 +#: builtin/apply.c:2789 #, c-format msgid "Hunk #%d succeeded at %d (offset %d line)." msgid_plural "Hunk #%d succeeded at %d (offset %d lines)." @@ -1547,12 +1725,12 @@ msgstr[0] "Patch-Bereich #%d erfolgreich angewendet bei %d (%d Zeile versetzt)" msgstr[1] "" "Patch-Bereich #%d erfolgreich angewendet bei %d (%d Zeilen versetzt)" -#: builtin/apply.c:2783 +#: builtin/apply.c:2801 #, c-format msgid "Context reduced to (%ld/%ld) to apply fragment at %d" msgstr "Kontext reduziert zu (%ld/%ld) um Patch-Bereich bei %d anzuwenden" -#: builtin/apply.c:2789 +#: builtin/apply.c:2807 #, c-format msgid "" "while searching for:\n" @@ -1561,339 +1739,335 @@ msgstr "" "bei der Suche nach:\n" "%.*s" -#: builtin/apply.c:2808 +#: builtin/apply.c:2826 #, c-format msgid "missing binary patch data for '%s'" msgstr "keine Daten in Binär-Patch für '%s'" -#: builtin/apply.c:2911 +#: builtin/apply.c:2929 #, c-format msgid "binary patch does not apply to '%s'" msgstr "Konnte Binär-Patch nicht auf '%s' anwenden" -#: builtin/apply.c:2917 +#: builtin/apply.c:2935 #, c-format msgid "binary patch to '%s' creates incorrect result (expecting %s, got %s)" msgstr "" "Binär-Patch für '%s' erzeugt falsches Ergebnis (erwartete %s, bekam %s)" -#: builtin/apply.c:2938 +#: builtin/apply.c:2956 #, c-format msgid "patch failed: %s:%ld" msgstr "Anwendung des Patches fehlgeschlagen: %s:%ld" -#: builtin/apply.c:3060 +#: builtin/apply.c:3078 #, c-format msgid "cannot checkout %s" msgstr "kann %s nicht auschecken" -#: builtin/apply.c:3105 builtin/apply.c:3114 builtin/apply.c:3158 +#: builtin/apply.c:3123 builtin/apply.c:3132 builtin/apply.c:3176 #, c-format msgid "read of %s failed" msgstr "Konnte %s nicht lesen" -#: builtin/apply.c:3138 builtin/apply.c:3360 +#: builtin/apply.c:3156 builtin/apply.c:3378 #, c-format msgid "path %s has been renamed/deleted" msgstr "Pfad %s wurde umbenannt/gelöscht" -#: builtin/apply.c:3219 builtin/apply.c:3374 +#: builtin/apply.c:3237 builtin/apply.c:3392 #, c-format msgid "%s: does not exist in index" msgstr "%s ist nicht bereitgestellt" -#: builtin/apply.c:3223 builtin/apply.c:3366 builtin/apply.c:3388 +#: builtin/apply.c:3241 builtin/apply.c:3384 builtin/apply.c:3406 #, c-format msgid "%s: %s" msgstr "%s: %s" -#: builtin/apply.c:3228 builtin/apply.c:3382 +#: builtin/apply.c:3246 builtin/apply.c:3400 #, c-format msgid "%s: does not match index" msgstr "%s entspricht nicht der Bereitstellung" -#: builtin/apply.c:3330 +#: builtin/apply.c:3348 msgid "removal patch leaves file contents" msgstr "Lösch-Patch hinterlässt Dateiinhalte" -#: builtin/apply.c:3399 +#: builtin/apply.c:3417 #, c-format msgid "%s: wrong type" msgstr "%s: falscher Typ" -#: builtin/apply.c:3401 +#: builtin/apply.c:3419 #, c-format msgid "%s has type %o, expected %o" msgstr "%s ist vom Typ %o, erwartete %o" -#: builtin/apply.c:3502 +#: builtin/apply.c:3520 #, c-format msgid "%s: already exists in index" msgstr "%s ist bereits bereitgestellt" -#: builtin/apply.c:3505 +#: builtin/apply.c:3523 #, c-format msgid "%s: already exists in working directory" msgstr "%s existiert bereits im Arbeitsverzeichnis" -#: builtin/apply.c:3525 +#: builtin/apply.c:3543 #, c-format msgid "new mode (%o) of %s does not match old mode (%o)" msgstr "neuer Modus (%o) von %s entspricht nicht dem alten Modus (%o)" -#: builtin/apply.c:3530 +#: builtin/apply.c:3548 #, c-format msgid "new mode (%o) of %s does not match old mode (%o) of %s" msgstr "neuer Modus (%o) von %s entspricht nicht dem alten Modus (%o) von %s" -#: builtin/apply.c:3538 +#: builtin/apply.c:3556 #, c-format msgid "%s: patch does not apply" msgstr "%s: Patch konnte nicht angewendet werden" -#: builtin/apply.c:3551 +#: builtin/apply.c:3569 #, c-format msgid "Checking patch %s..." msgstr "Prüfe Patch %s..." -#: builtin/apply.c:3606 builtin/checkout.c:213 builtin/reset.c:158 +#: builtin/apply.c:3624 builtin/checkout.c:215 builtin/reset.c:158 #, c-format msgid "make_cache_entry failed for path '%s'" msgstr "make_cache_entry für Pfad '%s' fehlgeschlagen" -#: builtin/apply.c:3749 +#: builtin/apply.c:3767 #, c-format msgid "unable to remove %s from index" msgstr "konnte %s nicht aus der Bereitstellung entfernen" -#: builtin/apply.c:3777 +#: builtin/apply.c:3795 #, c-format msgid "corrupt patch for subproject %s" msgstr "fehlerhafter Patch für Unterprojekt %s" -#: builtin/apply.c:3781 +#: builtin/apply.c:3799 #, c-format msgid "unable to stat newly created file '%s'" msgstr "konnte neu erstellte Datei '%s' nicht lesen" -#: builtin/apply.c:3786 +#: builtin/apply.c:3804 #, c-format msgid "unable to create backing store for newly created file %s" msgstr "kann internen Speicher für eben erstellte Datei %s nicht erzeugen" -#: builtin/apply.c:3789 builtin/apply.c:3897 +#: builtin/apply.c:3807 builtin/apply.c:3915 #, c-format msgid "unable to add cache entry for %s" msgstr "kann für %s keinen Eintrag in den Zwischenspeicher hinzufügen" -#: builtin/apply.c:3822 +#: builtin/apply.c:3840 #, c-format msgid "closing file '%s'" msgstr "schließe Datei '%s'" -#: builtin/apply.c:3871 +#: builtin/apply.c:3889 #, c-format msgid "unable to write file '%s' mode %o" msgstr "konnte Datei '%s' mit Modus %o nicht schreiben" -#: builtin/apply.c:3958 +#: builtin/apply.c:3976 #, c-format msgid "Applied patch %s cleanly." msgstr "Patch %s sauber angewendet" -#: builtin/apply.c:3966 +#: builtin/apply.c:3984 msgid "internal error" msgstr "interner Fehler" #. Say this even without --verbose -#: builtin/apply.c:3969 +#: builtin/apply.c:3987 #, c-format msgid "Applying patch %%s with %d reject..." msgid_plural "Applying patch %%s with %d rejects..." msgstr[0] "Wende Patch %%s mit %d Zurückweisung an..." msgstr[1] "Wende Patch %%s mit %d Zurückweisungen an..." -#: builtin/apply.c:3979 +#: builtin/apply.c:3997 #, c-format msgid "truncating .rej filename to %.*s.rej" msgstr "Verkürze Name von .rej Datei zu %.*s.rej" -#: builtin/apply.c:4000 +#: builtin/apply.c:4018 #, c-format msgid "Hunk #%d applied cleanly." msgstr "Patch-Bereich #%d sauber angewendet." -#: builtin/apply.c:4003 +#: builtin/apply.c:4021 #, c-format msgid "Rejected hunk #%d." msgstr "Patch-Bereich #%d zurückgewiesen." -#: builtin/apply.c:4153 +#: builtin/apply.c:4171 msgid "unrecognized input" msgstr "nicht erkannte Eingabe" -#: builtin/apply.c:4164 +#: builtin/apply.c:4182 msgid "unable to read index file" msgstr "Konnte Bereitstellungsdatei nicht lesen" -#: builtin/apply.c:4283 builtin/apply.c:4286 +#: builtin/apply.c:4301 builtin/apply.c:4304 builtin/clone.c:91 +#: builtin/fetch.c:63 msgid "path" msgstr "Pfad" -#: builtin/apply.c:4284 +#: builtin/apply.c:4302 msgid "don't apply changes matching the given path" msgstr "wendet keine Änderungen im angegebenen Pfad an" -#: builtin/apply.c:4287 +#: builtin/apply.c:4305 msgid "apply changes matching the given path" msgstr "wendet Änderungen nur im angegebenen Pfad an" -#: builtin/apply.c:4289 +#: builtin/apply.c:4307 msgid "num" msgstr "Anzahl" -#: builtin/apply.c:4290 +#: builtin/apply.c:4308 msgid "remove leading slashes from traditional diff paths" msgstr "" "entfernt vorangestellte Schrägstriche von herkömmlichen " "Differenzpfaden" -#: builtin/apply.c:4293 +#: builtin/apply.c:4311 msgid "ignore additions made by the patch" msgstr "ignoriert hinzugefügte Zeilen des Patches" -#: builtin/apply.c:4295 +#: builtin/apply.c:4313 msgid "instead of applying the patch, output diffstat for the input" msgstr "" "anstatt der Anwendung des Patches, wird der \"diffstat\" für die Eingabe " "ausgegeben" -#: builtin/apply.c:4299 -msgid "shows number of added and deleted lines in decimal notation" +#: builtin/apply.c:4317 +msgid "show number of added and deleted lines in decimal notation" msgstr "" "zeigt die Anzahl von hinzugefügten/entfernten Zeilen in Dezimalnotation" -#: builtin/apply.c:4301 +#: builtin/apply.c:4319 msgid "instead of applying the patch, output a summary for the input" msgstr "" "anstatt der Anwendung des Patches, wird eine Zusammenfassung für die Eingabe " "ausgegeben" -#: builtin/apply.c:4303 +#: builtin/apply.c:4321 msgid "instead of applying the patch, see if the patch is applicable" msgstr "" "anstatt der Anwendung des Patches, zeige ob Patch angewendet werden kann" -#: builtin/apply.c:4305 +#: builtin/apply.c:4323 msgid "make sure the patch is applicable to the current index" msgstr "" "stellt sicher, dass der Patch in der aktuellen Bereitstellung angewendet " "werden kann" -#: builtin/apply.c:4307 +#: builtin/apply.c:4325 msgid "apply a patch without touching the working tree" msgstr "wendet einen Patch an, ohne Änderungen im Arbeitszweig vorzunehmen" -#: builtin/apply.c:4309 +#: builtin/apply.c:4327 msgid "also apply the patch (use with --stat/--summary/--check)" msgstr "wendet den Patch an (Benutzung mit --stat/--summary/--check)" -#: builtin/apply.c:4311 +#: builtin/apply.c:4329 msgid "attempt three-way merge if a patch does not apply" msgstr "" "versucht 3-Wege-Zusammenführung, wenn der Patch nicht angewendet werden " "konnte" -#: builtin/apply.c:4313 +#: builtin/apply.c:4331 msgid "build a temporary index based on embedded index information" msgstr "" "erstellt eine temporäre Bereitstellung basierend auf den integrierten " "Bereitstellungsinformationen" -#: builtin/apply.c:4315 +#: builtin/apply.c:4333 builtin/checkout-index.c:197 builtin/ls-files.c:460 msgid "paths are separated with NUL character" msgstr "Pfade sind getrennt durch NUL Zeichen" -#: builtin/apply.c:4318 +#: builtin/apply.c:4336 msgid "ensure at least lines of context match" -msgstr "" -"stellt sicher, dass mindestens Zeilen des Kontextes übereinstimmen" +msgstr "stellt sicher, dass mindestens Zeilen des Kontextes übereinstimmen" -#: builtin/apply.c:4319 +#: builtin/apply.c:4337 msgid "action" msgstr "Aktion" -#: builtin/apply.c:4320 +#: builtin/apply.c:4338 msgid "detect new or modified lines that have whitespace errors" msgstr "ermittelt neue oder geänderte Zeilen die Fehler in Leerzeichen haben" -#: builtin/apply.c:4323 builtin/apply.c:4326 +#: builtin/apply.c:4341 builtin/apply.c:4344 msgid "ignore changes in whitespace when finding context" msgstr "ignoriert Änderungen in Leerzeichen bei der Suche des Kontextes" -#: builtin/apply.c:4329 +#: builtin/apply.c:4347 msgid "apply the patch in reverse" msgstr "wendet den Patch in umgekehrter Reihenfolge an" -#: builtin/apply.c:4331 +#: builtin/apply.c:4349 msgid "don't expect at least one line of context" msgstr "erwartet keinen Kontext" -#: builtin/apply.c:4333 +#: builtin/apply.c:4351 msgid "leave the rejected hunks in corresponding *.rej files" msgstr "" "hinterlässt zurückgewiesene Patch-Bereiche in den entsprechenden *.rej " "Dateien" -#: builtin/apply.c:4335 +#: builtin/apply.c:4353 msgid "allow overlapping hunks" msgstr "erlaubt sich überlappende Patch-Bereiche" -#: builtin/apply.c:4336 -msgid "be verbose" -msgstr "erweiterte Ausgaben" - -#: builtin/apply.c:4338 +#: builtin/apply.c:4356 msgid "tolerate incorrectly detected missing new-line at the end of file" msgstr "toleriert fehlerhaft erkannten fehlenden Zeilenumbruch am Dateiende" -#: builtin/apply.c:4341 +#: builtin/apply.c:4359 msgid "do not trust the line counts in the hunk headers" msgstr "vertraut nicht den Zeilennummern im Kopf des Patch-Bereiches" -#: builtin/apply.c:4343 +#: builtin/apply.c:4361 msgid "root" msgstr "Wurzelverzeichnis" -#: builtin/apply.c:4344 +#: builtin/apply.c:4362 msgid "prepend to all filenames" msgstr "stellt vor alle Dateinamen" -#: builtin/apply.c:4366 +#: builtin/apply.c:4384 msgid "--3way outside a repository" msgstr "--3way außerhalb eines Projektarchivs" -#: builtin/apply.c:4374 +#: builtin/apply.c:4392 msgid "--index outside a repository" msgstr "--index außerhalb eines Projektarchivs" -#: builtin/apply.c:4377 +#: builtin/apply.c:4395 msgid "--cached outside a repository" msgstr "--cached außerhalb eines Projektarchivs" -#: builtin/apply.c:4393 +#: builtin/apply.c:4411 #, c-format msgid "can't open patch '%s'" msgstr "kann Patch '%s' nicht öffnen" -#: builtin/apply.c:4407 +#: builtin/apply.c:4425 #, c-format msgid "squelched %d whitespace error" msgid_plural "squelched %d whitespace errors" msgstr[0] "unterdrückte %d Fehler in Leerzeichen" msgstr[1] "unterdrückte %d Fehler in Leerzeichen" -#: builtin/apply.c:4413 builtin/apply.c:4423 +#: builtin/apply.c:4431 builtin/apply.c:4441 #, c-format msgid "%d line adds whitespace errors." msgid_plural "%d lines add whitespace errors." @@ -1935,7 +2109,136 @@ msgstr "git archive: Protokollfehler" msgid "git archive: expected a flush" msgstr "git archive: erwartete eine Spülung (flush)" -#: builtin/branch.c:144 +#: builtin/bisect--helper.c:7 +msgid "git bisect--helper --next-all [--no-checkout]" +msgstr "git bisect--helper --next-all [--no-checkout]" + +#: builtin/bisect--helper.c:17 +msgid "perform 'git bisect next'" +msgstr "führt 'git bisect next' aus" + +#: builtin/bisect--helper.c:19 +msgid "update BISECT_HEAD instead of checking out the current commit" +msgstr "aktualisiert BISECT_HEAD, anstatt die aktuelle Version auszuchecken" + +#: builtin/blame.c:25 +msgid "git blame [options] [rev-opts] [rev] [--] file" +msgstr "git blame [Optionen] [rev-opts] [rev] [--] Datei" + +#: builtin/blame.c:30 builtin/shortlog.c:15 +msgid "[rev-opts] are documented in git-rev-list(1)" +msgstr "[rev-opts] sind dokumentiert in git-rev-list(1)" + +#: builtin/blame.c:2374 +msgid "Show blame entries as we find them, incrementally" +msgstr "Zeigt \"blame\"-Einträge schrittweise, während wir sie generieren" + +#: builtin/blame.c:2375 +msgid "Show blank SHA-1 for boundary commits (Default: off)" +msgstr "Zeigt leere SHA-1 für Grenzversionen (Standard: aus)" + +#: builtin/blame.c:2376 +msgid "Do not treat root commits as boundaries (Default: off)" +msgstr "Behandelt Ursprungsversionen nicht als Grenzen (Standard: aus)" + +#: builtin/blame.c:2377 +msgid "Show work cost statistics" +msgstr "Zeigt Statistiken zum Arbeitsaufwand" + +#: builtin/blame.c:2378 +msgid "Show output score for blame entries" +msgstr "Zeigt Ausgabebewertung für \"blame\"-Einträge" + +#: builtin/blame.c:2379 +msgid "Show original filename (Default: auto)" +msgstr "Zeigt ursprünglichen Dateinamen (Standard: auto)" + +#: builtin/blame.c:2380 +msgid "Show original linenumber (Default: off)" +msgstr "Zeigt ursprüngliche Zeilennummer (Standard: aus)" + +#: builtin/blame.c:2381 +msgid "Show in a format designed for machine consumption" +msgstr "Anzeige in einem Format für maschinelle Auswertung" + +#: builtin/blame.c:2382 +msgid "Show porcelain format with per-line commit information" +msgstr "" +"Anzeige in Format für Fremdprogramme mit Versionsinformationen pro Zeile" + +#: builtin/blame.c:2383 +msgid "Use the same output mode as git-annotate (Default: off)" +msgstr "Benutzt den gleichen Ausgabemodus wie \"git-annotate\" (Standard: aus)" + +#: builtin/blame.c:2384 +msgid "Show raw timestamp (Default: off)" +msgstr "Zeigt unbearbeiteten Zeitstempel (Standard: aus)" + +#: builtin/blame.c:2385 +msgid "Show long commit SHA1 (Default: off)" +msgstr "Zeigt langen Versions-SHA1 (Standard: aus)" + +#: builtin/blame.c:2386 +msgid "Suppress author name and timestamp (Default: off)" +msgstr "Unterdrückt den Namen des Autors und den Zeitstempel (Standard: aus)" + +#: builtin/blame.c:2387 +msgid "Show author email instead of name (Default: off)" +msgstr "Zeigt anstatt des Namens die Email-Adresse des Autors (Standard: aus)" + +#: builtin/blame.c:2388 +msgid "Ignore whitespace differences" +msgstr "Ignoriert Unterschiede in Leerzeichen" + +#: builtin/blame.c:2389 +msgid "Spend extra cycles to find better match" +msgstr "arbeite länger, um bessere Übereinstimmungen zu finden" + +#: builtin/blame.c:2390 +msgid "Use revisions from instead of calling git-rev-list" +msgstr "Benutzt Revisionen von anstatt \"git-rev-list\" aufzurufen" + +#: builtin/blame.c:2391 +msgid "Use 's contents as the final image" +msgstr "Benutzt Inhalte der en als entgültiges Abbild" + +#: builtin/blame.c:2392 builtin/blame.c:2393 +msgid "score" +msgstr "Bewertung" + +#: builtin/blame.c:2392 +msgid "Find line copies within and across files" +msgstr "Findet kopierte Zeilen innerhalb oder zwischen Dateien" + +#: builtin/blame.c:2393 +msgid "Find line movements within and across files" +msgstr "Findet verschobene Zeilen innerhalb oder zwischen Dateien" + +#: builtin/blame.c:2394 +msgid "n,m" +msgstr "n,m" + +#: builtin/blame.c:2394 +msgid "Process only line range n,m, counting from 1" +msgstr "Verarbeitet nur Zeilen im Bereich n,m, gezählt von 1" + +#: builtin/branch.c:23 +msgid "git branch [options] [-r | -a] [--merged | --no-merged]" +msgstr "git branch [Optionen] [-r | -a] [--merged | --no-merged]" + +#: builtin/branch.c:24 +msgid "git branch [options] [-l] [-f] []" +msgstr "git branch [Optionen] [-l] [-f] []" + +#: builtin/branch.c:25 +msgid "git branch [options] [-r] (-d | -D) ..." +msgstr "git branch [Optionen] [-r] (-d | -D) ..." + +#: builtin/branch.c:26 +msgid "git branch [options] (-m | -M) [] " +msgstr "git branch [Optionen] (-m | -M) [] " + +#: builtin/branch.c:145 #, c-format msgid "" "deleting branch '%s' that has been merged to\n" @@ -1945,7 +2248,7 @@ msgstr "" " '%s', aber noch nicht mit der Zweigspitze (HEAD) zusammengeführt " "wurde." -#: builtin/branch.c:148 +#: builtin/branch.c:149 #, c-format msgid "" "not deleting branch '%s' that is not yet merged to\n" @@ -1954,169 +2257,300 @@ msgstr "" "entferne Zweig '%s' nicht, der noch nicht zusammengeführt wurde mit\n" " '%s', obwohl er mit der Zweigspitze (HEAD) zusammengeführt wurde." +#: builtin/branch.c:163 +#, c-format +msgid "Couldn't look up commit object for '%s'" +msgstr "Konnte Versionsobjekt für '%s' nicht nachschlagen." + +#: 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 "" +"Der Zweig '%s' ist nicht vollständig zusammengeführt.\n" +"Wenn Sie sicher sind diesen Zweig zu entfernen, führen Sie 'git branch -D %s' aus." + #: builtin/branch.c:180 +msgid "Update of config-file failed" +msgstr "Aktualisierung der Konfigurationsdatei fehlgeschlagen." + +#: builtin/branch.c:208 msgid "cannot use -a with -d" msgstr "kann -a nicht mit -d benutzen" -#: builtin/branch.c:186 +#: builtin/branch.c:214 msgid "Couldn't look up commit object for HEAD" msgstr "Konnte Versionsobjekt für Zweigspitze (HEAD) nicht nachschlagen." -#: builtin/branch.c:191 +#: builtin/branch.c:222 #, c-format msgid "Cannot delete the branch '%s' which you are currently on." msgstr "" -"Kann Zweig '%s' nicht entfernen, da du dich gerade auf diesem befindest." +"Kann Zweig '%s' nicht entfernen, da Sie sich gerade auf diesem befinden." -#: builtin/branch.c:202 +#: builtin/branch.c:235 #, c-format msgid "remote branch '%s' not found." msgstr "externer Zweig '%s' nicht gefunden" -#: builtin/branch.c:203 +#: builtin/branch.c:236 #, c-format msgid "branch '%s' not found." msgstr "Zweig '%s' nicht gefunden." -#: builtin/branch.c:210 -#, c-format -msgid "Couldn't look up commit object for '%s'" -msgstr "Konnte Versionsobjekt für '%s' nicht nachschlagen." - -#: builtin/branch.c:216 -#, 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 "" -"Der Zweig '%s' ist nicht vollständig zusammengeführt.\n" -"Wenn du sicher bist diesen Zweig zu entfernen, führe 'git branch -D %s' aus." - -#: builtin/branch.c:225 +#: builtin/branch.c:250 #, c-format msgid "Error deleting remote branch '%s'" msgstr "Fehler beim Entfernen des externen Zweiges '%s'" -#: builtin/branch.c:226 +#: builtin/branch.c:251 #, c-format msgid "Error deleting branch '%s'" msgstr "Fehler beim Entfernen des Zweiges '%s'" -#: builtin/branch.c:233 +#: builtin/branch.c:258 #, c-format msgid "Deleted remote branch %s (was %s).\n" msgstr "Externer Zweig %s entfernt (war %s).\n" -#: builtin/branch.c:234 +#: builtin/branch.c:259 #, c-format msgid "Deleted branch %s (was %s).\n" msgstr "Zweig %s entfernt (war %s).\n" -#: builtin/branch.c:239 -msgid "Update of config-file failed" -msgstr "Aktualisierung der Konfigurationsdatei fehlgeschlagen." - -#: builtin/branch.c:337 +#: builtin/branch.c:361 #, c-format msgid "branch '%s' does not point at a commit" msgstr "Zweig '%s' zeigt auf keine Version" -#: builtin/branch.c:409 +#: builtin/branch.c:433 #, c-format msgid "[%s: behind %d]" msgstr "[%s: %d hinterher]" -#: builtin/branch.c:411 +#: builtin/branch.c:435 #, c-format msgid "[behind %d]" msgstr "[%d hinterher]" -#: builtin/branch.c:415 +#: builtin/branch.c:439 #, c-format msgid "[%s: ahead %d]" msgstr "[%s: %d voraus]" -#: builtin/branch.c:417 +#: builtin/branch.c:441 #, c-format msgid "[ahead %d]" msgstr "[%d voraus]" -#: builtin/branch.c:420 +#: builtin/branch.c:444 #, c-format msgid "[%s: ahead %d, behind %d]" msgstr "[%s: %d voraus, %d hinterher]" -#: builtin/branch.c:423 +#: builtin/branch.c:447 #, c-format msgid "[ahead %d, behind %d]" msgstr "[%d voraus, %d hinterher]" -#: builtin/branch.c:535 +#: builtin/branch.c:560 msgid "(no branch)" msgstr "(kein Zweig)" -#: builtin/branch.c:600 +#: builtin/branch.c:625 msgid "some refs could not be read" msgstr "Konnte einige Referenzen nicht lesen" -#: builtin/branch.c:613 +#: builtin/branch.c:638 msgid "cannot rename the current branch while not on any." msgstr "" -"Kann aktuellen Zweig nicht umbenennen, solange du dich auf keinem befindest." +"Kann aktuellen Zweig nicht umbenennen, solange Sie sich auf keinem befinden." -#: builtin/branch.c:623 +#: builtin/branch.c:648 #, c-format msgid "Invalid branch name: '%s'" msgstr "Ungültiger Zweig-Name: '%s'" -#: builtin/branch.c:638 +#: builtin/branch.c:663 msgid "Branch rename failed" msgstr "Umbenennung des Zweiges fehlgeschlagen" -#: builtin/branch.c:642 +#: builtin/branch.c:667 #, c-format msgid "Renamed a misnamed branch '%s' away" msgstr "falsch benannten Zweig '%s' umbenannt" -#: builtin/branch.c:646 +#: builtin/branch.c:671 #, c-format msgid "Branch renamed to %s, but HEAD is not updated!" msgstr "Zweig umbenannt zu %s, aber Zweigspitze (HEAD) ist nicht aktualisiert!" -#: builtin/branch.c:653 +#: builtin/branch.c:678 msgid "Branch is renamed, but update of config-file failed" msgstr "" "Zweig ist umbenannt, aber die Aktualisierung der Konfigurationsdatei ist " "fehlgeschlagen." -#: builtin/branch.c:668 +#: builtin/branch.c:693 #, c-format msgid "malformed object name %s" msgstr "Missgebildeter Objektname %s" -#: builtin/branch.c:692 +#: builtin/branch.c:717 #, c-format msgid "could not write branch description template: %s" msgstr "Konnte Beschreibungsvorlage für Zweig nicht schreiben: %s" -#: builtin/branch.c:783 +#: builtin/branch.c:747 +msgid "Generic options" +msgstr "Allgemeine Optionen" + +#: builtin/branch.c:749 +msgid "show hash and subject, give twice for upstream branch" +msgstr "Zeigt Hash und Betreff; -vv: zusätzlich externen Übernahmezweig" + +#: builtin/branch.c:750 +msgid "suppress informational messages" +msgstr "unterdrückt Informationsmeldungen" + +#: builtin/branch.c:751 +msgid "set up tracking mode (see git-pull(1))" +msgstr "stellt den Übernahmemodus ein (siehe git-pull(1))" + +#: builtin/branch.c:753 +msgid "change upstream info" +msgstr "ändert Informationen zum externen Übernahmezweig" + +#: builtin/branch.c:757 +msgid "use colored output" +msgstr "verwendet farbliche Ausgaben" + +#: builtin/branch.c:758 +msgid "act on remote-tracking branches" +msgstr "wirkt auf externe Übernahmezweige" + +#: 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 "Version" + +#: builtin/branch.c:762 builtin/branch.c:768 +msgid "print only branches that contain the commit" +msgstr "gibt nur Zweige aus, welche diese Version beinhalten" + +#: builtin/branch.c:774 +msgid "Specific git-branch actions:" +msgstr "spezifische Aktionen für \"git-branch\":" + +#: builtin/branch.c:775 +msgid "list both remote-tracking and local branches" +msgstr "listet externe Übernahmezweige und lokale Zweige auf" + +#: builtin/branch.c:777 +msgid "delete fully merged branch" +msgstr "entfernt vollständig zusammengeführten Zweig" + +#: builtin/branch.c:778 +msgid "delete branch (even if not merged)" +msgstr "löscht Zweig (auch wenn nicht zusammengeführt)" + +#: builtin/branch.c:779 +msgid "move/rename a branch and its reflog" +msgstr "verschiebt/benennt einen Zweig und dessen Referenzprotokoll um" + +#: builtin/branch.c:780 +msgid "move/rename a branch, even if target exists" +msgstr "" +"verschiebt/benennt einen Zweig um, auch wenn das Ziel bereits existiert" + +#: builtin/branch.c:781 +msgid "list branch names" +msgstr "listet Zweignamen auf" + +#: builtin/branch.c:782 +msgid "create the branch's reflog" +msgstr "erzeugt das Referenzprotokoll des Zweiges" + +#: builtin/branch.c:784 +msgid "edit the description for the branch" +msgstr "bearbeitet die Beschreibung für den Zweig" + +#: builtin/branch.c:785 +msgid "force creation (when already exists)" +msgstr "erzeuge auch, wenn der Zweig bereits existiert" + +#: builtin/branch.c:788 +msgid "print only not merged branches" +msgstr "gibt nur Zweige aus, die nicht zusammengeführt sind" + +#: builtin/branch.c:794 +msgid "print only merged branches" +msgstr "gibt nur Zweige aus, die zusammengeführt sind" + +#: builtin/branch.c:798 +msgid "list branches in columns" +msgstr "listet Zweige in Spalten auf" + +#: builtin/branch.c:811 msgid "Failed to resolve HEAD as a valid ref." msgstr "Konnte Zweigspitze (HEAD) nicht als gültige Referenz auflösen." -#: builtin/branch.c:788 builtin/clone.c:561 +#: builtin/branch.c:816 builtin/clone.c:561 msgid "HEAD not found below refs/heads!" msgstr "Zweigspitze (HEAD) wurde nicht unter \"refs/heads\" gefunden!" -#: builtin/branch.c:808 +#: builtin/branch.c:836 msgid "--column and --verbose are incompatible" msgstr "--column und --verbose sind inkompatibel" -#: builtin/branch.c:857 +#: builtin/branch.c:887 +#, c-format +msgid "branch '%s' does not exist" +msgstr "Zweig '%s' existiert nicht" + +#: builtin/branch.c:899 +#, c-format +msgid "Branch '%s' has no upstream information" +msgstr "Zweig '%s' hat keinen externen Übernahmezweig gesetzt" + +#: builtin/branch.c:914 msgid "-a and -r options to 'git branch' do not make sense with a branch name" msgstr "" "Die Optionen -a und -r bei 'git branch' machen mit einem Zweignamen keinen " "Sinn." +#: 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 "" +"Die --set-upstream Option ist veraltet und wird entfernt. Benutzen Sie " +"--track oder --set-upstream-to\n" + +#: builtin/branch.c:934 +#, c-format +msgid "" +"\n" +"If you wanted to make '%s' track '%s', do this:\n" +"\n" +msgstr "" +"\n" +"Wenn Sie wollten, dass '%s' den Zweig '%s' als externen Übernahmezweig hat, " +"führen Sie aus:\n" + +#: builtin/branch.c:935 +#, c-format +msgid " git branch -d %s\n" +msgstr " git branch -d %s\n" + +#: builtin/branch.c:936 +#, c-format +msgid " git branch --set-upstream-to %s\n" +msgstr " git branch --set-upstream-to %s\n" + #: builtin/bundle.c:47 #, c-format msgid "%s is okay\n" @@ -2130,95 +2564,228 @@ msgstr "Um ein Paket zu erstellen wird ein Projektarchiv benötigt." msgid "Need a repository to unbundle." msgstr "Zum Entpacken wird ein Projektarchiv benötigt." -#: builtin/checkout.c:114 builtin/checkout.c:147 -#, c-format -msgid "path '%s' does not have our version" -msgstr "Pfad '%s' hat nicht unsere Version." +#: builtin/cat-file.c:247 +msgid "git cat-file (-t|-s|-e|-p||--textconv) " +msgstr "git cat-file (-t|-s|-e|-p||--textconv) " -#: builtin/checkout.c:116 builtin/checkout.c:149 -#, c-format -msgid "path '%s' does not have their version" -msgstr "Pfad '%s' hat nicht deren Version." +#: builtin/cat-file.c:248 +msgid "git cat-file (--batch|--batch-check) < " +msgstr "git cat-file (--batch|--batch-check) < " -#: builtin/checkout.c:132 -#, c-format -msgid "path '%s' does not have all necessary versions" -msgstr "Pfad '%s' hat nicht alle notwendigen Versionen." +#: builtin/cat-file.c:266 +msgid " can be one of: blob, tree, commit, tag" +msgstr " kann sein: blob, tree, commit, tag" -#: builtin/checkout.c:176 -#, c-format -msgid "path '%s' does not have necessary versions" -msgstr "Pfad '%s' hat nicht die notwendigen Versionen." +#: builtin/cat-file.c:267 +msgid "show object type" +msgstr "zeigt Objektart" -#: builtin/checkout.c:193 -#, c-format -msgid "path '%s': cannot merge" -msgstr "Pfad '%s': kann nicht zusammenführen" +#: builtin/cat-file.c:268 +msgid "show object size" +msgstr "zeigt Objektgröße" -#: builtin/checkout.c:210 -#, c-format -msgid "Unable to add merge result for '%s'" -msgstr "Konnte Ergebnis der Zusammenführung von '%s' nicht hinzufügen." +#: builtin/cat-file.c:270 +msgid "exit with zero when there's no error" +msgstr "beendet mit Rückgabewert 0, wenn kein Fehler aufgetreten ist" -#: builtin/checkout.c:235 builtin/checkout.c:392 -msgid "corrupt index file" -msgstr "beschädigte Bereitstellungsdatei" +#: builtin/cat-file.c:271 +msgid "pretty-print object's content" +msgstr "ansprechende Anzeige des Objektinhaltes" -#: builtin/checkout.c:265 builtin/checkout.c:272 -#, c-format -msgid "path '%s' is unmerged" -msgstr "Pfad '%s' ist nicht zusammengeführt." +#: builtin/cat-file.c:273 +msgid "for blob objects, run textconv on object's content" +msgstr "führt eine Textkonvertierung auf den Inhalt von Blob-Objekten aus" -#: builtin/checkout.c:303 builtin/checkout.c:498 builtin/clone.c:586 -#: builtin/merge.c:811 -msgid "unable to write new index file" -msgstr "Konnte neue Bereitstellungsdatei nicht schreiben." +#: builtin/cat-file.c:275 +msgid "show info and content of objects fed from the standard input" +msgstr "" +"Anzeige von Informationen und Inhalt von Objekten, gelesen von der Standard-" +"Eingabe" -#: builtin/checkout.c:414 -msgid "you need to resolve your current index first" -msgstr "Du musst zuerst deine aktuelle Bereitstellung auflösen." +#: builtin/cat-file.c:278 +msgid "show info about objects fed from the standard input" +msgstr "" +"Anzeige von Informationen über Objekte, gelesen von der Standard-Eingabe" -#: builtin/checkout.c:533 -#, c-format -msgid "Can not do reflog for '%s'\n" +#: builtin/check-attr.c:11 +msgid "git check-attr [-a | --all | attr...] [--] pathname..." +msgstr "git check-attr [-a | --all | Attribut...] [--] Pfadname..." + +#: builtin/check-attr.c:12 +msgid "git check-attr --stdin [-z] [-a | --all | attr...] < " +msgstr "git check-attr --stdin [-z] [-a | --all | Attribut...] < " + +#: builtin/check-attr.c:19 +msgid "report all attributes set on file" +msgstr "gibt alle Attribute einer Datei aus" + +#: builtin/check-attr.c:20 +msgid "use .gitattributes only from the index" +msgstr "verwendet .gitattributes nur von der Bereitstellung" + +#: builtin/check-attr.c:21 builtin/hash-object.c:75 +msgid "read file names from stdin" +msgstr "liest Dateinamen von der Standard-Eingabe" + +#: builtin/check-attr.c:23 +msgid "input paths are terminated by a null character" +msgstr "Eingabepfade sind durch ein NUL Zeichen abgeschlossen" + +#: builtin/checkout-index.c:126 +msgid "git checkout-index [options] [--] [...]" +msgstr "git checkout-index [Optionen] [--] [...]" + +#: builtin/checkout-index.c:187 +msgid "check out all files in the index" +msgstr "checkt alle Dateien in der Bereitstellung aus" + +#: builtin/checkout-index.c:188 +msgid "force overwrite of existing files" +msgstr "erzwingt das Überschreiben bereits existierender Dateien" + +#: builtin/checkout-index.c:190 +msgid "no warning for existing files and files not in index" +msgstr "" +"keine Warnung für existierende Dateien, und Dateien, die sich nicht in der " +"Bereitstellung befinden" + +#: builtin/checkout-index.c:192 +msgid "don't checkout new files" +msgstr "checkt keine neuen Dateien aus" + +#: builtin/checkout-index.c:194 +msgid "update stat information in the index file" +msgstr "aktualisiert Dateiinformationen in der Bereitstellungsdatei" + +#: builtin/checkout-index.c:200 +msgid "read list of paths from the standard input" +msgstr "liest eine Liste von Pfaden von der Standard-Eingabe" + +#: builtin/checkout-index.c:202 +msgid "write the content to temporary files" +msgstr "schreibt den Inhalt in temporäre Dateien" + +#: builtin/checkout-index.c:203 builtin/column.c:30 +msgid "string" +msgstr "Zeichenkette" + +#: builtin/checkout-index.c:204 +msgid "when creating files, prepend " +msgstr "" +"wenn Dateien erzeugt werden, stelle dem Dateinamen voran" + +#: builtin/checkout-index.c:207 +msgid "copy out the files from named stage" +msgstr "kopiert Dateien von dem benannten Stand" + +#: builtin/checkout.c:25 +msgid "git checkout [options] " +msgstr "git checkout [Optionen] " + +#: builtin/checkout.c:26 +msgid "git checkout [options] [] -- ..." +msgstr "git checkout [Optionen] [Zweig>] -- ..." + +#: builtin/checkout.c:116 builtin/checkout.c:149 +#, c-format +msgid "path '%s' does not have our version" +msgstr "Pfad '%s' hat nicht unsere Version." + +#: builtin/checkout.c:118 builtin/checkout.c:151 +#, c-format +msgid "path '%s' does not have their version" +msgstr "Pfad '%s' hat nicht deren Version." + +#: builtin/checkout.c:134 +#, c-format +msgid "path '%s' does not have all necessary versions" +msgstr "Pfad '%s' hat nicht alle notwendigen Versionen." + +#: builtin/checkout.c:178 +#, c-format +msgid "path '%s' does not have necessary versions" +msgstr "Pfad '%s' hat nicht die notwendigen Versionen." + +#: builtin/checkout.c:195 +#, c-format +msgid "path '%s': cannot merge" +msgstr "Pfad '%s': kann nicht zusammenführen" + +#: builtin/checkout.c:212 +#, c-format +msgid "Unable to add merge result for '%s'" +msgstr "Konnte Ergebnis der Zusammenführung von '%s' nicht hinzufügen." + +#: builtin/checkout.c:236 builtin/checkout.c:239 builtin/checkout.c:242 +#: builtin/checkout.c:245 +#, c-format +msgid "'%s' cannot be used with updating paths" +msgstr "'%s' kann nicht mit Pfaden benutzt werden" + +#: builtin/checkout.c:248 builtin/checkout.c:251 +#, c-format +msgid "'%s' cannot be used with %s" +msgstr "'%s' kann nicht mit '%s' benutzt werden" + +#: builtin/checkout.c:254 +#, c-format +msgid "Cannot update paths and switch to branch '%s' at the same time." +msgstr "Kann nicht gleichzeitig Pfade aktualisieren und zu Zweig '%s' wechseln" + +#: builtin/checkout.c:265 builtin/checkout.c:426 +msgid "corrupt index file" +msgstr "beschädigte Bereitstellungsdatei" + +#: builtin/checkout.c:295 builtin/checkout.c:302 +#, c-format +msgid "path '%s' is unmerged" +msgstr "Pfad '%s' ist nicht zusammengeführt." + +#: builtin/checkout.c:448 +msgid "you need to resolve your current index first" +msgstr "Sie müssen zuerst Ihre aktuelle Bereitstellung auflösen." + +#: builtin/checkout.c:569 +#, c-format +msgid "Can not do reflog for '%s'\n" msgstr "Konnte \"reflog\" für '%s' nicht durchführen\n" -#: builtin/checkout.c:566 +#: builtin/checkout.c:602 msgid "HEAD is now at" msgstr "Zweigspitze (HEAD) ist jetzt bei" -#: builtin/checkout.c:573 +#: builtin/checkout.c:609 #, c-format msgid "Reset branch '%s'\n" msgstr "Setze Zweig '%s' zurück\n" -#: builtin/checkout.c:576 +#: builtin/checkout.c:612 #, c-format msgid "Already on '%s'\n" msgstr "Bereits auf '%s'\n" -#: builtin/checkout.c:580 +#: builtin/checkout.c:616 #, c-format msgid "Switched to and reset branch '%s'\n" msgstr "Gewechselt zu zurückgesetztem Zweig '%s'\n" -#: builtin/checkout.c:582 +#: builtin/checkout.c:618 builtin/checkout.c:955 #, c-format msgid "Switched to a new branch '%s'\n" msgstr "Gewechselt zu einem neuen Zweig '%s'\n" -#: builtin/checkout.c:584 +#: builtin/checkout.c:620 #, c-format msgid "Switched to branch '%s'\n" msgstr "Gewechselt zu Zweig '%s'\n" -#: builtin/checkout.c:640 +#: builtin/checkout.c:676 #, c-format msgid " ... and %d more.\n" msgstr " ... und %d weitere.\n" #. The singular version -#: builtin/checkout.c:646 +#: builtin/checkout.c:682 #, c-format msgid "" "Warning: you are leaving %d commit behind, not connected to\n" @@ -2231,17 +2798,17 @@ msgid_plural "" "\n" "%s\n" msgstr[0] "" -"Warnung: Du bist um %d Version hinterher, nicht verbunden zu\n" -"einem deiner Zweige:\n" +"Warnung: Sie sind um %d Version hinterher, nicht verbunden zu\n" +"einem Ihrer Zweige:\n" "\n" "%s\n" msgstr[1] "" -"Warnung: Du bist um %d Versionen hinterher, nicht verbunden zu\n" -"einem deiner Zweige:\n" +"Warnung: Sie sind um %d Versionen hinterher, nicht verbunden zu\n" +"einem Ihrer Zweige:\n" "\n" "%s\n" -#: builtin/checkout.c:664 +#: builtin/checkout.c:700 #, c-format msgid "" "If you want to keep them by creating a new branch, this may be a good time\n" @@ -2250,112 +2817,196 @@ msgid "" " git branch new_branch_name %s\n" "\n" msgstr "" -"Wenn du diese durch einen neuen Zweig behalten möchtest, dann könnte jetzt\n" +"Wenn Sie diese durch einen neuen Zweig behalten möchten, dann könnte jetzt\n" "ein guter Zeitpunkt sein dies zu tun mit:\n" "\n" " git branch neuer_zweig_name %s\n" "\n" -#: builtin/checkout.c:694 +#: builtin/checkout.c:730 msgid "internal error in revision walk" msgstr "interner Fehler im Revisionsgang" -#: builtin/checkout.c:698 +#: builtin/checkout.c:734 msgid "Previous HEAD position was" msgstr "Vorherige Position der Zweigspitze (HEAD) war" -#: builtin/checkout.c:724 builtin/checkout.c:919 +#: builtin/checkout.c:761 builtin/checkout.c:950 msgid "You are on a branch yet to be born" -msgstr "du bist auf einem Zweig, der noch geboren wird" +msgstr "Sie sind auf einem Zweig, der noch geboren wird" #. case (1) -#: builtin/checkout.c:855 +#: builtin/checkout.c:886 #, c-format msgid "invalid reference: %s" msgstr "Ungültige Referenz: %s" #. case (1): want a tree -#: builtin/checkout.c:894 +#: builtin/checkout.c:925 #, c-format msgid "reference is not a tree: %s" msgstr "Referenz ist kein Baum: %s" -#: builtin/checkout.c:976 -msgid "-B cannot be used with -b" -msgstr "-B kann nicht mit -b benutzt werden" +#: builtin/checkout.c:964 +msgid "paths cannot be used with switching branches" +msgstr "Pfade können nicht mit dem Wechseln von Zweigen benutzt werden" -#: builtin/checkout.c:985 -msgid "--patch is incompatible with all other options" -msgstr "--patch ist inkompatibel mit allen anderen Optionen" +#: builtin/checkout.c:967 builtin/checkout.c:971 +#, c-format +msgid "'%s' cannot be used with switching branches" +msgstr "'%s' kann nicht mit dem Wechseln von Zweigen benutzt werden" -#: builtin/checkout.c:988 -msgid "--detach cannot be used with -b/-B/--orphan" -msgstr "--detach kann nicht mit -b/-B/--orphan benutzt werden" +#: 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 "'%s' kann nicht mit '%s' benutzt werden" -#: builtin/checkout.c:990 -msgid "--detach cannot be used with -t" -msgstr "--detach kann nicht mit -t benutzt werden" +#: builtin/checkout.c:991 +#, c-format +msgid "Cannot switch branch to a non-commit '%s'" +msgstr "Kann Zweig nicht zu Nicht-Version '%s' wechseln" -#: builtin/checkout.c:996 -msgid "--track needs a branch name" -msgstr "--track benötigt einen Zweignamen" +#: builtin/checkout.c:1012 builtin/gc.c:177 +msgid "suppress progress reporting" +msgstr "unterdrückt Fortschrittsanzeige" -#: builtin/checkout.c:1003 -msgid "Missing branch name; try -b" -msgstr "Vermisse Zweignamen; versuche -b" +#: builtin/checkout.c:1013 builtin/checkout.c:1015 builtin/clone.c:89 +#: builtin/remote.c:169 builtin/remote.c:171 +msgid "branch" +msgstr "Zweig" -#: builtin/checkout.c:1009 -msgid "--orphan and -b|-B are mutually exclusive" -msgstr "--orphan und -b|-B sind gegenseitig exklusiv" +#: builtin/checkout.c:1014 +msgid "create and checkout a new branch" +msgstr "erzeugt und checkt einen neuen Zweig aus" -#: builtin/checkout.c:1011 -msgid "--orphan cannot be used with -t" -msgstr "--orphan kann nicht mit -t benutzt werden" +#: builtin/checkout.c:1016 +msgid "create/reset and checkout a branch" +msgstr "erzeugt/setzt neu und checkt einen Zweig aus" + +#: builtin/checkout.c:1017 +msgid "create reflog for new branch" +msgstr "erzeugt Referenzprotokoll für den neuen Zweig" + +#: builtin/checkout.c:1018 +msgid "detach the HEAD at named commit" +msgstr "setzt die Zweigspitze (HEAD) zu benannter Version" + +#: builtin/checkout.c:1019 +msgid "set upstream info for new branch" +msgstr "setzt Informationen zum externen Übernahmezweig für den neuen Zweig" + +#: builtin/checkout.c:1021 +msgid "new branch" +msgstr "neuer Zweig" #: builtin/checkout.c:1021 -msgid "git checkout: -f and -m are incompatible" -msgstr "git checkout: -f und -m sind inkompatibel" +msgid "new unparented branch" +msgstr "neuer Zweig ohne Elternversion" + +#: builtin/checkout.c:1022 +msgid "checkout our version for unmerged files" +msgstr "checkt unsere Variante für nicht zusammengeführte Dateien aus" + +#: builtin/checkout.c:1024 +msgid "checkout their version for unmerged files" +msgstr "checkt ihre Variante für nicht zusammengeführte Dateien aus" + +#: builtin/checkout.c:1026 +msgid "force checkout (throw away local modifications)" +msgstr "erzwingt Auschecken (verwirft lokale Änderungen)" -#: builtin/checkout.c:1055 +#: builtin/checkout.c:1027 +msgid "perform a 3-way merge with the new branch" +msgstr "führt eine 3-Wege-Zusammenführung mit dem neuen Zweig aus" + +#: builtin/checkout.c:1028 builtin/merge.c:215 +msgid "update ignored files (default)" +msgstr "aktualisiert ignorierte Dateien (Standard)" + +#: builtin/checkout.c:1029 builtin/log.c:1116 parse-options.h:241 +msgid "style" +msgstr "Stil" + +#: builtin/checkout.c:1030 +msgid "conflict style (merge or diff3)" +msgstr "Konfliktstil (merge oder diff3)" + +#: builtin/checkout.c:1033 +msgid "second guess 'git checkout no-such-branch'" +msgstr "second guess 'git checkout no-such-branch'" + +#: builtin/checkout.c:1057 +msgid "-b, -B and --orphan are mutually exclusive" +msgstr "-b, -B und --orphan schliessen sich gegenseitig aus" + +#: builtin/checkout.c:1074 +msgid "--track needs a branch name" +msgstr "--track benötigt einen Zweignamen" + +#: builtin/checkout.c:1081 +msgid "Missing branch name; try -b" +msgstr "Vermisse Zweignamen; versuchen Sie -b" + +#: builtin/checkout.c:1116 msgid "invalid path specification" msgstr "ungültige Pfadspezifikation" -#: builtin/checkout.c:1063 +#: builtin/checkout.c:1123 #, c-format msgid "" -"git checkout: updating paths is incompatible with switching branches.\n" +"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 "" -"git checkout: Die Aktualisierung von Pfaden ist inkompatibel mit dem Wechsel " -"von Zweigen.\n" -"Hast du beabsichtigt '%s' auszuchecken, welcher nicht als Version aufgelöst " +"Kann nicht gleichzeitig Pfade aktualisieren und zu Zweig '%s' wechseln.\n" +"Haben Sie beabsichtigt '%s' auszuchecken, welcher nicht als Version aufgelöst " "werden kann?" -#: builtin/checkout.c:1065 -msgid "git checkout: updating paths is incompatible with switching branches." -msgstr "" -"git checkout: Die Aktualisierung von Pfaden ist inkompatibel mit dem Wechsel " -"von Zweigen." - -#: builtin/checkout.c:1070 -msgid "git checkout: --detach does not take a path argument" -msgstr "git checkout: --detach nimmt kein Pfad-Argument" +#: builtin/checkout.c:1128 +#, c-format +msgid "git checkout: --detach does not take a path argument '%s'" +msgstr "git checkout: --detach nimmt kein Pfad-Argument '%s'" -#: builtin/checkout.c:1073 +#: builtin/checkout.c:1132 msgid "" "git checkout: --ours/--theirs, --force and --merge are incompatible when\n" "checking out of the index." msgstr "" "git checkout: --ours/--theirs, --force und --merge sind inkompatibel wenn\n" -"du aus der Bereitstellung auscheckst." +"Sie aus der Bereitstellung auschecken." + +#: builtin/clean.c:19 +msgid "git clean [-d] [-f] [-n] [-q] [-e ] [-x | -X] [--] ..." +msgstr "git clean [-d] [-f] [-n] [-q] [-e ] [-x | -X] [--] ..." -#: builtin/checkout.c:1092 -msgid "Cannot switch branch to a non-commit." -msgstr "Kann Zweig nur zu einer Version wechseln." +#: builtin/clean.c:51 +msgid "do not print names of files removed" +msgstr "gibt keine Namen von gelöschten Dateien aus" -#: builtin/checkout.c:1095 -msgid "--ours/--theirs is incompatible with switching branches." -msgstr "--ours/--theirs ist inkompatibel mit den Wechseln von Zweigen." +#: builtin/clean.c:53 +msgid "force" +msgstr "erzwingt Aktion" + +#: builtin/clean.c:55 +msgid "remove whole directories" +msgstr "löscht ganze Verzeichnisse" + +#: 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 "Muster" + +#: builtin/clean.c:57 +msgid "add to ignore rules" +msgstr "fügt zu den Regeln für ignorierte Pfade hinzu" + +#: builtin/clean.c:58 +msgid "remove ignored files, too" +msgstr "löscht auch ignorierte Dateien" + +#: builtin/clean.c:60 +msgid "remove only ignored files" +msgstr "löscht nur ignorierte Dateien" #: builtin/clean.c:78 msgid "-x and -X cannot be used together" @@ -2379,28 +3030,122 @@ msgstr "" #: builtin/clean.c:155 builtin/clean.c:176 #, c-format msgid "Would remove %s\n" -msgstr "Würde %s entfernen\n" +msgstr "Würde %s löschen\n" #: builtin/clean.c:159 builtin/clean.c:179 #, c-format msgid "Removing %s\n" -msgstr "Entferne %s\n" +msgstr "Lösche %s\n" #: builtin/clean.c:162 builtin/clean.c:182 #, c-format msgid "failed to remove %s" -msgstr "Fehler beim Entfernen von %s" +msgstr "Fehler beim Löschen von %s" #: builtin/clean.c:166 #, c-format msgid "Would not remove %s\n" -msgstr "Würde nicht entfernen %s\n" +msgstr "Würde '%s' nicht löschen\n" #: builtin/clean.c:168 #, c-format msgid "Not removing %s\n" msgstr "Entferne nicht %s\n" +#: builtin/clone.c:36 +msgid "git clone [options] [--] []" +msgstr "git clone [Optionen] [--] []" + +#: builtin/clone.c:64 builtin/fetch.c:82 builtin/merge.c:212 +#: builtin/push.c:399 +msgid "force progress reporting" +msgstr "erzwingt Fortschrittsanzeige" + +#: builtin/clone.c:66 +msgid "don't create a checkout" +msgstr "kein Auschecken" + +#: builtin/clone.c:67 builtin/clone.c:69 builtin/init-db.c:488 +msgid "create a bare repository" +msgstr "erstellt ein bloßes Projektarchiv" + +#: builtin/clone.c:72 +msgid "create a mirror repository (implies bare)" +msgstr "erstellt ein Spiegelarchiv (impliziert bloßes Projektarchiv)" + +#: builtin/clone.c:74 +msgid "to clone from a local repository" +msgstr "um von einem lokalen Projektarchiv zu klonen" + +#: builtin/clone.c:76 +msgid "don't use local hardlinks, always copy" +msgstr "benutzt lokal keine harten Links, immer Kopien" + +#: builtin/clone.c:78 +msgid "setup as shared repository" +msgstr "Einrichtung als verteiltes Projektarchiv" + +#: builtin/clone.c:80 builtin/clone.c:82 +msgid "initialize submodules in the clone" +msgstr "initialisiert Unterprojekte im Klon" + +#: builtin/clone.c:83 builtin/init-db.c:485 +msgid "template-directory" +msgstr "Vorlagenverzeichnis" + +#: builtin/clone.c:84 builtin/init-db.c:486 +msgid "directory from which templates will be used" +msgstr "Verzeichnis, von welchem die Vorlagen verwendet werden" + +#: builtin/clone.c:86 +msgid "reference repository" +msgstr "referenziert Projektarchiv" + +#: builtin/clone.c:87 builtin/column.c:26 builtin/merge-file.c:44 +msgid "name" +msgstr "Name" + +#: builtin/clone.c:88 +msgid "use instead of 'origin' to track upstream" +msgstr "benutzt statt 'origin' für externes Projektarchiv" + +#: builtin/clone.c:90 +msgid "checkout instead of the remote's HEAD" +msgstr "" +"checkt aus, anstatt Zweigspitze (HEAD) des externen Projektarchivs" + +#: builtin/clone.c:92 +msgid "path to git-upload-pack on the remote" +msgstr "Pfad zu \"git-upload-pack\" auf der Gegenseite" + +#: builtin/clone.c:93 builtin/fetch.c:83 builtin/grep.c:662 +msgid "depth" +msgstr "Tiefe" + +#: builtin/clone.c:94 +msgid "create a shallow clone of that depth" +msgstr "erstellt einen flachen Klon mit dieser Tiefe" + +#: builtin/clone.c:96 +msgid "clone only one branch, HEAD or --branch" +msgstr "klont nur einen Zweig, Zweigspitze (HEAD) oder --branch" + +#: builtin/clone.c:97 builtin/init-db.c:494 +msgid "gitdir" +msgstr ".git-Verzeichnis" + +#: builtin/clone.c:98 builtin/init-db.c:495 +msgid "separate git dir from working tree" +msgstr "separiert Git-Verzeichnis vom Arbeitsbaum" + +#: builtin/clone.c:99 +msgid "key=value" +msgstr "Schlüssel=Wert" + +#: builtin/clone.c:100 +msgid "set config inside the new repository" +msgstr "setzt Konfiguration innerhalb des neuen Projektarchivs" + #: builtin/clone.c:243 #, c-format msgid "reference repository '%s' is not a local directory." @@ -2452,77 +3197,113 @@ msgstr "" "Externe Zweigspitze (HEAD) bezieht sich auf eine nicht existierende Referenz " "und kann nicht ausgecheckt werden.\n" -#: builtin/clone.c:642 +#: builtin/clone.c:690 msgid "Too many arguments." msgstr "Zu viele Argumente." -#: builtin/clone.c:646 +#: builtin/clone.c:694 msgid "You must specify a repository to clone." -msgstr "Du musst ein Projektarchiv zum Klonen angeben." +msgstr "Sie müssen ein Projektarchiv zum Klonen angeben." -#: builtin/clone.c:657 +#: builtin/clone.c:705 #, c-format msgid "--bare and --origin %s options are incompatible." msgstr "--bare und --origin %s Optionen sind inkompatibel." -#: builtin/clone.c:671 +#: builtin/clone.c:719 #, c-format msgid "repository '%s' does not exist" msgstr "Projektarchiv '%s' existiert nicht." -#: builtin/clone.c:676 +#: builtin/clone.c:724 msgid "--depth is ignored in local clones; use file:// instead." -msgstr "--depth wird in lokalen Klonen ignoriert; benutze stattdessen file://." +msgstr "--depth wird in lokalen Klonen ignoriert; benutzen Sie stattdessen file://." -#: builtin/clone.c:686 +#: builtin/clone.c:734 #, c-format msgid "destination path '%s' already exists and is not an empty directory." msgstr "Zielpfad '%s' existiert bereits und ist kein leeres Verzeichnis." -#: builtin/clone.c:696 +#: builtin/clone.c:744 #, c-format msgid "working tree '%s' already exists." msgstr "Arbeitsbaum '%s' existiert bereits." -#: builtin/clone.c:709 builtin/clone.c:723 +#: builtin/clone.c:757 builtin/clone.c:771 #, c-format msgid "could not create leading directories of '%s'" msgstr "Konnte führende Verzeichnisse von '%s' nicht erstellen." -#: builtin/clone.c:712 +#: builtin/clone.c:760 #, c-format msgid "could not create work tree dir '%s'." msgstr "Konnte Arbeitsverzeichnis '%s' nicht erstellen." -#: builtin/clone.c:731 +#: builtin/clone.c:779 #, c-format msgid "Cloning into bare repository '%s'...\n" msgstr "Klone in bloßes Projektarchiv '%s'...\n" -#: builtin/clone.c:733 +#: builtin/clone.c:781 #, c-format msgid "Cloning into '%s'...\n" msgstr "Klone nach '%s'...\n" -#: builtin/clone.c:789 +#: builtin/clone.c:823 #, c-format msgid "Don't know how to clone %s" msgstr "Weiß nicht wie %s zu klonen ist." -#: builtin/clone.c:838 +#: builtin/clone.c:872 #, c-format msgid "Remote branch %s not found in upstream %s" msgstr "externer Zweig %s nicht im anderen Projektarchiv %s gefunden" -#: builtin/clone.c:845 +#: builtin/clone.c:879 msgid "You appear to have cloned an empty repository." -msgstr "Du scheinst ein leeres Projektarchiv geklont zu haben." +msgstr "Sie scheinen ein leeres Projektarchiv geklont zu haben." + +#: builtin/column.c:9 +msgid "git column [options]" +msgstr "git column [Optionen]" + +#: builtin/column.c:26 +msgid "lookup config vars" +msgstr "schlägt Konfigurationsvariablen nach" + +#: builtin/column.c:27 builtin/column.c:28 +msgid "layout to use" +msgstr "zu verwendende Anordnung" + +#: builtin/column.c:29 +msgid "Maximum width" +msgstr "maximale Breite" + +#: builtin/column.c:30 +msgid "Padding space on left border" +msgstr "Abstand zum linken Rand" + +#: builtin/column.c:31 +msgid "Padding space on right border" +msgstr "Abstand zur rechten Rand" + +#: builtin/column.c:32 +msgid "Padding space between columns" +msgstr "Abstand zwischen Spalten" #: builtin/column.c:51 msgid "--command must be the first argument" msgstr "Option --command muss zuerst angegeben werden" -#: builtin/commit.c:43 +#: builtin/commit.c:34 +msgid "git commit [options] [--] ..." +msgstr "git commit [Optionen] [--] ..." + +#: builtin/commit.c:39 +msgid "git status [options] [--] ..." +msgstr "git status [Optionen] [--] ..." + +#: builtin/commit.c:44 msgid "" "Your name and email address were configured automatically based\n" "on your username and hostname. Please check that they are accurate.\n" @@ -2535,30 +3316,30 @@ msgid "" "\n" " git commit --amend --reset-author\n" msgstr "" -"Dein Name und E-Mail Adresse wurden automatisch auf Basis\n" -"deines Benutzer- und Rechnernamens konfiguriert. Bitte prüfe, dass diese\n" -"zutreffend sind. Du kannst diese Meldung unterdrücken, indem du diese\n" -"explizit setzt:\n" +"Ihr Name und E-Mail Adresse wurden automatisch auf Basis\n" +"Ihres Benutzer- und Rechnernamens konfiguriert. Bitte prüfen Sie, dass\n" +"diese zutreffend sind. Sie können diese Meldung unterdrücken, indem Sie\n" +"diese explizit setzen:\n" "\n" -" git config --global user.name \"Dein Name\"\n" -" git config --global user.email deine@emailadresse.de\n" +" git config --global user.name \"Ihr Name\"\n" +" git config --global user.email ihre@emailadresse.de\n" "\n" -"Nachdem du das getan hast, kannst du deine Identität für diese Version " +"Nachdem Sie das getan hast, können Sie Ihre Identität für diese Version " "ändern mit:\n" "\n" " git commit --amend --reset-author\n" -#: builtin/commit.c:55 +#: builtin/commit.c:56 msgid "" "You asked to amend the most recent commit, but doing so would make\n" "it empty. You can repeat your command with --allow-empty, or you can\n" "remove the commit entirely with \"git reset HEAD^\".\n" msgstr "" -"Du fragtest die jüngste Version nachzubessern, aber das würde diese leer\n" -"machen. Du kannst Dein Kommando mit --allow-empty wiederholen, oder die\n" +"Sie fragten die jüngste Version nachzubessern, aber das würde diese leer\n" +"machen. Sie können Ihr Kommando mit --allow-empty wiederholen, oder die\n" "Version mit \"git reset HEAD^\" vollständig entfernen.\n" -#: builtin/commit.c:60 +#: builtin/commit.c:61 msgid "" "The previous cherry-pick is now empty, possibly due to conflict resolution.\n" "If you wish to commit it anyway, use:\n" @@ -2569,103 +3350,103 @@ msgid "" msgstr "" "Der letzte \"cherry-pick\" ist jetzt leer, möglicherweise durch eine " "Konfliktauflösung.\n" -"Wenn du dies trotzdem eintragen willst, benutze:\n" +"Wenn Sie dies trotzdem eintragen wollen, benutzen Sie:\n" "\n" " git commit --allow-empty\n" "\n" -"Andernfalls benutze bitte 'git reset'\n" +"Andernfalls benutzen Sie bitte 'git reset'\n" -#: builtin/commit.c:256 +#: builtin/commit.c:258 msgid "failed to unpack HEAD tree object" msgstr "Fehler beim Entpacken des Baum-Objektes der Zweigspitze (HEAD)." -#: builtin/commit.c:298 +#: builtin/commit.c:300 msgid "unable to create temporary index" msgstr "Konnte temporäre Bereitstellung nicht erstellen." -#: builtin/commit.c:304 +#: builtin/commit.c:306 msgid "interactive add failed" msgstr "interaktives Hinzufügen fehlgeschlagen" -#: builtin/commit.c:337 builtin/commit.c:358 builtin/commit.c:408 +#: builtin/commit.c:339 builtin/commit.c:360 builtin/commit.c:410 msgid "unable to write new_index file" msgstr "Konnte new_index Datei nicht schreiben" -#: builtin/commit.c:389 +#: builtin/commit.c:391 msgid "cannot do a partial commit during a merge." msgstr "" "Kann keine partielle Eintragung durchführen, während eine Zusammenführung im " "Gange ist." -#: builtin/commit.c:391 +#: builtin/commit.c:393 msgid "cannot do a partial commit during a cherry-pick." msgstr "" "Kann keine partielle Eintragung durchführen, während \"cherry-pick\" im " "Gange ist." -#: builtin/commit.c:401 +#: builtin/commit.c:403 msgid "cannot read the index" msgstr "Kann Bereitstellung nicht lesen" -#: builtin/commit.c:421 +#: builtin/commit.c:423 msgid "unable to write temporary index file" msgstr "Konnte temporäre Bereitstellungsdatei nicht schreiben." -#: builtin/commit.c:496 builtin/commit.c:502 +#: builtin/commit.c:511 builtin/commit.c:517 #, c-format msgid "invalid commit: %s" msgstr "Ungültige Version: %s" -#: builtin/commit.c:525 +#: builtin/commit.c:540 msgid "malformed --author parameter" msgstr "Fehlerhafter --author Parameter" -#: builtin/commit.c:585 +#: builtin/commit.c:560 #, c-format msgid "Malformed ident string: '%s'" msgstr "Fehlerhafte Identifikations-String: '%s'" -#: builtin/commit.c:623 builtin/commit.c:656 builtin/commit.c:970 +#: builtin/commit.c:598 builtin/commit.c:631 builtin/commit.c:954 #, c-format msgid "could not lookup commit %s" msgstr "Konnte Version %s nicht nachschlagen" -#: builtin/commit.c:635 builtin/shortlog.c:296 +#: builtin/commit.c:610 builtin/shortlog.c:296 #, c-format msgid "(reading log message from standard input)\n" msgstr "(lese Log-Nachricht von Standard-Eingabe)\n" -#: builtin/commit.c:637 +#: builtin/commit.c:612 msgid "could not read log from standard input" msgstr "Konnte Log nicht von Standard-Eingabe lesen." -#: builtin/commit.c:641 +#: builtin/commit.c:616 #, c-format msgid "could not read log file '%s'" msgstr "Konnte Log-Datei '%s' nicht lesen" -#: builtin/commit.c:647 +#: builtin/commit.c:622 msgid "commit has empty message" msgstr "Version hat eine leere Beschreibung" -#: builtin/commit.c:663 +#: builtin/commit.c:638 msgid "could not read MERGE_MSG" msgstr "Konnte MERGE_MSG nicht lesen" -#: builtin/commit.c:667 +#: builtin/commit.c:642 msgid "could not read SQUASH_MSG" msgstr "Konnte SQUASH_MSG nicht lesen" -#: builtin/commit.c:671 +#: builtin/commit.c:646 #, c-format msgid "could not read '%s'" msgstr "Konnte '%s' nicht lesen" -#: builtin/commit.c:723 +#: builtin/commit.c:707 msgid "could not write commit template" msgstr "Konnte Versionsvorlage nicht schreiben" -#: builtin/commit.c:734 +#: builtin/commit.c:718 #, c-format msgid "" "\n" @@ -2675,12 +3456,12 @@ msgid "" "and try again.\n" msgstr "" "\n" -"Es sieht so aus, als trägst du eine Zusammenführung ein.\n" -"Falls das nicht korrekt ist, entferne bitte die Datei\n" +"Es sieht so aus, als tragen Sie eine Zusammenführung ein.\n" +"Falls das nicht korrekt ist, löschen Sie bitte die Datei\n" "\t%s\n" -"und versuche es erneut.\n" +"und versuchen Sie es erneut.\n" -#: builtin/commit.c:739 +#: builtin/commit.c:723 #, c-format msgid "" "\n" @@ -2690,143 +3471,345 @@ msgid "" "and try again.\n" msgstr "" "\n" -"Es sieht so aus, als trägst du ein \"cherry-pick\" ein.\n" -"Falls das nicht korrekt ist, entferne bitte die Datei\n" +"Es sieht so aus, als tragen Sie ein \"cherry-pick\" ein.\n" +"Falls das nicht korrekt ist, löschen Sie bitte die Datei\n" "\t%s\n" -"und versuche es erneut.\n" +"und versuchen Sie es erneut.\n" -#: builtin/commit.c:751 +#: 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 "" -"Bitte gebe eine Versionsbeschreibung für deine Änderungen ein. Zeilen,\n" +"Bitte geben Sie eine Versionsbeschreibung für Ihre Änderungen ein. Zeilen,\n" "die mit '#' beginnen, werden ignoriert, und eine leere Versionsbeschreibung\n" "bricht die Eintragung ab.\n" -#: builtin/commit.c:756 +#: 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 "" -"Bitte gebe eine Versionsbeschreibung für deine Änderungen ein. Zeilen, die\n" -"mit '#' beginnen, werden beibehalten; wenn du möchtest, kannst du diese " +"Bitte geben Sie eine Versionsbeschreibung für Ihre Änderungen ein. Zeilen, die\n" +"mit '#' beginnen, werden beibehalten; wenn Sie möchten, können Sie diese " "entfernen.\n" "Eine leere Versionsbeschreibung bricht die Eintragung ab.\n" -#: builtin/commit.c:769 +#: builtin/commit.c:753 #, c-format msgid "%sAuthor: %s" msgstr "%sAutor: %s" -#: builtin/commit.c:776 +#: builtin/commit.c:760 #, c-format msgid "%sCommitter: %s" msgstr "%sEintragender: %s" -#: builtin/commit.c:796 +#: builtin/commit.c:780 msgid "Cannot read index" msgstr "Kann Bereitstellung nicht lesen" -#: builtin/commit.c:833 +#: builtin/commit.c:817 msgid "Error building trees" msgstr "Fehler beim Erzeugen der Zweige" -#: builtin/commit.c:848 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 "Bitte liefere eine Beschreibung entweder mit der Option -m oder -F.\n" -#: builtin/commit.c:945 +#: builtin/commit.c:929 #, c-format msgid "No existing author found with '%s'" msgstr "Kein existierender Autor mit '%s' gefunden." -#: builtin/commit.c:960 builtin/commit.c:1160 +#: builtin/commit.c:944 builtin/commit.c:1148 #, c-format msgid "Invalid untracked files mode '%s'" msgstr "Ungültiger Modus '%s' für unbeobachtete Dateien" -#: builtin/commit.c:1000 +#: builtin/commit.c:984 msgid "Using both --reset-author and --author does not make sense" msgstr "Verwendung von --reset-author und --author macht keinen Sinn." -#: builtin/commit.c:1011 +#: builtin/commit.c:995 msgid "You have nothing to amend." -msgstr "Du hast nichts zum nachbessern." +msgstr "Sie haben nichts zum nachbessern." -#: builtin/commit.c:1014 +#: builtin/commit.c:998 msgid "You are in the middle of a merge -- cannot amend." msgstr "Eine Zusammenführung ist im Gange -- kann nicht nachbessern." -#: builtin/commit.c:1016 +#: builtin/commit.c:1000 msgid "You are in the middle of a cherry-pick -- cannot amend." msgstr "\"cherry-pick\" ist im Gange -- kann nicht nachbessern." -#: builtin/commit.c:1019 +#: builtin/commit.c:1003 msgid "Options --squash and --fixup cannot be used together" msgstr "" "Die Optionen --squash und --fixup können nicht gemeinsam benutzt werden." -#: builtin/commit.c:1029 +#: builtin/commit.c:1013 msgid "Only one of -c/-C/-F/--fixup can be used." msgstr "Nur eines von -c/-C/-F/--fixup kann benutzt werden." -#: builtin/commit.c:1031 +#: builtin/commit.c:1015 msgid "Option -m cannot be combined with -c/-C/-F/--fixup." msgstr "Option -m kann nicht mit -c/-C/-F/--fixup kombiniert werden" -#: builtin/commit.c:1039 +#: builtin/commit.c:1023 msgid "--reset-author can be used only with -C, -c or --amend." msgstr "--reset--author kann nur mit -C, -c oder --amend benutzt werden" -#: builtin/commit.c:1056 +#: builtin/commit.c:1040 msgid "Only one of --include/--only/--all/--interactive/--patch can be used." msgstr "" "Nur eines von --include/--only/--all/--interactive/--patch kann benutzt " "werden." -#: builtin/commit.c:1058 +#: builtin/commit.c:1042 msgid "No paths with --include/--only does not make sense." msgstr "--include/--only machen ohne Pfade keinen Sinn." -#: builtin/commit.c:1060 +#: builtin/commit.c:1044 msgid "Clever... amending the last one with dirty index." msgstr "" "Klug... die letzte Version mit einer unsauberen Bereitstellung nachbessern." -#: builtin/commit.c:1062 +#: builtin/commit.c:1046 msgid "Explicit paths specified without -i nor -o; assuming --only paths..." msgstr "" "Explizite Pfade ohne -i oder -o angegeben; unter der Annahme von --only " "Pfaden..." -#: builtin/commit.c:1072 builtin/tag.c:577 +#: builtin/commit.c:1056 builtin/tag.c:577 #, c-format msgid "Invalid cleanup mode %s" msgstr "Ungültiger \"cleanup\" Modus %s" -#: builtin/commit.c:1077 +#: builtin/commit.c:1061 msgid "Paths with -a does not make sense." msgstr "Pfade mit -a machen keinen Sinn." -#: builtin/commit.c:1260 +#: builtin/commit.c:1067 builtin/commit.c:1202 +msgid "--long and -z are incompatible" +msgstr "--long und -z sind inkompatibel" + +#: builtin/commit.c:1162 builtin/commit.c:1398 +msgid "show status concisely" +msgstr "zeigt Status im Kurzformat" + +#: builtin/commit.c:1164 builtin/commit.c:1400 +msgid "show branch information" +msgstr "zeigt Zweiginformationen" + +#: builtin/commit.c:1166 builtin/commit.c:1402 builtin/push.c:389 +msgid "machine-readable output" +msgstr "maschinenlesbare Ausgabe" + +#: builtin/commit.c:1169 builtin/commit.c:1404 +msgid "show status in long format (default)" +msgstr "zeigt Status im Langformat (Standard)" + +#: builtin/commit.c:1172 builtin/commit.c:1407 +msgid "terminate entries with NUL" +msgstr "schließt Einträge mit NUL-Zeichen ab" + +#: 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 "Modus" + +#: builtin/commit.c:1175 builtin/commit.c:1410 +msgid "show untracked files, optional modes: all, normal, no. (Default: all)" +msgstr "" +"zeigt nicht beobachtete Dateien, optionale Modi: all, normal, no. (Standard: " +"all)" + +#: builtin/commit.c:1178 +msgid "show ignored files" +msgstr "zeigt ignorierte Dateien" + +#: builtin/commit.c:1179 parse-options.h:151 +msgid "when" +msgstr "wann" + +#: builtin/commit.c:1180 +msgid "" +"ignore changes to submodules, optional when: all, dirty, untracked. " +"(Default: all)" +msgstr "" +"ignoriert Änderungen in Unterprojekten, optional wenn: all, dirty, " +"untracked. (Standard: all)" + +#: builtin/commit.c:1182 +msgid "list untracked files in columns" +msgstr "listet unbeobachtete Dateien in Spalten auf" + +#: builtin/commit.c:1256 msgid "couldn't look up newly created commit" msgstr "Konnte neu erstellte Version nicht nachschlagen." -#: builtin/commit.c:1262 +#: builtin/commit.c:1258 msgid "could not parse newly created commit" msgstr "Konnte neulich erstellte Version nicht analysieren." -#: builtin/commit.c:1303 +#: builtin/commit.c:1299 msgid "detached HEAD" msgstr "losgelöste Zweigspitze (HEAD)" -#: builtin/commit.c:1305 +#: builtin/commit.c:1301 msgid " (root-commit)" msgstr " (Basis-Version)" +#: builtin/commit.c:1368 +msgid "suppress summary after successful commit" +msgstr "unterdrückt Zusammenfassung nach erfolgreicher Eintragung" + +#: builtin/commit.c:1369 +msgid "show diff in commit message template" +msgstr "zeigt Unterschiede in Versionsbeschreibungsvorlage an" + +#: builtin/commit.c:1371 +msgid "Commit message options" +msgstr "Optionen für Versionsbeschreibung" + +#: builtin/commit.c:1372 builtin/tag.c:459 +msgid "read message from file" +msgstr "liest Beschreibung von Datei" + +#: builtin/commit.c:1373 +msgid "author" +msgstr "Autor" + +#: builtin/commit.c:1373 +msgid "override author for commit" +msgstr "überschreibt Autor von Version" + +#: builtin/commit.c:1374 builtin/gc.c:178 +msgid "date" +msgstr "Datum" + +#: builtin/commit.c:1374 +msgid "override date for commit" +msgstr "überschreibt Datum von Version" + +#: builtin/commit.c:1375 builtin/merge.c:206 builtin/notes.c:537 +#: builtin/notes.c:694 builtin/tag.c:457 +msgid "message" +msgstr "Beschreibung" + +#: builtin/commit.c:1375 +msgid "commit message" +msgstr "Versionsbeschreibung" + +#: builtin/commit.c:1376 +msgid "reuse and edit message from specified commit" +msgstr "verwendet wieder und editiert Beschreibung von der angegebenen Version" + +#: builtin/commit.c:1377 +msgid "reuse message from specified commit" +msgstr "verwendet Beschreibung der angegebenen Version wieder" + +#: builtin/commit.c:1378 +msgid "use autosquash formatted message to fixup specified commit" +msgstr "" +"benutzt eine automatisch zusammengesetzte Beschreibung zum Nachbessern der " +"angegebenen Version" + +#: builtin/commit.c:1379 +msgid "use autosquash formatted message to squash specified commit" +msgstr "" +"benutzt eine automatisch zusammengesetzte Beschreibung zum Zusammenführen " +"der angegebenen Version" + +#: builtin/commit.c:1380 +msgid "the commit is authored by me now (used with -C/-c/--amend)" +msgstr "Setzt Sie als Autor der Version (benutzt mit -C/-c/--amend)" + +#: builtin/commit.c:1381 builtin/log.c:1073 builtin/revert.c:109 +msgid "add Signed-off-by:" +msgstr "fügt 'Signed-off-by:'-Zeile hinzu" + +#: builtin/commit.c:1382 +msgid "use specified template file" +msgstr "benutzt angegebene Vorlagendatei" + +#: builtin/commit.c:1383 +msgid "force edit of commit" +msgstr "erzwingt Bearbeitung der Version" + +#: builtin/commit.c:1384 +msgid "default" +msgstr "Standard" + +#: builtin/commit.c:1384 builtin/tag.c:462 +msgid "how to strip spaces and #comments from message" +msgstr "" +"wie Leerzeichen und #Kommentare von der Beschreibung getrennt werden sollen" + +#: builtin/commit.c:1385 +msgid "include status in commit message template" +msgstr "fügt Status in die Versionsbeschreibungsvorlage ein" + +#: builtin/commit.c:1386 builtin/merge.c:213 builtin/tag.c:463 +msgid "key id" +msgstr "Schlüssel-ID" + +#: builtin/commit.c:1387 builtin/merge.c:214 +msgid "GPG sign commit" +msgstr "signiert Version mit GPG" + +#. end commit message options +#: builtin/commit.c:1390 +msgid "Commit contents options" +msgstr "Optionen für Versionsinhalt" + +#: builtin/commit.c:1391 +msgid "commit all changed files" +msgstr "trägt alle geänderten Dateien ein" + +#: builtin/commit.c:1392 +msgid "add specified files to index for commit" +msgstr "trägt die angegebenen Dateien zusätzlich zur Bereitstellung ein" + +#: builtin/commit.c:1393 +msgid "interactively add files" +msgstr "interaktives Hinzufügen von Dateien" + +#: builtin/commit.c:1394 +msgid "interactively add changes" +msgstr "interaktives Hinzufügen von Änderungen" + +#: builtin/commit.c:1395 +msgid "commit only specified files" +msgstr "trägt nur die angegebenen Dateien ein" + +#: builtin/commit.c:1396 +msgid "bypass pre-commit hook" +msgstr "umgeht \"pre-commit hook\"" + +#: builtin/commit.c:1397 +msgid "show what would be committed" +msgstr "zeigt an, was eingetragen werden würde" + +#: builtin/commit.c:1408 +msgid "amend previous commit" +msgstr "ändert vorherige Version" + +#: builtin/commit.c:1409 +msgid "bypass post-rewrite hook" +msgstr "umgeht \"post-rewrite hook\"" + +#: builtin/commit.c:1414 +msgid "ok to record an empty change" +msgstr "erlaubt Aufzeichnung einer leeren Änderung" + +#: builtin/commit.c:1417 +msgid "ok to record a change with an empty message" +msgstr "erlaubt Aufzeichnung einer Änderung mit einer leeren Beschreibung" + #: builtin/commit.c:1449 msgid "could not parse HEAD commit" msgstr "Konnte Version der Zweigspitze (HEAD) nicht analysieren." @@ -2853,14 +3836,14 @@ msgstr "Konnte Versionsbeschreibung nicht lesen: %s" #: builtin/commit.c:1534 #, c-format msgid "Aborting commit; you did not edit the message.\n" -msgstr "Eintragung abgebrochen; du hast die Beschreibung nicht editiert.\n" +msgstr "Eintragung abgebrochen; Sie haben die Beschreibung nicht editiert.\n" #: builtin/commit.c:1539 #, c-format msgid "Aborting commit due to empty commit message.\n" msgstr "Eintragung aufgrund leerer Versionsbeschreibung abgebrochen.\n" -#: builtin/commit.c:1554 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 "Fehler beim Schreiben des Versionsobjektes." @@ -2879,10 +3862,138 @@ msgid "" "not exceeded, and then \"git reset HEAD\" to recover." msgstr "" "Das Projektarchiv wurde aktualisiert, aber die \"new_index\"-Datei\n" -"konnte nicht geschrieben werden. Prüfe, dass deine Festplatte nicht\n" -"voll und Dein Kontingent nicht aufgebraucht ist und führe\n" +"konnte nicht geschrieben werden. Prüfen Sie, dass Ihre Festplatte nicht\n" +"voll und Ihr Kontingent nicht aufgebraucht ist und führen Sie\n" "anschließend \"git reset HEAD\" zu Wiederherstellung aus." +#: builtin/config.c:7 +msgid "git config [options]" +msgstr "git config [Optionen]" + +#: builtin/config.c:51 +msgid "Config file location" +msgstr "Ort der Konfigurationsdatei" + +#: builtin/config.c:52 +msgid "use global config file" +msgstr "benutzt globale Konfigurationsdatei" + +#: builtin/config.c:53 +msgid "use system config file" +msgstr "benutzt systemweite Konfigurationsdatei" + +#: builtin/config.c:54 +msgid "use repository config file" +msgstr "benutzt Konfigurationsdatei des Projektarchivs" + +#: builtin/config.c:55 +msgid "use given config file" +msgstr "benutzt die angegebene Konfigurationsdatei" + +#: builtin/config.c:56 +msgid "Action" +msgstr "Aktion" + +#: builtin/config.c:57 +msgid "get value: name [value-regex]" +msgstr "gibt Wert zurück: Name [Wert-regex]" + +#: builtin/config.c:58 +msgid "get all values: key [value-regex]" +msgstr "gibt alle Werte zurück: Schlüssel [Wert-regex]" + +#: builtin/config.c:59 +msgid "get values for regexp: name-regex [value-regex]" +msgstr "gibt Werte für den regulären Ausdruck zurück: Name-regex [Wert-regex]" + +#: builtin/config.c:60 +msgid "replace all matching variables: name value [value_regex]" +msgstr "ersetzt alle passenden Variablen: Name Wert [Wert-regex] " + +#: builtin/config.c:61 +msgid "add a new variable: name value" +msgstr "fügt neue Variable hinzu: Name Wert" + +#: builtin/config.c:62 +msgid "remove a variable: name [value-regex]" +msgstr "entfernt eine Variable: Name [Wert-regex]" + +#: builtin/config.c:63 +msgid "remove all matches: name [value-regex]" +msgstr "entfernt alle Übereinstimmungen: Name [Wert-regex]" + +#: builtin/config.c:64 +msgid "rename section: old-name new-name" +msgstr "benennt eine Sektion um: alter-Name neuer-Name" + +#: builtin/config.c:65 +msgid "remove a section: name" +msgstr "entfernt eine Sektion: Name" + +#: builtin/config.c:66 +msgid "list all" +msgstr "listet alles auf" + +#: builtin/config.c:67 +msgid "open an editor" +msgstr "öffnet einen Editor" + +#: builtin/config.c:68 builtin/config.c:69 +msgid "slot" +msgstr "Slot" + +#: builtin/config.c:68 +msgid "find the color configured: [default]" +msgstr "findet die konfigurierte Farbe: [Standard]" + +#: builtin/config.c:69 +msgid "find the color setting: [stdout-is-tty]" +msgstr "findet die Farbeinstellung: [Standard-Ausgabe-ist-Terminal]" + +#: builtin/config.c:70 +msgid "Type" +msgstr "Typ" + +#: builtin/config.c:71 +msgid "value is \"true\" or \"false\"" +msgstr "Wert ist \"true\" oder \"false\"" + +#: builtin/config.c:72 +msgid "value is decimal number" +msgstr "Wert ist eine Dezimalzahl" + +#: builtin/config.c:73 +msgid "value is --bool or --int" +msgstr "Wert ist --bool oder --int" + +#: builtin/config.c:74 +msgid "value is a path (file or directory name)" +msgstr "Wert ist ein Pfad (Datei oder Verzeichnisname)" + +#: builtin/config.c:75 +msgid "Other" +msgstr "Sonstiges" + +#: builtin/config.c:76 +msgid "terminate values with NUL byte" +msgstr "schließt Werte mit NUL-Byte ab" + +#: builtin/config.c:77 +msgid "respect include directives on lookup" +msgstr "beachtet \"include\"-Direktiven beim Nachschlagen" + +#: builtin/count-objects.c:69 +msgid "git count-objects [-v]" +msgstr "git count-objects [-v]" + +#: builtin/describe.c:15 +msgid "git describe [options] *" +msgstr "git describe [Optionen] *" + +#: builtin/describe.c:16 +msgid "git describe [options] --dirty" +msgstr "git describe [Optionen] --dirty" + #: builtin/describe.c:234 #, c-format msgid "annotated tag %s not available" @@ -2930,7 +4041,7 @@ msgid "" "However, there were unannotated tags: try --tags." msgstr "" "Keine annotierten Markierungen können '%s' beschreiben.\n" -"Jedoch gab es nicht annotierte Markierungen: versuche --tags." +"Jedoch gab es nicht annotierte Markierungen: versuchen Sie --tags." #: builtin/describe.c:357 #, c-format @@ -2939,7 +4050,7 @@ msgid "" "Try --always, or create some tags." msgstr "" "Keine Markierungen können '%s' beschreiben.\n" -"Versuche --always oder erstelle einige Markierungen." +"Versuchen Sie --always oder erstellen Sie einige Markierungen." #: builtin/describe.c:378 #, c-format @@ -2955,6 +4066,51 @@ msgstr "" "mehr als %i Markierungen gefunden; Führe die ersten %i auf\n" "Suche bei %s aufgegeben\n" +#: builtin/describe.c:403 +msgid "find the tag that comes after the commit" +msgstr "findet die Markierung, die nach der Version kommt" + +#: builtin/describe.c:404 +msgid "debug search strategy on stderr" +msgstr "protokolliert die Suchstrategie in der Standard-Fehlerausgabe" + +#: builtin/describe.c:405 +msgid "use any ref in .git/refs" +msgstr "benutzt jede Referenz in .git/refs" + +#: builtin/describe.c:406 +msgid "use any tag in .git/refs/tags" +msgstr "benutzt alle Markierungen in .git/refs/tags" + +#: builtin/describe.c:407 +msgid "always use long format" +msgstr "benutzt immer langes Format" + +#: builtin/describe.c:410 +msgid "only output exact matches" +msgstr "gibt nur exakte Übereinstimmungen aus" + +#: builtin/describe.c:412 +msgid "consider most recent tags (default: 10)" +msgstr "betrachtet die jüngsten Markierungen (Standard: 10)" + +#: builtin/describe.c:414 +msgid "only consider tags matching " +msgstr "betrachtet nur Markierungen die entsprechen" + +#: builtin/describe.c:416 builtin/name-rev.c:238 +msgid "show abbreviated commit object as fallback" +msgstr "zeigt gekürztes Versionsobjekt, wenn sonst nichts zutrifft" + +#: builtin/describe.c:417 +msgid "mark" +msgstr "Kennzeichen" + +#: builtin/describe.c:418 +msgid "append on dirty working tree (default: \"-dirty\")" +msgstr "" +"fügt bei geändertem Arbeitsbaum hinzu (Standard: \"-dirty\")" + #: builtin/describe.c:436 msgid "--long is incompatible with --abbrev=0" msgstr "--long ist inkompatibel mit --abbrev=0" @@ -2972,40 +4128,162 @@ msgstr "--dirty ist inkompatibel mit Versionen" msgid "'%s': not a regular file or symlink" msgstr "'%s': keine reguläre Datei oder symbolischer Link" -#: builtin/diff.c:224 +#: builtin/diff.c:228 #, c-format msgid "invalid option: %s" msgstr "Ungültige Option: %s" -#: builtin/diff.c:301 +#: builtin/diff.c:305 msgid "Not a git repository" msgstr "Kein Git-Projektarchiv" -#: builtin/diff.c:344 +#: builtin/diff.c:348 #, c-format msgid "invalid object '%s' given." msgstr "Objekt '%s' ist ungültig." -#: builtin/diff.c:349 +#: builtin/diff.c:353 #, c-format msgid "more than %d trees given: '%s'" msgstr "Mehr als %d Zweige angegeben: '%s'" -#: builtin/diff.c:359 +#: builtin/diff.c:363 #, c-format msgid "more than two blobs given: '%s'" msgstr "Mehr als zwei Blobs angegeben: '%s'" -#: builtin/diff.c:367 +#: builtin/diff.c:371 #, c-format msgid "unhandled object '%s' given." msgstr "unbehandeltes Objekt '%s' angegeben" -#: builtin/fetch.c:200 +#: builtin/fast-export.c:22 +msgid "git fast-export [rev-list-opts]" +msgstr "git fast-export [rev-list-opts]" + +#: builtin/fast-export.c:635 +msgid "show progress after objects" +msgstr "zeigt Fortschritt nach Objekten an" + +#: builtin/fast-export.c:637 +msgid "select handling of signed tags" +msgstr "wählt Behandlung von signierten Markierungen" + +#: builtin/fast-export.c:640 +msgid "select handling of tags that tag filtered objects" +msgstr "wählt Behandlung von Markierungen, die gefilterte Objekte markieren" + +#: builtin/fast-export.c:643 +msgid "Dump marks to this file" +msgstr "Schreibt Kennzeichen in diese Datei" + +#: builtin/fast-export.c:645 +msgid "Import marks from this file" +msgstr "Importiert Kennzeichen von dieser Datei" + +#: builtin/fast-export.c:647 +msgid "Fake a tagger when tags lack one" +msgstr "" +"erzeugt künstlich einen Markierungsersteller, wenn die Markierung keinen hat" + +#: builtin/fast-export.c:649 +msgid "Output full tree for each commit" +msgstr "gibt für jede Version den gesamten Baum aus" + +#: builtin/fast-export.c:651 +msgid "Use the done feature to terminate the stream" +msgstr "Benutzt die \"done\"-Funktion um den Strom abzuschließen" + +#: builtin/fast-export.c:652 +msgid "Skip output of blob data" +msgstr "Überspringt Ausgabe von Blob-Daten" + +#: builtin/fetch.c:20 +msgid "git fetch [] [ [...]]" +msgstr "git fetch [] [ [...]]" + +#: builtin/fetch.c:21 +msgid "git fetch [] " +msgstr "git fetch [] " + +#: builtin/fetch.c:22 +msgid "git fetch --multiple [] [( | )...]" +msgstr "git fetch --multiple [] [( | )...]" + +#: builtin/fetch.c:23 +msgid "git fetch --all []" +msgstr "git fetch --all []" + +#: builtin/fetch.c:60 +msgid "fetch from all remotes" +msgstr "fordert von allen externen Projektarchiven an" + +#: builtin/fetch.c:62 +msgid "append to .git/FETCH_HEAD instead of overwriting" +msgstr "hängt an .git/FETCH_HEAD an, anstatt zu überschreiben" + +#: builtin/fetch.c:64 +msgid "path to upload pack on remote end" +msgstr "Pfad des Programms zum Hochladen von Paketen auf der Gegenseite" + +#: builtin/fetch.c:65 +msgid "force overwrite of local branch" +msgstr "erzwingt das Überschreiben von lokalen Zweigen" + +#: builtin/fetch.c:67 +msgid "fetch from multiple remotes" +msgstr "fordert von mehreren externen Projektarchiven an" + +#: builtin/fetch.c:69 +msgid "fetch all tags and associated objects" +msgstr "fordert alle Markierungen und verbundene Objekte an" + +#: builtin/fetch.c:71 +msgid "do not fetch all tags (--no-tags)" +msgstr "fordert nicht alle Markierungen an (--no-tags)" + +#: builtin/fetch.c:73 +msgid "prune remote-tracking branches no longer on remote" +msgstr "" +"entfernt externe Übernahmezweige, die sich nicht mehr im Fernarchiv befinden" + +#: builtin/fetch.c:74 +msgid "on-demand" +msgstr "bei-Bedarf" + +#: builtin/fetch.c:75 +msgid "control recursive fetching of submodules" +msgstr "kontrolliert rekursive Anforderungen von Unterprojekten" + +#: builtin/fetch.c:79 +msgid "keep downloaded pack" +msgstr "behält heruntergeladenes Paket" + +#: builtin/fetch.c:81 +msgid "allow updating of HEAD ref" +msgstr "erlaubt Aktualisierung der \"HEAD\"-Referenz" + +#: builtin/fetch.c:84 +msgid "deepen history of shallow clone" +msgstr "vertieft die Historie eines flachen Klon" + +#: builtin/fetch.c:85 builtin/log.c:1088 +msgid "dir" +msgstr "Verzeichnis" + +#: builtin/fetch.c:86 +msgid "prepend this to submodule path output" +msgstr "stellt dies an die Ausgabe der Unterprojekt-Pfade voran" + +#: builtin/fetch.c:89 +msgid "default mode for recursion" +msgstr "Standard-Modus für Rekursion" + +#: builtin/fetch.c:201 msgid "Couldn't find remote ref HEAD" msgstr "Konnte externe Referenz der Zweigspitze (HEAD) nicht finden." -#: builtin/fetch.c:253 +#: builtin/fetch.c:254 #, c-format msgid "object %s not found" msgstr "Objekt %s nicht gefunden" @@ -3076,8 +4354,8 @@ msgid "" "some local refs could not be updated; try running\n" " 'git remote prune %s' to remove any old, conflicting branches" msgstr "" -"Einige lokale Referenzen konnten nicht aktualisiert werden; versuche\n" -"'git remote prune %s' um jeden älteren, widersprüchlichen Zweig zu entfernen." +"Einige lokale Referenzen konnten nicht aktualisiert werden; versuchen Sie\n" +"'git remote prune %s' um jeden älteren, widersprüchlichen Zweig zu löschen." #: builtin/fetch.c:549 #, c-format @@ -3119,48 +4397,167 @@ msgstr "Option \"%s\" Wert \"%s\" ist nicht gültig für %s" msgid "Option \"%s\" is ignored for %s\n" msgstr "Option \"%s\" wird ignoriert für %s\n" -#: builtin/fetch.c:888 +#: builtin/fetch.c:891 #, c-format msgid "Fetching %s\n" msgstr "Fordere an von %s\n" -#: builtin/fetch.c:890 builtin/remote.c:100 +#: builtin/fetch.c:893 builtin/remote.c:100 #, c-format msgid "Could not fetch %s" msgstr "Konnte nicht von %s anfordern" -#: builtin/fetch.c:907 +#: builtin/fetch.c:912 msgid "" "No remote repository specified. Please, specify either a URL or a\n" "remote name from which new revisions should be fetched." msgstr "" -"Kein externes Projektarchiv angegeben. Bitte gebe entweder eine URL\n" +"Kein externes Projektarchiv angegeben. Bitte geben Sie entweder eine URL\n" "oder den Namen des externen Archivs an, von welchem neue\n" "Versionen angefordert werden sollen." -#: builtin/fetch.c:927 +#: builtin/fetch.c:932 msgid "You need to specify a tag name." -msgstr "Du musst den Namen der Markierung angeben." +msgstr "Sie müssen den Namen der Markierung angeben." -#: builtin/fetch.c:979 +#: builtin/fetch.c:984 msgid "fetch --all does not take a repository argument" msgstr "fetch --all akzeptiert kein Projektarchiv als Argument" -#: builtin/fetch.c:981 +#: builtin/fetch.c:986 msgid "fetch --all does not make sense with refspecs" msgstr "fetch --all macht keinen Sinn mit Referenzspezifikationen" -#: builtin/fetch.c:992 +#: builtin/fetch.c:997 #, c-format msgid "No such remote or remote group: %s" msgstr "Kein externes Archiv (einzeln oder Gruppe): %s" -#: builtin/fetch.c:1000 +#: builtin/fetch.c:1005 msgid "Fetching a group and specifying refspecs does not make sense" msgstr "" "Abholen einer Gruppe mit Angabe von Referenzspezifikationen macht keinen " "Sinn." +#: builtin/fmt-merge-msg.c:13 +msgid "git fmt-merge-msg [-m ] [--log[=]|--no-log] [--file ]" +msgstr "" +"git fmt-merge-msg [-m ] [--log[=]|--no-log] [--file ]" + +#: 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 "Anzahl" + +#: builtin/fmt-merge-msg.c:654 +msgid "populate log with at most entries from shortlog" +msgstr "fügt Historie mit höchstens Einträgen von \"shortlog\" hinzu" + +#: builtin/fmt-merge-msg.c:657 +msgid "alias for --log (deprecated)" +msgstr "Alias für --log (veraltet)" + +#: builtin/fmt-merge-msg.c:660 +msgid "text" +msgstr "Text" + +#: builtin/fmt-merge-msg.c:661 +msgid "use as start of message" +msgstr "benutzt als Beschreibungsanfang" + +#: builtin/fmt-merge-msg.c:662 +msgid "file to read from" +msgstr "Datei zum Einlesen" + +#: builtin/for-each-ref.c:979 +msgid "git for-each-ref [options] []" +msgstr "git for-each-ref [Optionen] []" + +#: builtin/for-each-ref.c:994 +msgid "quote placeholders suitably for shells" +msgstr "formatiert Platzhalter als Shell-String" + +#: builtin/for-each-ref.c:996 +msgid "quote placeholders suitably for perl" +msgstr "formatiert Platzhalter als Perl-String" + +#: builtin/for-each-ref.c:998 +msgid "quote placeholders suitably for python" +msgstr "formatiert Platzhalter als Python-String" + +#: builtin/for-each-ref.c:1000 +msgid "quote placeholders suitably for tcl" +msgstr "formatiert Platzhalter als TCL-String" + +#: builtin/for-each-ref.c:1003 +msgid "show only matched refs" +msgstr "zeigt nur passende Referenzen" + +#: builtin/for-each-ref.c:1004 +msgid "format" +msgstr "Format" + +#: builtin/for-each-ref.c:1004 +msgid "format to use for the output" +msgstr "für die Ausgabe zu verwendendes Format" + +#: builtin/for-each-ref.c:1005 +msgid "key" +msgstr "Schüssel" + +#: builtin/for-each-ref.c:1006 +msgid "field name to sort on" +msgstr "sortiere nach diesem Feld" + +#: builtin/fsck.c:608 +msgid "git fsck [options] [...]" +msgstr "git fsck [Optionen] [...]" + +#: builtin/fsck.c:614 +msgid "show unreachable objects" +msgstr "zeigt unerreichbare Objekte" + +#: builtin/fsck.c:615 +msgid "show dangling objects" +msgstr "zeigt unreferenzierte Objekte" + +#: builtin/fsck.c:616 +msgid "report tags" +msgstr "meldet Markierungen" + +#: builtin/fsck.c:617 +msgid "report root nodes" +msgstr "meldet Hauptwurzeln" + +#: builtin/fsck.c:618 +msgid "make index objects head nodes" +msgstr "erzeugt Kopfknoten der Bereitstellungsobjekte" + +#: builtin/fsck.c:619 +msgid "make reflogs head nodes (default)" +msgstr "erzeugt Kopfknoten des Referenzprotokolls (Standard)" + +#: builtin/fsck.c:620 +msgid "also consider packs and alternate objects" +msgstr "" + +#: builtin/fsck.c:621 +msgid "enable more strict checking" +msgstr "aktiviert genauere Prüfung" + +#: builtin/fsck.c:623 +msgid "write dangling objects in .git/lost-found" +msgstr "schreibt unreferenzierte Objekte nach .git/lost-found" + +#: builtin/fsck.c:624 builtin/prune.c:134 +msgid "show progress" +msgstr "zeigt Fortschrittsanzeige" + +#: builtin/gc.c:22 +msgid "git gc [options]" +msgstr "git gc [Optionen]" + #: builtin/gc.c:63 #, c-format msgid "Invalid %s: '%s'" @@ -3171,90 +4568,326 @@ msgstr "Ungültiger %s: '%s'" msgid "insanely long object directory %.*s" msgstr "zu langes Objekt-Verzeichnis %.*s" -#: builtin/gc.c:221 -#, c-format -msgid "Auto packing the repository for optimum performance.\n" -msgstr "" -"Die Datenbank des Projektarchivs wird für eine optimale Performance " -"komprimiert.\n" +#: builtin/gc.c:179 +msgid "prune unreferenced objects" +msgstr "entfernt unreferenzierte Objekte" + +#: builtin/gc.c:181 +msgid "be more thorough (increased runtime)" +msgstr "mehr Gründlichkeit (erhöht Laufzeit)" -#: builtin/gc.c:224 +#: builtin/gc.c:182 +msgid "enable auto-gc mode" +msgstr "aktiviert \"auto-gc\" Modus" + +#: builtin/gc.c:222 #, c-format msgid "" "Auto packing the repository for optimum performance. You may also\n" "run \"git gc\" manually. See \"git help gc\" for more information.\n" msgstr "" "Die Datenbank des Projektarchivs wird für eine optimale Performance\n" -"komprimiert. Du kannst auch \"git gc\" manuell ausführen.\n" +"komprimiert. Sie können auch \"git gc\" manuell ausführen.\n" "Siehe \"git help gc\" für weitere Informationen.\n" -#: builtin/gc.c:251 +#: builtin/gc.c:249 msgid "" "There are too many unreachable loose objects; run 'git prune' to remove them." msgstr "" -"Es gibt zu viele unerreichbare lose Objekte; führe 'git prune' aus, um diese " -"zu entfernen." +"Es gibt zu viele unerreichbare lose Objekte; führen Sie 'git prune' aus, um " +"diese zu löschen." + +#: builtin/grep.c:22 +msgid "git grep [options] [-e] [...] [[--] ...]" +msgstr "git grep [Optionen] [-e] [...] [[--] ...]" -#: builtin/grep.c:216 +#: builtin/grep.c:217 #, c-format msgid "grep: failed to create thread: %s" msgstr "grep: Fehler beim Erzeugen eines Thread: %s" -#: builtin/grep.c:454 +#: builtin/grep.c:365 #, c-format msgid "Failed to chdir: %s" msgstr "Fehler beim Verzeichniswechsel: %s" -#: builtin/grep.c:530 builtin/grep.c:564 +#: builtin/grep.c:443 builtin/grep.c:478 #, c-format msgid "unable to read tree (%s)" msgstr "konnte Zweig (%s) nicht lesen" -#: builtin/grep.c:578 +#: builtin/grep.c:493 #, c-format msgid "unable to grep from object of type %s" msgstr "kann \"grep\" nicht mit Objekten des Typs %s durchführen" -#: builtin/grep.c:636 +#: builtin/grep.c:551 #, c-format msgid "switch `%c' expects a numerical value" msgstr "Schalter '%c' erwartet einen numerischen Wert" -#: builtin/grep.c:653 +#: builtin/grep.c:568 #, c-format msgid "cannot open '%s'" msgstr "kann '%s' nicht öffnen" -#: builtin/grep.c:917 +#: builtin/grep.c:643 +msgid "search in index instead of in the work tree" +msgstr "sucht in der Bereitstellung anstatt im Arbeitsbaum" + +#: builtin/grep.c:645 +msgid "find in contents not managed by git" +msgstr "findet auch in Inhalten, die nicht von Git verwaltet werden" + +#: builtin/grep.c:647 +msgid "search in both tracked and untracked files" +msgstr "sucht in beobachteten und unbeobachteten Dateien" + +#: builtin/grep.c:649 +msgid "search also in ignored files" +msgstr "sucht auch in ignorierten Dateien" + +#: builtin/grep.c:652 +msgid "show non-matching lines" +msgstr "zeigt Zeilen ohne Übereinstimmungen" + +#: builtin/grep.c:654 +msgid "case insensitive matching" +msgstr "findet Übereinstimmungen unabhängig von Groß- und Kleinschreibung" + +#: builtin/grep.c:656 +msgid "match patterns only at word boundaries" +msgstr "sucht nur ganze Wörter" + +#: builtin/grep.c:658 +msgid "process binary files as text" +msgstr "verarbeitet binäre Dateien als Text" + +#: builtin/grep.c:660 +msgid "don't match patterns in binary files" +msgstr "findet keine Muster in Binärdateien" + +#: builtin/grep.c:663 +msgid "descend at most levels" +msgstr "durchläuft höchstens Ebenen" + +#: builtin/grep.c:667 +msgid "use extended POSIX regular expressions" +msgstr "benutzt erweiterte reguläre Ausdrücke aus POSIX" + +#: builtin/grep.c:670 +msgid "use basic POSIX regular expressions (default)" +msgstr "benutzt grundlegende reguläre Ausdrücke aus POSIX (Standard)" + +#: builtin/grep.c:673 +msgid "interpret patterns as fixed strings" +msgstr "interpretiert Muster als feste Zeichenketten" + +#: builtin/grep.c:676 +msgid "use Perl-compatible regular expressions" +msgstr "benutzt Perl-kompatible reguläre Ausdrücke" + +#: builtin/grep.c:679 +msgid "show line numbers" +msgstr "zeigt Zeilennummern" + +#: builtin/grep.c:680 +msgid "don't show filenames" +msgstr "zeigt keine Dateinamen" + +#: builtin/grep.c:681 +msgid "show filenames" +msgstr "zeigt Dateinamen" + +#: builtin/grep.c:683 +msgid "show filenames relative to top directory" +msgstr "zeigt Dateinamen relativ zum Projektverzeichnis" + +#: builtin/grep.c:685 +msgid "show only filenames instead of matching lines" +msgstr "zeigt nur Dateinamen anstatt übereinstimmende Zeilen" + +#: builtin/grep.c:687 +msgid "synonym for --files-with-matches" +msgstr "Synonym für --files-with-matches" + +#: builtin/grep.c:690 +msgid "show only the names of files without match" +msgstr "zeigt nur die Dateinamen ohne Übereinstimmungen" + +#: builtin/grep.c:692 +msgid "print NUL after filenames" +msgstr "gibt NUL-Zeichen nach Dateinamen aus" + +#: builtin/grep.c:694 +msgid "show the number of matches instead of matching lines" +msgstr "zeigt, anstatt der Zeilen, die Anzahl der übereinstimmenden Zeilen" + +#: builtin/grep.c:695 +msgid "highlight matches" +msgstr "hebt Übereinstimmungen hervor" + +#: builtin/grep.c:697 +msgid "print empty line between matches from different files" +msgstr "" +"gibt eine Leerzeile zwischen Übereinstimmungen in verschiedenen Dateien aus" + +#: builtin/grep.c:699 +msgid "show filename only once above matches from same file" +msgstr "" +"zeigt den Dateinamen nur einmal oberhalb der Übereinstimmungen aus dieser " +"Datei an" + +#: builtin/grep.c:702 +msgid "show context lines before and after matches" +msgstr "zeigt Zeilen vor und nach den Übereinstimmungen an" + +#: builtin/grep.c:705 +msgid "show context lines before matches" +msgstr "zeigt Zeilen vor den Übereinstimmungen an" + +#: builtin/grep.c:707 +msgid "show context lines after matches" +msgstr "zeigt Zeilen nach den Übereinstimmungen an" + +#: builtin/grep.c:708 +msgid "shortcut for -C NUM" +msgstr "Kurzform für -C NUM" + +#: builtin/grep.c:711 +msgid "show a line with the function name before matches" +msgstr "zeigt eine Zeile mit dem Funktionsnamen vor Übereinstimmungen an" + +#: builtin/grep.c:713 +msgid "show the surrounding function" +msgstr "zeigt die umgebende Funktion an" + +#: builtin/grep.c:716 +msgid "read patterns from file" +msgstr "liest Muster von einer Datei" + +#: builtin/grep.c:718 +msgid "match " +msgstr "findet " + +#: builtin/grep.c:720 +msgid "combine patterns specified with -e" +msgstr "kombiniert Muster, die mit -e angegeben wurden" + +#: builtin/grep.c:732 +msgid "indicate hit with exit status without output" +msgstr "zeigt Übereinstimmungen nur durch Beendigungsstatus an" + +#: builtin/grep.c:734 +msgid "show only matches from files that match all patterns" +msgstr "zeigt nur Übereinstimmungen von Dateien, die allen Mustern entsprechen" + +#: builtin/grep.c:736 +msgid "show parse tree for grep expression" +msgstr "zeigt geparsten Baum für \"grep\"-Ausdruck" + +#: builtin/grep.c:740 +msgid "pager" +msgstr "Anzeigeprogramm" + +#: builtin/grep.c:740 +msgid "show matching files in the pager" +msgstr "zeigt Dateien mit Übereinstimmungen im Anzeigeprogramm" + +#: builtin/grep.c:743 +msgid "allow calling of grep(1) (ignored by this build)" +msgstr "erlaubt den Aufruf von grep(1) (von dieser Programmversion ignoriert)" + +#: builtin/grep.c:744 builtin/show-ref.c:184 +msgid "show usage" +msgstr "zeigt Verwendung" + +#: builtin/grep.c:811 msgid "no pattern given." msgstr "keine Muster angegeben" -#: builtin/grep.c:931 +#: builtin/grep.c:825 #, c-format msgid "bad object %s" msgstr "ungültiges Objekt %s" -#: builtin/grep.c:972 +#: builtin/grep.c:866 msgid "--open-files-in-pager only works on the worktree" msgstr "--open-files-in-pager arbeitet nur innerhalb des Arbeitsbaums" -#: builtin/grep.c:995 +#: builtin/grep.c:889 msgid "--cached or --untracked cannot be used with --no-index." msgstr "--cached oder --untracked kann nicht mit --no-index benutzt werden" -#: builtin/grep.c:1000 +#: builtin/grep.c:894 msgid "--no-index or --untracked cannot be used with revs." msgstr "--no-index oder --untracked kann nicht mit Versionen benutzt werden" -#: builtin/grep.c:1003 +#: builtin/grep.c:897 msgid "--[no-]exclude-standard cannot be used for tracked contents." msgstr "" "--[no-]exlude-standard kann nicht mit beobachteten Inhalten benutzt werden" -#: builtin/grep.c:1011 +#: builtin/grep.c:905 msgid "both --cached and trees are given." msgstr "sowohl --cached als auch Zweige gegeben" +#: builtin/hash-object.c:60 +msgid "" +"git hash-object [-t ] [-w] [--path=|--no-filters] [--stdin] [--] " +"..." +msgstr "" +"git hash-object [-t ] [-w] [--path=|--no-filters] [--stdin] [--] " +"..." + +#: builtin/hash-object.c:61 +msgid "git hash-object --stdin-paths < " +msgstr "git hash-object --stdin-paths < " + +#: builtin/hash-object.c:72 +msgid "type" +msgstr "Art" + +#: builtin/hash-object.c:72 +msgid "object type" +msgstr "Art des Objektes" + +#: builtin/hash-object.c:73 +msgid "write the object into the object database" +msgstr "schreibt das Objekt in die Objektdatenbank" + +#: builtin/hash-object.c:74 +msgid "read the object from stdin" +msgstr "liest das Objekt von der Standard-Eingabe" + +#: builtin/hash-object.c:76 +msgid "store file as is without filters" +msgstr "speichert Datei wie sie ist, ohne Filter" + +#: builtin/hash-object.c:77 +msgid "process file as it were from this path" +msgstr "verarbeitet Datei, als ob sie von diesem Pfad wäre" + +#: builtin/help.c:43 +msgid "print all available commands" +msgstr "Anzeige aller vorhandenen Kommandos" + +#: builtin/help.c:44 +msgid "show man page" +msgstr "zeigt Handbuch" + +#: builtin/help.c:45 +msgid "show manual in web browser" +msgstr "zeigt Handbuch in einem Webbrowser" + +#: builtin/help.c:47 +msgid "show info page" +msgstr "zeigt Info-Seite" + +#: builtin/help.c:53 +msgid "git help [--all] [--man|--web|--info] [command]" +msgstr "git help [--all] [--man|--web|--info] [Kommando]" + #: builtin/help.c:65 #, c-format msgid "unrecognized help format '%s'" @@ -3285,7 +4918,7 @@ msgid "" "Please consider using 'man..cmd' instead." msgstr "" "'%s': Pfad für nicht unterstützten Handbuchbetrachter.\n" -"Du könntest stattdessen 'man..cmd' benutzen." +"Sie könnten stattdessen 'man..cmd' benutzen." #: builtin/help.c:229 #, c-format @@ -3294,7 +4927,7 @@ msgid "" "Please consider using 'man..path' instead." msgstr "" "'%s': Kommando für unterstützten Handbuchbetrachter.\n" -"Du könntest stattdessen 'man..path' benutzen." +"Sie könnten stattdessen 'man..path' benutzen." #: builtin/help.c:299 msgid "The most commonly used git commands are:" @@ -3369,6 +5002,11 @@ msgstr "Kann Paketdatei '%s' nicht öffnen" msgid "pack signature mismatch" msgstr "Paketsignatur stimmt nicht überein" +#: builtin/index-pack.c:294 +#, c-format +msgid "pack version % unsupported" +msgstr "Paketversion % nicht unterstützt" + #: builtin/index-pack.c:312 #, c-format msgid "pack has bad object at offset %lu: %s" @@ -3476,93 +5114,123 @@ msgstr "Fehler beim Ausführen von \"parse_pack_objects()\"" msgid "Resolving deltas" msgstr "Löse Unterschiede auf" -#: builtin/index-pack.c:1105 +#: builtin/index-pack.c:1064 +#, c-format +msgid "unable to create thread: %s" +msgstr "kann Thread nicht erzeugen: %s" + +#: builtin/index-pack.c:1106 msgid "confusion beyond insanity" msgstr "Fehler beim Auflösen der Unterschiede" -#: builtin/index-pack.c:1124 +#: builtin/index-pack.c:1112 +#, c-format +msgid "completed with %d local objects" +msgstr "vervollständigt mit %d lokalen Objekten" + +#: builtin/index-pack.c:1121 +#, c-format +msgid "Unexpected tail checksum for %s (disk corruption?)" +msgstr "Unerwartete Prüfsumme für %s (Festplattenfehler?)" + +#: builtin/index-pack.c:1125 #, c-format msgid "pack has %d unresolved delta" msgid_plural "pack has %d unresolved deltas" msgstr[0] "Paket hat %d unaufgelöste Unterschied" msgstr[1] "Paket hat %d unaufgelöste Unterschiede" -#: builtin/index-pack.c:1149 +#: builtin/index-pack.c:1150 #, c-format msgid "unable to deflate appended object (%d)" msgstr "Konnte angehängtes Objekt (%d) nicht komprimieren" -#: builtin/index-pack.c:1228 +#: builtin/index-pack.c:1229 #, c-format msgid "local object %s is corrupt" msgstr "lokales Objekt %s ist beschädigt" -#: builtin/index-pack.c:1252 +#: builtin/index-pack.c:1253 msgid "error while closing pack file" msgstr "Fehler beim Schließen der Paketdatei" -#: builtin/index-pack.c:1265 +#: builtin/index-pack.c:1266 #, c-format msgid "cannot write keep file '%s'" msgstr "Kann Paketbeschreibungsdatei '%s' nicht schreiben" -#: builtin/index-pack.c:1273 +#: builtin/index-pack.c:1274 #, c-format msgid "cannot close written keep file '%s'" msgstr "Kann eben erstellte Paketbeschreibungsdatei '%s' nicht schließen" -#: builtin/index-pack.c:1286 +#: builtin/index-pack.c:1287 msgid "cannot store pack file" msgstr "Kann Paketdatei nicht speichern" -#: builtin/index-pack.c:1297 +#: builtin/index-pack.c:1298 msgid "cannot store index file" msgstr "Kann Indexdatei nicht speichern" -#: builtin/index-pack.c:1398 +#: builtin/index-pack.c:1331 +#, c-format +msgid "bad pack.indexversion=%" +msgstr "\"pack.indexversion=%\" ist ungültig" + +#: builtin/index-pack.c:1337 +#, c-format +msgid "invalid number of threads specified (%d)" +msgstr "ungültige Anzahl von Threads angegeben (%d)" + +#: builtin/index-pack.c:1341 builtin/index-pack.c:1514 +#, c-format +msgid "no threads support, ignoring %s" +msgstr "keine Unterstützung von Threads, '%s' wird ignoriert" + +#: builtin/index-pack.c:1399 #, c-format msgid "Cannot open existing pack file '%s'" msgstr "Kann existierende Paketdatei '%s' nicht öffnen" -#: builtin/index-pack.c:1400 +#: builtin/index-pack.c:1401 #, c-format msgid "Cannot open existing pack idx file for '%s'" msgstr "Kann existierende Indexdatei für Paket '%s' nicht öffnen" -#: builtin/index-pack.c:1447 +#: builtin/index-pack.c:1448 #, c-format msgid "non delta: %d object" msgid_plural "non delta: %d objects" msgstr[0] "kein Unterschied: %d Objekt" msgstr[1] "kein Unterschied: %d Objekte" -#: builtin/index-pack.c:1454 +#: builtin/index-pack.c:1455 #, c-format msgid "chain length = %d: %lu object" msgid_plural "chain length = %d: %lu objects" msgstr[0] "Länge der Objekt-Liste = %d: %lu Objekt" msgstr[1] "Länge der Objekt-Liste = %d: %lu Objekte" -#: builtin/index-pack.c:1481 +#: builtin/index-pack.c:1482 msgid "Cannot come back to cwd" msgstr "Kann nicht zurück zu Arbeitsverzeichnis wechseln" -#: builtin/index-pack.c:1525 builtin/index-pack.c:1528 -#: builtin/index-pack.c:1540 builtin/index-pack.c:1544 +#: builtin/index-pack.c:1526 builtin/index-pack.c:1529 +#: builtin/index-pack.c:1541 builtin/index-pack.c:1545 #, c-format msgid "bad %s" msgstr "%s ist ungültig" -#: builtin/index-pack.c:1558 +#: builtin/index-pack.c:1559 msgid "--fix-thin cannot be used without --stdin" msgstr "--fix-thin kann nicht ohne --stdin benutzt werden" -#: builtin/index-pack.c:1562 builtin/index-pack.c:1572 +#: builtin/index-pack.c:1563 builtin/index-pack.c:1573 #, c-format msgid "packfile name '%s' does not end with '.pack'" msgstr "Name der Paketdatei '%s' endet nicht mit '.pack'" -#: builtin/index-pack.c:1581 +#: builtin/index-pack.c:1582 msgid "--verify with no packfile name given" msgstr "--verify ohne Name der Paketdatei angegeben" @@ -3682,6 +5350,27 @@ msgstr " gemeinsames" msgid "cannot tell cwd" msgstr "kann aktuelles Arbeitsverzeichnis nicht ermitteln" +#: builtin/init-db.c:467 +msgid "" +"git init [-q | --quiet] [--bare] [--template=] [--shared" +"[=]] [directory]" +msgstr "" +"git init [-q | --quiet] [--bare] [--template=] [--shared" +"[=]] [Verzeichnis]" + +#: builtin/init-db.c:490 +msgid "permissions" +msgstr "Berechtigungen" + +#: builtin/init-db.c:491 +msgid "specify that the git repository is to be shared amongst several users" +msgstr "" +"gibt an, dass das Git-Projektarchiv mit mehreren Benutzern geteilt wird" + +#: builtin/init-db.c:493 builtin/prune-packed.c:77 +msgid "be quiet" +msgstr "weniger Ausgaben" + #: builtin/init-db.c:522 builtin/init-db.c:529 #, c-format msgid "cannot mkdir %s" @@ -3710,105 +5399,396 @@ msgstr "Kann nicht auf aktuelles Arbeitsverzeichnis zugreifen." msgid "Cannot access work tree '%s'" msgstr "Kann nicht auf Arbeitsbaum '%s' zugreifen." +#: builtin/log.c:37 +msgid "git log [] [..] [[--] ...]\n" +msgstr "git log [] [..] [[--] ...]\n" + +#: builtin/log.c:38 +msgid " or: git show [options] ..." +msgstr " oder: git show [Optionen] ..." + +#: builtin/log.c:100 +msgid "suppress diff output" +msgstr "unterdrückt Ausgabe der Unterschiede" + +#: builtin/log.c:101 +msgid "show source" +msgstr "zeigt Quelle" + +#: builtin/log.c:102 +msgid "decorate options" +msgstr "decorate-Optionen" + #: builtin/log.c:189 #, c-format msgid "Final output: %d %s\n" msgstr "letzte Ausgabe: %d %s\n" -#: 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 "Kann Objekt %s nicht lesen." -#: builtin/log.c:518 +#: builtin/log.c:521 #, c-format msgid "Unknown type: %d" msgstr "Unbekannter Typ: %d" -#: builtin/log.c:608 +#: builtin/log.c:613 msgid "format.headers without value" msgstr "format.headers ohne Wert" -#: builtin/log.c:682 +#: builtin/log.c:687 msgid "name of output directory is too long" msgstr "Name des Ausgabeverzeichnisses ist zu lang." -#: builtin/log.c:693 +#: builtin/log.c:698 #, c-format msgid "Cannot open patch file %s" msgstr "Kann Patch-Datei %s nicht öffnen" -#: builtin/log.c:707 +#: builtin/log.c:712 msgid "Need exactly one range." msgstr "Brauche genau einen Versionsbereich." -#: builtin/log.c:715 +#: builtin/log.c:720 msgid "Not a range." msgstr "Kein Versionsbereich." -#: builtin/log.c:789 +#: builtin/log.c:794 msgid "Cover letter needs email format" msgstr "Anschreiben benötigt E-Mail-Format" -#: builtin/log.c:862 +#: builtin/log.c:867 #, c-format msgid "insane in-reply-to: %s" msgstr "ungültiges in-reply-to: %s" -#: builtin/log.c:935 +#: builtin/log.c:895 +msgid "git format-patch [options] [ | ]" +msgstr "git format-patch [Optionen] [ | ]" + +#: builtin/log.c:940 msgid "Two output directories?" msgstr "Zwei Ausgabeverzeichnisse?" -#: builtin/log.c:1157 -#, c-format -msgid "bogus committer info %s" -msgstr "unechte Einreicher-Informationen %s" +#: builtin/log.c:1068 +msgid "use [PATCH n/m] even with a single patch" +msgstr "benutzt [PATCH n/m] auch mit einzelnem Patch" + +#: builtin/log.c:1071 +msgid "use [PATCH] even with multiple patches" +msgstr "benutzt [PATCH] auch mit mehreren Patches" + +#: builtin/log.c:1075 +msgid "print patches to standard out" +msgstr "Ausgabe der Patches in Standard-Ausgabe" + +#: builtin/log.c:1077 +msgid "generate a cover letter" +msgstr "erzeugt ein Deckblatt" + +#: builtin/log.c:1079 +msgid "use simple number sequence for output file names" +msgstr "benutzt einfache Nummernfolge für die Namen der Ausgabedateien" + +#: builtin/log.c:1080 +msgid "sfx" +msgstr "Dateiendung" + +#: builtin/log.c:1081 +msgid "use instead of '.patch'" +msgstr "verwendet anstatt '.patch'" + +#: builtin/log.c:1083 +msgid "start numbering patches at instead of 1" +msgstr "beginnt die Nummerierung der Patches bei anstatt bei 1" + +#: builtin/log.c:1085 +msgid "Use [] instead of [PATCH]" +msgstr "benutzt [] anstatt [PATCH]" + +#: builtin/log.c:1088 +msgid "store resulting files in " +msgstr "speichert erzeugte Dateien in " + +#: builtin/log.c:1091 +msgid "don't strip/add [PATCH]" +msgstr "[PATCH] wird nicht entfernt/hinzugefügt" + +#: builtin/log.c:1094 +msgid "don't output binary diffs" +msgstr "gibt keine binären Unterschiede aus" + +#: builtin/log.c:1096 +msgid "don't include a patch matching a commit upstream" +msgstr "" +"schließt keine Patches ein, die einer Version im Übernahmezweig entsprechen" + +#: builtin/log.c:1098 +msgid "show patch format instead of default (patch + stat)" +msgstr "zeigt Patchformat anstatt des Standards (Patch + Zusammenfassung)" + +#: builtin/log.c:1100 +msgid "Messaging" +msgstr "Email-Einstellungen" + +#: builtin/log.c:1101 +msgid "header" +msgstr "Header" + +#: builtin/log.c:1102 +msgid "add email header" +msgstr "fügt Email-Header hinzu" + +#: builtin/log.c:1103 builtin/log.c:1105 +msgid "email" +msgstr "Email" + +#: builtin/log.c:1103 +msgid "add To: header" +msgstr "fügt \"To:\"-Header hinzu" + +#: builtin/log.c:1105 +msgid "add Cc: header" +msgstr "fügt \"Cc:\"-Header hinzu" + +#: builtin/log.c:1107 +msgid "message-id" +msgstr "message-id" + +#: builtin/log.c:1108 +msgid "make first mail a reply to " +msgstr "macht aus erster Email eine Antwort zu " + +#: builtin/log.c:1109 builtin/log.c:1112 +msgid "boundary" +msgstr "Grenze" + +#: builtin/log.c:1110 +msgid "attach the patch" +msgstr "hängt einen Patch an" + +#: builtin/log.c:1113 +msgid "inline the patch" +msgstr "fügt den Patch direkt in die Nachricht ein" + +#: builtin/log.c:1117 +msgid "enable message threading, styles: shallow, deep" +msgstr "aktiviert Nachrichtenverkettung, Stile: shallow, deep" + +#: builtin/log.c:1119 +msgid "signature" +msgstr "Signatur" + +#: builtin/log.c:1120 +msgid "add a signature" +msgstr "fügt eine Signatur hinzu" + +#: builtin/log.c:1122 +msgid "don't print the patch filenames" +msgstr "zeigt keine Dateinamen der Patches" + +#: builtin/log.c:1163 +#, c-format +msgid "bogus committer info %s" +msgstr "unechte Einreicher-Informationen %s" + +#: builtin/log.c:1208 +msgid "-n and -k are mutually exclusive." +msgstr "-n und -k schließen sich gegenseitig aus" + +#: builtin/log.c:1210 +msgid "--subject-prefix and -k are mutually exclusive." +msgstr "--subject-prefix und -k schließen sich gegenseitig aus" + +#: builtin/log.c:1218 +msgid "--name-only does not make sense" +msgstr "--name-only macht keinen Sinn" + +#: builtin/log.c:1220 +msgid "--name-status does not make sense" +msgstr "--name-status macht keinen Sinn" + +#: builtin/log.c:1222 +msgid "--check does not make sense" +msgstr "--check macht keinen Sinn" + +#: builtin/log.c:1245 +msgid "standard output, or directory, which one?" +msgstr "Standard-Ausgabe oder Verzeichnis, welches von beidem?" + +#: builtin/log.c:1247 +#, c-format +msgid "Could not create directory '%s'" +msgstr "Konnte Verzeichnis '%s' nicht erstellen." + +#: builtin/log.c:1400 +msgid "Failed to create output files" +msgstr "Fehler beim Erstellen der Ausgabedateien." + +#: builtin/log.c:1449 +msgid "git cherry [-v] [ [ []]]" +msgstr "git cherry [-v] [<Übernahmezweig> [ []]]" + +#: builtin/log.c:1504 +#, c-format +msgid "" +"Could not find a tracked remote branch, please specify manually.\n" +msgstr "" +"Konnte gefolgten, externen Zweig nicht finden, bitte geben Sie " +"manuell an.\n" + +#: builtin/log.c:1517 builtin/log.c:1519 builtin/log.c:1531 +#, c-format +msgid "Unknown commit %s" +msgstr "Unbekannte Version %s" + +#: builtin/ls-files.c:408 +msgid "git ls-files [options] [...]" +msgstr "git ls-files [Optionen] [...]" + +#: builtin/ls-files.c:463 +msgid "identify the file status with tags" +msgstr "zeigt den Dateistatus mit Markierungen" + +#: builtin/ls-files.c:465 +msgid "use lowercase letters for 'assume unchanged' files" +msgstr "benutzt Kleinbuchstaben für Dateien mit 'assume unchanged' Markierung" + +#: builtin/ls-files.c:467 +msgid "show cached files in the output (default)" +msgstr "zeigt zwischengespeicherten Dateien in der Ausgabe an (Standard)" + +#: builtin/ls-files.c:469 +msgid "show deleted files in the output" +msgstr "zeigt entfernte Dateien in der Ausgabe an" + +#: builtin/ls-files.c:471 +msgid "show modified files in the output" +msgstr "zeigt geänderte Dateien in der Ausgabe an" + +#: builtin/ls-files.c:473 +msgid "show other files in the output" +msgstr "zeigt sonstige Dateien in der Ausgabe an" + +#: builtin/ls-files.c:475 +msgid "show ignored files in the output" +msgstr "zeigt ignorierte Dateien in der Ausgabe an" + +#: builtin/ls-files.c:478 +msgid "show staged contents' object name in the output" +msgstr "zeigt Objektnamen von Inhalten in der Bereitstellung in der Ausgabe an" + +#: builtin/ls-files.c:480 +msgid "show files on the filesystem that need to be removed" +msgstr "zeigt Dateien im Dateisystem, die gelöscht werden müssen, an" + +#: builtin/ls-files.c:482 +msgid "show 'other' directories' name only" +msgstr "zeigt nur Namen von 'sonstigen' Verzeichnissen an" + +#: builtin/ls-files.c:485 +msgid "don't show empty directories" +msgstr "zeigt keine leeren Verzeichnisse an" + +#: builtin/ls-files.c:488 +msgid "show unmerged files in the output" +msgstr "zeigt nicht zusammengeführte Dateien in der Ausgabe an" + +#: builtin/ls-files.c:490 +msgid "show resolve-undo information" +msgstr "zeigt 'resolve-undo' Informationen an" + +#: builtin/ls-files.c:492 +msgid "skip files matching pattern" +msgstr "lässt Dateien aus, die einem Muster entsprechen" + +#: builtin/ls-files.c:495 +msgid "exclude patterns are read from " +msgstr "schließt Muster, gelesen von , aus" + +#: builtin/ls-files.c:498 +msgid "read additional per-directory exclude patterns in " +msgstr "liest zusätzliche pro-Verzeichnis Auschlussmuster aus " + +#: builtin/ls-files.c:500 +msgid "add the standard git exclusions" +msgstr "fügt die standardmäßigen Git-Ausschlüsse hinzu" + +#: builtin/ls-files.c:503 +msgid "make the output relative to the project top directory" +msgstr "Ausgabe relativ zum Projektverzeichnis" + +#: builtin/ls-files.c:506 +msgid "if any is not in the index, treat this as an error" +msgstr "" +"behandle es als Fehler, wenn sich eine nicht in der Bereitstellung " +"befindet" + +#: builtin/ls-files.c:507 +msgid "tree-ish" +msgstr "Versionsreferenz" + +#: builtin/ls-files.c:508 +msgid "pretend that paths removed since are still present" +msgstr "" +"gibt vor, dass Pfade, die seit gelöscht wurden, immer " +"noch vorhanden sind" + +#: builtin/ls-files.c:510 +msgid "show debugging data" +msgstr "zeigt Ausgaben zur Fehlersuche an" -#: builtin/log.c:1202 -msgid "-n and -k are mutually exclusive." -msgstr "-n und -k schließen sich gegenseitig aus" +#: builtin/ls-tree.c:27 +msgid "git ls-tree [] [...]" +msgstr "git ls-tree [] [...]" -#: builtin/log.c:1204 -msgid "--subject-prefix and -k are mutually exclusive." -msgstr "--subject-prefix und -k schließen sich gegenseitig aus" +#: builtin/ls-tree.c:125 +msgid "only show trees" +msgstr "zeigt nur Bäume an" -#: builtin/log.c:1212 -msgid "--name-only does not make sense" -msgstr "--name-only macht keinen Sinn" +#: builtin/ls-tree.c:127 +msgid "recurse into subtrees" +msgstr "führt Rekursion in Teilbäumen durch" -#: builtin/log.c:1214 -msgid "--name-status does not make sense" -msgstr "--name-status macht keinen Sinn" +#: builtin/ls-tree.c:129 +msgid "show trees when recursing" +msgstr "zeigt Bäume bei Rekursion an" -#: builtin/log.c:1216 -msgid "--check does not make sense" -msgstr "--check macht keinen Sinn" +#: builtin/ls-tree.c:132 +msgid "terminate entries with NUL byte" +msgstr "schließt Einträge mit NUL-Byte ab" -#: builtin/log.c:1239 -msgid "standard output, or directory, which one?" -msgstr "Standard-Ausgabe oder Verzeichnis, welches von beidem?" +#: builtin/ls-tree.c:133 +msgid "include object size" +msgstr "schließt Objektgröße ein" -#: builtin/log.c:1241 -#, c-format -msgid "Could not create directory '%s'" -msgstr "Konnte Verzeichnis '%s' nicht erstellen." +#: builtin/ls-tree.c:135 builtin/ls-tree.c:137 +msgid "list only filenames" +msgstr "listet nur Dateinamen auf" -#: builtin/log.c:1394 -msgid "Failed to create output files" -msgstr "Fehler beim Erstellen der Ausgabedateien." +#: builtin/ls-tree.c:140 +msgid "use full path names" +msgstr "benutzt vollständige Pfadnamen" -#: builtin/log.c:1498 -#, c-format -msgid "" -"Could not find a tracked remote branch, please specify manually.\n" +#: builtin/ls-tree.c:142 +msgid "list entire tree; not just current directory (implies --full-name)" msgstr "" -"Konnte gefolgten, externen Zweig nicht finden, bitte gebe manuell " -"an.\n" +"listet den gesamten Baum auf; nicht nur das aktuelle Verzeichnis (impliziert " +"--full-name)" -#: builtin/log.c:1511 builtin/log.c:1513 builtin/log.c:1525 -#, c-format -msgid "Unknown commit %s" -msgstr "Unbekannte Version %s" +#: builtin/merge.c:43 +msgid "git merge [options] [...]" +msgstr "git merge [Optionen] [...]" + +#: builtin/merge.c:44 +msgid "git merge [options] HEAD " +msgstr "git merge [Optionen] HEAD " + +#: builtin/merge.c:45 +msgid "git merge --abort" +msgstr "git merge --abort" #: builtin/merge.c:90 msgid "switch `m' requires a value" @@ -3829,6 +5809,75 @@ msgstr "Verfügbare Strategien sind:" msgid "Available custom strategies are:" msgstr "Verfügbare benutzerdefinierte Strategien sind:" +#: builtin/merge.c:183 +msgid "do not show a diffstat at the end of the merge" +msgstr "" +"zeigt keine Zusammenfassung der Unterschiede am Schluss der Zusammenführung " +"an" + +#: builtin/merge.c:186 +msgid "show a diffstat at the end of the merge" +msgstr "" +"zeigt eine Zusammenfassung der Unterschiede am Schluss der Zusammenführung an" + +#: builtin/merge.c:187 +msgid "(synonym to --stat)" +msgstr "(Synonym für --stat)" + +#: builtin/merge.c:189 +msgid "add (at most ) entries from shortlog to merge commit message" +msgstr "" +"fügt (höchstens ) Einträge von \"shortlog\" zur Beschreibung der " +"Zusammenführung hinzu" + +#: builtin/merge.c:192 +msgid "create a single commit instead of doing a merge" +msgstr "erzeugt eine einzelne Version anstatt einer Zusammenführung" + +#: builtin/merge.c:194 +msgid "perform a commit if the merge succeeds (default)" +msgstr "" +"führt eine Eintragung durch, wenn die Zusammenführung erfolgreich war " +"(Standard)" + +#: builtin/merge.c:196 +msgid "edit message before committing" +msgstr "Bearbeitung der Versionsbeschreibung vor der Eintragung" + +#: builtin/merge.c:198 +msgid "allow fast-forward (default)" +msgstr "erlaubt Vorspulen (Standard)" + +#: builtin/merge.c:200 +msgid "abort if fast-forward is not possible" +msgstr "bricht ab, wenn kein Vorspulen möglich ist" + +#: builtin/merge.c:202 builtin/notes.c:870 builtin/revert.c:112 +msgid "strategy" +msgstr "Strategie" + +#: builtin/merge.c:203 +msgid "merge strategy to use" +msgstr "zu verwendende Zusammenführungsstrategie" + +#: builtin/merge.c:204 +msgid "option=value" +msgstr "Option=Wert" + +#: builtin/merge.c:205 +msgid "option for selected merge strategy" +msgstr "Option für ausgewählte Zusammenführungsstrategie" + +#: builtin/merge.c:207 +msgid "merge commit message (for a non-fast-forward merge)" +msgstr "" +"führt Versionsbeschreibung zusammen (für eine Zusammenführung, die kein " +"Vorspulen war)" + +#: builtin/merge.c:211 +msgid "abort the current in-progress merge" +msgstr "bricht die sich im Gange befindliche Zusammenführung ab" + #: builtin/merge.c:240 msgid "could not run stash." msgstr "Konnte \"stash\" nicht ausführen." @@ -3884,37 +5933,33 @@ msgstr "Ungültiger branch.%s.mergeoptions String: %s" msgid "git write-tree failed to write a tree" msgstr "\"git write-tree\" schlug beim Schreiben eines Baumes fehl" -#: builtin/merge.c:678 -msgid "failed to read the cache" -msgstr "Lesen des Zwischenspeichers fehlgeschlagen" - -#: builtin/merge.c:709 +#: builtin/merge.c:656 msgid "Not handling anything other than two heads merge." msgstr "Es wird nur die Zusammenführung von zwei Zweigen behandelt." -#: builtin/merge.c:723 +#: builtin/merge.c:670 #, c-format msgid "Unknown option for merge-recursive: -X%s" msgstr "Unbekannte Option für merge-recursive: -X%s" -#: builtin/merge.c:737 +#: builtin/merge.c:684 #, c-format msgid "unable to write %s" msgstr "konnte %s nicht schreiben" -#: builtin/merge.c:876 +#: builtin/merge.c:773 #, c-format msgid "Could not read from '%s'" msgstr "konnte nicht von '%s' lesen" -#: builtin/merge.c:885 +#: builtin/merge.c:782 #, c-format msgid "Not committing merge; use 'git commit' to complete the merge.\n" msgstr "" -"Zusammenführung wurde nicht eingetragen; benutze 'git commit' um die " +"Zusammenführung wurde nicht eingetragen; benutzen Sie 'git commit' um die " "Zusammenführung abzuschließen.\n" -#: 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" @@ -3922,7 +5967,7 @@ msgid "" "Lines starting with '#' will be ignored, and an empty message aborts\n" "the commit.\n" msgstr "" -"Bitte gebe eine Versionsbeschreibung ein um zu erklären, warum diese " +"Bitte geben Sie eine Versionsbeschreibung ein um zu erklären, warum diese " "Zusammenführung erforderlich ist,\n" "insbesondere wenn es einen aktualisierten, externen Zweig mit einem Thema-" "Zweig zusammenführt.\n" @@ -3930,156 +5975,256 @@ msgstr "" "Zeilen beginnend mit '#' werden ignoriert, und eine leere Beschreibung " "bricht die Eintragung ab.\n" -#: builtin/merge.c:915 +#: builtin/merge.c:812 msgid "Empty commit message." msgstr "Leere Versionsbeschreibung" -#: builtin/merge.c:927 +#: builtin/merge.c:824 #, c-format msgid "Wonderful.\n" msgstr "Wunderbar.\n" -#: builtin/merge.c:992 +#: builtin/merge.c:889 #, c-format msgid "Automatic merge failed; fix conflicts and then commit the result.\n" msgstr "" -"Automatische Zusammenführung fehlgeschlagen; behebe die Konflikte und trage " -"dann das Ergebnis ein.\n" +"Automatische Zusammenführung fehlgeschlagen; beheben Sie die Konflikte und tragen " +"Sie dann das Ergebnis ein.\n" -#: builtin/merge.c:1008 +#: builtin/merge.c:905 #, c-format msgid "'%s' is not a commit" msgstr "'%s' ist keine Version" -#: builtin/merge.c:1049 +#: builtin/merge.c:946 msgid "No current branch." -msgstr "Du befindest dich auf keinem Zweig." +msgstr "Sie befinden sich auf keinem Zweig." -#: builtin/merge.c:1051 +#: builtin/merge.c:948 msgid "No remote for the current branch." msgstr "Kein externes Archiv für den aktuellen Zweig." -#: builtin/merge.c:1053 +#: builtin/merge.c:950 msgid "No default upstream defined for the current branch." msgstr "" "Es ist kein externes Standard-Projektarchiv für den aktuellen Zweig " "definiert." -#: builtin/merge.c:1058 +#: builtin/merge.c:955 #, c-format msgid "No remote tracking branch for %s from %s" msgstr "Kein externer Übernahmezweig für %s von %s" -#: 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 "%s - nichts was wir zusammenführen können" -#: builtin/merge.c:1213 +#: builtin/merge.c:1110 msgid "There is no merge to abort (MERGE_HEAD missing)." msgstr "Es gibt keine Zusammenführung zum Abbrechen (vermisse MERGE_HEAD)" -#: 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 "" -"Du hast deine Zusammenführung nicht abgeschlossen (MERGE_HEAD existiert).\n" -"Bitte trage deine Änderungen ein, bevor du zusammenführen kannst." +"Sie haben Ihre Zusammenführung nicht abgeschlossen (MERGE_HEAD existiert).\n" +"Bitte tragen Sie Ihre Änderungen ein, bevor Sie zusammenführen können." -#: 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 "" -"Du hast deine Zusammenführung nicht abgeschlossen (MERGE_HEAD existiert)." +"Sie haben Ihre Zusammenführung nicht abgeschlossen (MERGE_HEAD existiert)." -#: 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 "" -"Du hast \"cherry-pick\" nicht abgeschlossen (CHERRY_PICK_HEAD existiert).\n" -"Bitte trage deine Änderungen ein, bevor du zusammenführen kannst." +"Sie haben \"cherry-pick\" nicht abgeschlossen (CHERRY_PICK_HEAD existiert).\n" +"Bitte tragen Sie Ihre Änderungen ein, bevor Sie zusammenführen können." -#: builtin/merge.c:1239 +#: builtin/merge.c:1136 msgid "You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists)." msgstr "" -"Du hast \"cherry-pick\" nicht abgeschlossen (CHERRY_PICK_HEAD existiert)." +"Sie haben \"cherry-pick\" nicht abgeschlossen (CHERRY_PICK_HEAD existiert)." -#: builtin/merge.c:1248 +#: builtin/merge.c:1145 msgid "You cannot combine --squash with --no-ff." -msgstr "Du kannst --squash nicht mit --no-ff kombinieren." +msgstr "Sie können --squash nicht mit --no-ff kombinieren." -#: builtin/merge.c:1253 +#: builtin/merge.c:1150 msgid "You cannot combine --no-ff with --ff-only." -msgstr "Du kannst --no-ff nicht mit --ff--only kombinieren." +msgstr "Sie können --no-ff nicht mit --ff--only kombinieren." -#: builtin/merge.c:1260 +#: builtin/merge.c:1157 msgid "No commit specified and merge.defaultToUpstream not set." msgstr "Keine Version angegeben und merge.defaultToUpstream ist nicht gesetzt." -#: builtin/merge.c:1292 +#: builtin/merge.c:1189 msgid "Can merge only exactly one commit into empty head" msgstr "Kann nur exakt eine Version in einem leeren Zweig zusammenführen." -#: builtin/merge.c:1295 +#: builtin/merge.c:1192 msgid "Squash commit into empty head not supported yet" msgstr "Bin auf einem Zweig, der noch geboren wird; kann nicht quetschen." -#: builtin/merge.c:1297 +#: builtin/merge.c:1194 msgid "Non-fast-forward commit does not make sense into an empty head" msgstr "nicht vorzuspulende Version macht in einem leeren Zweig keinen Sinn" -#: builtin/merge.c:1412 +#: builtin/merge.c:1309 #, c-format msgid "Updating %s..%s\n" msgstr "Aktualisiere %s..%s\n" -#: builtin/merge.c:1450 +#: builtin/merge.c:1348 #, c-format msgid "Trying really trivial in-index merge...\n" msgstr "Probiere wirklich triviale \"in-index\"-Zusammenführung...\n" -#: builtin/merge.c:1457 +#: builtin/merge.c:1355 #, c-format msgid "Nope.\n" msgstr "Nein.\n" -#: builtin/merge.c:1489 +#: builtin/merge.c:1387 msgid "Not possible to fast-forward, aborting." msgstr "Vorspulen nicht möglich, breche ab." -#: 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 "Rücklauf des Zweiges bis zum Ursprung...\n" -#: builtin/merge.c:1516 +#: builtin/merge.c:1414 #, c-format msgid "Trying merge strategy %s...\n" msgstr "Probiere Zusammenführungsstrategie %s...\n" -#: builtin/merge.c:1582 +#: builtin/merge.c:1480 #, c-format msgid "No merge strategy handled the merge.\n" msgstr "Keine Zusammenführungsstrategie behandelt diese Zusammenführung.\n" -#: builtin/merge.c:1584 +#: builtin/merge.c:1482 #, c-format msgid "Merge with strategy %s failed.\n" msgstr "Zusammenführung mit Strategie %s fehlgeschlagen.\n" -#: builtin/merge.c:1593 +#: builtin/merge.c:1491 #, c-format msgid "Using the %s to prepare resolving by hand.\n" -msgstr "Benutze \"%s\" um die Auflösung per Hand vorzubereiten.\n" +msgstr "Benutzen Sie \"%s\" um die Auflösung per Hand vorzubereiten.\n" -#: builtin/merge.c:1605 +#: builtin/merge.c:1503 #, c-format msgid "Automatic merge went well; stopped before committing as requested\n" msgstr "" "Automatische Zusammenführung abgeschlossen; halte, wie gewünscht, vor der " "Eintragung an\n" +#: builtin/merge-base.c:26 +msgid "git merge-base [-a|--all] ..." +msgstr "git merge-base [-a|--all] ..." + +#: builtin/merge-base.c:27 +msgid "git merge-base [-a|--all] --octopus ..." +msgstr "git merge-base [-a|--all] --octopus ..." + +#: builtin/merge-base.c:28 +msgid "git merge-base --independent ..." +msgstr "git merge-base --independent ..." + +#: builtin/merge-base.c:29 +msgid "git merge-base --is-ancestor " +msgstr "git merge-base --is-ancestor " + +#: builtin/merge-base.c:98 +msgid "output all common ancestors" +msgstr "Ausgabe aller gemeinsamen Vorfahren" + +#: builtin/merge-base.c:99 +msgid "find ancestors for a single n-way merge" +msgstr "findet Vorfahren für eine einzelne n-Wege-Zusammenführung" + +#: builtin/merge-base.c:100 +msgid "list revs not reachable from others" +msgstr "listet Revisionen auf, die nicht durch Andere erreichbar sind" + +#: builtin/merge-base.c:102 +msgid "is the first one ancestor of the other?" +msgstr "ist der Erste ein Vorfahre von dem Anderen?" + +#: builtin/merge-file.c:8 +msgid "" +"git merge-file [options] [-L name1 [-L orig [-L name2]]] file1 orig_file " +"file2" +msgstr "" +"git merge-file [Optionen] [-L Name1 [-L orig [-L Name2]]] Datei1 orig_Datei " +"Datei2" + +#: builtin/merge-file.c:33 +msgid "send results to standard output" +msgstr "sendet Ergebnisse zur Standard-Ausgabe" + +#: builtin/merge-file.c:34 +msgid "use a diff3 based merge" +msgstr "benutzt eine diff3 basierte Zusammenführung" + +#: builtin/merge-file.c:35 +msgid "for conflicts, use our version" +msgstr "verwendet bei Konflikten unsere Variante" + +#: builtin/merge-file.c:37 +msgid "for conflicts, use their version" +msgstr "verwendet bei Konflikten ihre Variante" + +#: builtin/merge-file.c:39 +msgid "for conflicts, use a union version" +msgstr "verwendet bei Konflikten eine gemeinsame Variante" + +#: builtin/merge-file.c:42 +msgid "for conflicts, use this marker size" +msgstr "verwendet bei Konflikten diese Kennzeichnungslänge" + +#: builtin/merge-file.c:43 +msgid "do not warn about conflicts" +msgstr "keine Warnung bei Konflikten" + +#: builtin/merge-file.c:45 +msgid "set labels for file1/orig_file/file2" +msgstr "setzt Beschriftung für Datei1/orig_Datei/Datei2" + +#: builtin/mktree.c:67 +msgid "git mktree [-z] [--missing] [--batch]" +msgstr "git mktree [-z] [--missing] [--batch]" + +#: builtin/mktree.c:153 +msgid "input is NUL terminated" +msgstr "Eingabe ist durch NUL-Zeichen abgeschlossen" + +#: builtin/mktree.c:154 builtin/write-tree.c:24 +msgid "allow missing objects" +msgstr "erlaubt fehlende Objekte" + +#: builtin/mktree.c:155 +msgid "allow creation of more than one tree" +msgstr "erlaubt die Erstellung von mehr als einem Baum" + +#: builtin/mv.c:14 +msgid "git mv [options] ... " +msgstr "git mv [Optionen] ... " + +#: builtin/mv.c:64 +msgid "force move/rename even if target exists" +msgstr "erzwingt Verschieben/Umbenennen, auch wenn das Ziel existiert" + +#: builtin/mv.c:65 +msgid "skip move/rename errors" +msgstr "überspringt Fehler beim Verschieben oder Umbenennen" + #: builtin/mv.c:108 #, c-format msgid "Checking rename of '%s' to '%s'\n" @@ -4142,190 +6287,596 @@ msgstr "Benenne %s nach %s um\n" msgid "renaming '%s' failed" msgstr "Umbenennung von '%s' fehlgeschlagen" -#: builtin/notes.c:139 +#: builtin/name-rev.c:175 +msgid "git name-rev [options] ..." +msgstr "git name-rev [Optionen] ..." + +#: builtin/name-rev.c:176 +msgid "git name-rev [options] --all" +msgstr "git name-rev [Optionen] --all" + +#: builtin/name-rev.c:177 +msgid "git name-rev [options] --stdin" +msgstr "git name-rev [Optionen] --stdin" + +#: builtin/name-rev.c:229 +msgid "print only names (no SHA-1)" +msgstr "zeigt nur Namen an (keine SHA-1)" + +#: builtin/name-rev.c:230 +msgid "only use tags to name the commits" +msgstr "benutzt nur Markierungen um die Versionen zu benennen" + +#: builtin/name-rev.c:232 +msgid "only use refs matching " +msgstr "benutzt nur Referenzen die entsprechen" + +#: builtin/name-rev.c:234 +msgid "list all commits reachable from all refs" +msgstr "listet alle Versionen auf, die von allen Referenzen erreichbar sind" + +#: builtin/name-rev.c:235 +msgid "read from stdin" +msgstr "liest von der Standard-Eingabe" + +#: builtin/name-rev.c:236 +msgid "allow to print `undefined` names" +msgstr "erlaubt Ausgabe von `undefinierten` Namen" + +#: builtin/notes.c:26 +msgid "git notes [--ref ] [list []]" +msgstr "git notes [--ref ] [list []]" + +#: builtin/notes.c:27 +msgid "" +"git notes [--ref ] add [-f] [-m | -F | (-c | -C) " +"] []" +msgstr "" +"git notes [--ref ] add [-f] [-m | -F | (-" +"c | -C) ] []" + +#: builtin/notes.c:28 +msgid "git notes [--ref ] copy [-f] " +msgstr "" +"git notes [--ref ] copy [-f] " + +#: builtin/notes.c:29 +msgid "" +"git notes [--ref ] append [-m | -F | (-c | -C) " +"] []" +msgstr "" +"git notes [--ref ] append [-m | -F | (-c " +"| -C) ] []" + +#: builtin/notes.c:30 +msgid "git notes [--ref ] edit []" +msgstr "git notes [--ref ] edit []" + +#: builtin/notes.c:31 +msgid "git notes [--ref ] show []" +msgstr "git notes [--ref ] show []" + +#: builtin/notes.c:32 +msgid "" +"git notes [--ref ] merge [-v | -q] [-s ] " +msgstr "" +"git notes [--ref ] merge [-v | -q] [-s ] " + +#: builtin/notes.c:33 +msgid "git notes merge --commit [-v | -q]" +msgstr "git notes merge --commit [-v | -q]" + +#: builtin/notes.c:34 +msgid "git notes merge --abort [-v | -q]" +msgstr "git notes merge --abort [-v | -q]" + +#: builtin/notes.c:35 +msgid "git notes [--ref ] remove [...]" +msgstr "git notes [--ref ] remove [...]" + +#: builtin/notes.c:36 +msgid "git notes [--ref ] prune [-n | -v]" +msgstr "git notes [--ref ] prune [-n | -v]" + +#: builtin/notes.c:37 +msgid "git notes [--ref ] get-ref" +msgstr "git notes [--ref ] get-ref" + +#: builtin/notes.c:42 +msgid "git notes [list []]" +msgstr "git notes [list []]" + +#: builtin/notes.c:47 +msgid "git notes add [] []" +msgstr "git notes add [] []" + +#: builtin/notes.c:52 +msgid "git notes copy [] " +msgstr "git notes copy [] " + +#: builtin/notes.c:53 +msgid "git notes copy --stdin [ ]..." +msgstr "git notes copy --stdin [ ]..." + +#: builtin/notes.c:58 +msgid "git notes append [] []" +msgstr "git notes append [] []" + +#: builtin/notes.c:63 +msgid "git notes edit []" +msgstr "git notes edit []" + +#: builtin/notes.c:68 +msgid "git notes show []" +msgstr "git notes show []" + +#: builtin/notes.c:73 +msgid "git notes merge [] " +msgstr "git notes merge [] " + +#: builtin/notes.c:74 +msgid "git notes merge --commit []" +msgstr "git notes merge --commit []" + +#: builtin/notes.c:75 +msgid "git notes merge --abort []" +msgstr "git notes merge --abort []" + +#: builtin/notes.c:80 +msgid "git notes remove []" +msgstr "git notes remove []" + +#: builtin/notes.c:85 +msgid "git notes prune []" +msgstr "git notes prune []" + +#: builtin/notes.c:90 +msgid "git notes get-ref" +msgstr "git notes get-ref" + +#: builtin/notes.c:142 #, c-format msgid "unable to start 'show' for object '%s'" msgstr "konnte 'show' für Objekt '%s' nicht starten" -#: builtin/notes.c:145 +#: builtin/notes.c:148 msgid "can't fdopen 'show' output fd" msgstr "konnte Datei-Deskriptor für Ausgabe von 'show' nicht öffnen" -#: builtin/notes.c:155 +#: builtin/notes.c:158 #, c-format msgid "failed to close pipe to 'show' for object '%s'" msgstr "Schließen der Verbindung zu 'show' ist für Objekt '%s' fehlgeschlagen." -#: builtin/notes.c:158 +#: builtin/notes.c:161 #, c-format msgid "failed to finish 'show' for object '%s'" msgstr "konnte 'show' für Objekt '%s' nicht abschließen" -#: builtin/notes.c:175 builtin/tag.c:347 +#: builtin/notes.c:178 builtin/tag.c:347 #, c-format msgid "could not create file '%s'" msgstr "konnte Datei '%s' nicht erstellen" -#: builtin/notes.c:189 +#: builtin/notes.c:192 msgid "Please supply the note contents using either -m or -F option" -msgstr "Bitte liefere den Notiz-Inhalt unter Verwendung der Option -m oder -F." +msgstr "Bitte liefern Sie den Notiz-Inhalt unter Verwendung der Option -m oder -F." -#: builtin/notes.c:210 builtin/notes.c:973 +#: builtin/notes.c:213 builtin/notes.c:976 #, c-format msgid "Removing note for object %s\n" msgstr "Entferne Notiz für Objekt %s\n" -#: builtin/notes.c:215 +#: builtin/notes.c:218 msgid "unable to write note object" msgstr "Konnte Notiz-Objekt nicht schreiben" -#: builtin/notes.c:217 +#: builtin/notes.c:220 #, c-format msgid "The note contents has been left in %s" msgstr "Die Notiz-Inhalte wurden in %s belassen" -#: builtin/notes.c:251 builtin/tag.c:542 +#: builtin/notes.c:254 builtin/tag.c:542 #, c-format msgid "cannot read '%s'" msgstr "kann '%s' nicht lesen" -#: builtin/notes.c:253 builtin/tag.c:545 +#: builtin/notes.c:256 builtin/tag.c:545 #, c-format msgid "could not open or read '%s'" msgstr "konnte '%s' nicht öffnen oder lesen" -#: builtin/notes.c:272 builtin/notes.c:445 builtin/notes.c:447 -#: builtin/notes.c:507 builtin/notes.c:561 builtin/notes.c:644 -#: builtin/notes.c:649 builtin/notes.c:724 builtin/notes.c:766 -#: builtin/notes.c:968 builtin/reset.c:293 builtin/tag.c:558 +#: builtin/notes.c:275 builtin/notes.c:448 builtin/notes.c:450 +#: builtin/notes.c:510 builtin/notes.c:564 builtin/notes.c:647 +#: builtin/notes.c:652 builtin/notes.c:727 builtin/notes.c:769 +#: builtin/notes.c:971 builtin/reset.c:293 builtin/tag.c:558 #, c-format msgid "Failed to resolve '%s' as a valid ref." msgstr "Konnte '%s' nicht als gültige Referenz auflösen." -#: builtin/notes.c:275 +#: builtin/notes.c:278 #, c-format msgid "Failed to read object '%s'." msgstr "Fehler beim Lesen des Objektes '%s'." -#: builtin/notes.c:299 +#: builtin/notes.c:302 msgid "Cannot commit uninitialized/unreferenced notes tree" msgstr "Kann uninitialisierten/unreferenzierten Notiz-Baum nicht eintragen." -#: builtin/notes.c:340 +#: builtin/notes.c:343 #, c-format msgid "Bad notes.rewriteMode value: '%s'" msgstr "Ungültiger notes.rewriteMode Wert: '%s'" -#: builtin/notes.c:350 +#: builtin/notes.c:353 #, c-format msgid "Refusing to rewrite notes in %s (outside of refs/notes/)" msgstr "" -"Neuschreiben der Notizen in %s zurückgewiesen (außerhalb von refs/notes/)" +"Umschreiben der Notizen in %s zurückgewiesen (außerhalb von refs/notes/)" #. TRANSLATORS: The first %s is the name of the #. environment variable, the second %s is its value -#: builtin/notes.c:377 +#: builtin/notes.c:380 #, c-format msgid "Bad %s value: '%s'" msgstr "Ungültiger %s Wert: '%s'" -#: builtin/notes.c:441 +#: builtin/notes.c:444 #, c-format msgid "Malformed input line: '%s'." msgstr "Fehlerhafte Eingabezeile: '%s'." -#: builtin/notes.c:456 +#: builtin/notes.c:459 #, c-format msgid "Failed to copy notes from '%s' to '%s'" msgstr "Fehler beim Kopieren der Notizen von '%s' nach '%s'" -#: builtin/notes.c:500 builtin/notes.c:554 builtin/notes.c:627 -#: builtin/notes.c:639 builtin/notes.c:712 builtin/notes.c:759 -#: builtin/notes.c:1033 +#: builtin/notes.c:503 builtin/notes.c:557 builtin/notes.c:630 +#: builtin/notes.c:642 builtin/notes.c:715 builtin/notes.c:762 +#: builtin/notes.c:1036 msgid "too many parameters" msgstr "zu viele Parameter" -#: builtin/notes.c:513 builtin/notes.c:772 +#: builtin/notes.c:516 builtin/notes.c:775 #, c-format msgid "No note found for object %s." msgstr "Kein Notiz für Objekt %s gefunden." -#: builtin/notes.c:580 +#: builtin/notes.c:538 builtin/notes.c:695 +msgid "note contents as a string" +msgstr "Notizinhalte als Zeichenkette" + +#: builtin/notes.c:541 builtin/notes.c:698 +msgid "note contents in a file" +msgstr "Notizinhalte in einer Datei" + +#: builtin/notes.c:543 builtin/notes.c:546 builtin/notes.c:700 +#: builtin/notes.c:703 builtin/tag.c:476 +msgid "object" +msgstr "Objekt" + +#: builtin/notes.c:544 builtin/notes.c:701 +msgid "reuse and edit specified note object" +msgstr "Wiederverwendung und Bearbeitung des angegebenen Notiz-Objektes" + +#: builtin/notes.c:547 builtin/notes.c:704 +msgid "reuse specified note object" +msgstr "Wiederverwendung des angegebenen Notiz-Objektes" + +#: builtin/notes.c:549 builtin/notes.c:617 +msgid "replace existing notes" +msgstr "ersetzt existierende Notizen" + +#: builtin/notes.c:583 #, c-format msgid "" "Cannot add notes. Found existing notes for object %s. Use '-f' to overwrite " "existing notes" msgstr "" "Konnte Notizen nicht hinzufügen. Existierende Notizen für Objekt %s " -"gefunden. Verwende '-f' um die existierenden Notizen zu überschreiben." +"gefunden. Verwenden Sie '-f' um die existierenden Notizen zu überschreiben." -#: builtin/notes.c:585 builtin/notes.c:662 +#: builtin/notes.c:588 builtin/notes.c:665 #, c-format msgid "Overwriting existing notes for object %s\n" msgstr "Überschreibe existierende Notizen für Objekt %s\n" -#: builtin/notes.c:635 +#: builtin/notes.c:618 +msgid "read objects from stdin" +msgstr "liest Objekte von der Standard-Eingabe" + +#: builtin/notes.c:620 +msgid "load rewriting config for (implies --stdin)" +msgstr "" +"lädt Konfiguration für beim Umschreiben von Versionen (impliziert " +"--stdin)" + +#: builtin/notes.c:638 msgid "too few parameters" msgstr "zu wenig Parameter" -#: builtin/notes.c:656 +#: builtin/notes.c:659 #, c-format msgid "" "Cannot copy notes. Found existing notes for object %s. Use '-f' to overwrite " "existing notes" msgstr "" "Kann Notizen nicht kopieren. Existierende Notizen für Objekt %s gefunden. " -"Verwende '-f' um die existierenden Notizen zu überschreiben." +"Verwenden Sie '-f' um die existierenden Notizen zu überschreiben." -#: builtin/notes.c:668 +#: builtin/notes.c:671 #, c-format msgid "Missing notes on source object %s. Cannot copy." msgstr "Keine Notizen für Quell-Objekt %s. Kopie nicht möglich." -#: builtin/notes.c:717 +#: builtin/notes.c:720 #, c-format msgid "" "The -m/-F/-c/-C options have been deprecated for the 'edit' subcommand.\n" "Please use 'git notes add -f -m/-F/-c/-C' instead.\n" msgstr "" "Die Optionen -m/-F/-c/-C sind für das Unterkommando 'edit' veraltet.\n" -"Bitte benutze stattdessen 'git notes add -f -m/-F/-c/-C'.\n" +"Bitte benutzen Sie stattdessen 'git notes add -f -m/-F/-c/-C'.\n" + +#: builtin/notes.c:867 +msgid "General options" +msgstr "Allgemeine Optionen" + +#: builtin/notes.c:869 +msgid "Merge options" +msgstr "Optionen für Zusammenführung" + +#: builtin/notes.c:871 +msgid "" +"resolve notes conflicts using the given strategy (manual/ours/theirs/union/" +"cat_sort_uniq)" +msgstr "" +"löst Konflikte bei Notizen mit der angegebenen Strategie auf (manual/ours/" +"theirs/union/cat_sort_uniq)" + +#: builtin/notes.c:873 +msgid "Committing unmerged notes" +msgstr "trägt nicht zusammengeführte Notizen ein" -#: builtin/notes.c:971 +#: builtin/notes.c:875 +msgid "finalize notes merge by committing unmerged notes" +msgstr "" +"schließt Zusammenführung von Notizen ab, in dem nicht zusammengeführte " +"Notizen eingetragen werden" + +#: builtin/notes.c:877 +msgid "Aborting notes merge resolution" +msgstr "bricht Konfliktauflösung bei Zusammenführung von Notizen ab" + +#: builtin/notes.c:879 +msgid "abort notes merge" +msgstr "bricht Zusammenführung von Notizen ab" + +#: builtin/notes.c:974 #, c-format msgid "Object %s has no note\n" msgstr "Objekt %s hat keine Notiz\n" -#: builtin/notes.c:1103 builtin/remote.c:1598 +#: builtin/notes.c:986 +msgid "attempt to remove non-existent note is not an error" +msgstr "der Versuch, eine nicht existierende Notiz zu löschen, ist kein Fehler" + +#: builtin/notes.c:989 +msgid "read object names from the standard input" +msgstr "liest Objektnamen von der Standard-Eingabe" + +#: builtin/notes.c:1070 +msgid "notes_ref" +msgstr "Notiz-Referenz" + +#: builtin/notes.c:1071 +msgid "use notes from " +msgstr "benutzt Notizen von " + +#: builtin/notes.c:1106 builtin/remote.c:1598 #, c-format msgid "Unknown subcommand: %s" msgstr "Unbekanntes Unterkommando: %s" +#: builtin/pack-objects.c:23 +msgid "git pack-objects --stdout [options...] [< ref-list | < object-list]" +msgstr "" +"git pack-objects --stdout [Optionen...] [< Referenzliste | < Objektliste]" + +#: builtin/pack-objects.c:24 +msgid "git pack-objects [options...] base-name [< ref-list | < object-list]" +msgstr "" +"git pack-objects [Optionen...] Basis-Name [< Referenzliste | < Objektliste]" + #: builtin/pack-objects.c:183 builtin/pack-objects.c:186 #, c-format msgid "deflate error (%d)" msgstr "Fehler beim Komprimieren (%d)" -#: builtin/pack-objects.c:2398 +#: builtin/pack-objects.c:2397 #, c-format msgid "unsupported index version %s" msgstr "Nicht unterstützte Bereitstellungsversion %s" -#: builtin/pack-objects.c:2402 +#: builtin/pack-objects.c:2401 #, c-format msgid "bad index version '%s'" msgstr "Ungültige Bereitstellungsversion '%s'" -#: builtin/pack-objects.c:2425 +#: builtin/pack-objects.c:2424 #, c-format msgid "option %s does not accept negative form" msgstr "Option %s akzeptiert keine negative Form" -#: builtin/pack-objects.c:2429 +#: builtin/pack-objects.c:2428 #, c-format msgid "unable to parse value '%s' for option %s" msgstr "konnte Wert '%s' für Option %s nicht parsen" +#: builtin/pack-objects.c:2447 +msgid "do not show progress meter" +msgstr "zeigt keine Fortschrittsanzeige" + +#: builtin/pack-objects.c:2449 +msgid "show progress meter" +msgstr "zeigt Fortschrittsanzeige" + +#: builtin/pack-objects.c:2451 +msgid "show progress meter during object writing phase" +msgstr "zeigt Forschrittsanzeige während der Phase des Schreibens der Objekte" + +#: builtin/pack-objects.c:2454 +msgid "similar to --all-progress when progress meter is shown" +msgstr "ähnlich zu --all-progress wenn Fortschrittsanzeige darstellt wird" + +#: builtin/pack-objects.c:2455 +msgid "version[,offset]" +msgstr "version[,offset]" + +#: builtin/pack-objects.c:2456 +msgid "write the pack index file in the specified idx format version" +msgstr "" +"schreibt die Index-Datei des Paketes in der angegebenen Indexformat-Version" + +#: builtin/pack-objects.c:2459 +msgid "maximum size of each output pack file" +msgstr "maximale Größe für jede ausgegebene Paketdatei" + +#: builtin/pack-objects.c:2461 +msgid "ignore borrowed objects from alternate object store" +msgstr "ignoriert geliehene Objekte von alternativem Objektspeicher" + +#: builtin/pack-objects.c:2463 +msgid "ignore packed objects" +msgstr "ignoriert gepackte Objekte" + +#: builtin/pack-objects.c:2465 +msgid "limit pack window by objects" +msgstr "begrenzt Paketfenster durch Objekte" + +#: builtin/pack-objects.c:2467 +msgid "limit pack window by memory in addition to object limit" +msgstr "begrenzt Paketfenster, zusätzlich zur Objektbegrenzung, durch Speicher" + +#: builtin/pack-objects.c:2469 +msgid "maximum length of delta chain allowed in the resulting pack" +msgstr "" +"maximale Länge der erlaubten Differenzverkettung im resultierenden Paket" + +#: builtin/pack-objects.c:2471 +msgid "reuse existing deltas" +msgstr "verwendet existierende Unterschiede wieder" + +#: builtin/pack-objects.c:2473 +msgid "reuse existing objects" +msgstr "verwendet existierende Objekte wieder" + +#: builtin/pack-objects.c:2475 +msgid "use OFS_DELTA objects" +msgstr "benutzt OFS_DELTA Objekte" + +#: builtin/pack-objects.c:2477 +msgid "use threads when searching for best delta matches" +msgstr "" +"benutzt Threads bei der Suche nach den besten Übereinstimmungen bei " +"Unterschieden" + +#: builtin/pack-objects.c:2479 +msgid "do not create an empty pack output" +msgstr "erzeugt keine leeren Pakete" + +#: builtin/pack-objects.c:2481 +msgid "read revision arguments from standard input" +msgstr "liest Argumente bezüglich Revisionen von der Standard-Eingabe" + +#: builtin/pack-objects.c:2483 +msgid "limit the objects to those that are not yet packed" +msgstr "begrenzt die Objekte zu solchen, die noch nicht gepackt wurden" + +#: builtin/pack-objects.c:2486 +msgid "include objects reachable from any reference" +msgstr "schließt Objekte ein, die von jeder Referenz erreichbar sind" + +#: builtin/pack-objects.c:2489 +msgid "include objects referred by reflog entries" +msgstr "" +"schließt Objekte ein, die von Einträgen des Referenzprotokolls referenziert " +"werden" + +#: builtin/pack-objects.c:2492 +msgid "output pack to stdout" +msgstr "schreibt Paket in die Standard-Ausgabe" + +#: builtin/pack-objects.c:2494 +msgid "include tag objects that refer to objects to be packed" +msgstr "" +"schließt Markierungsobjekte ein, die auf gepackte Objekte referenzieren" + +#: builtin/pack-objects.c:2496 +msgid "keep unreachable objects" +msgstr "behält nicht erreichbare Objekte" + +#: builtin/pack-objects.c:2497 parse-options.h:141 +msgid "time" +msgstr "Zeit" + +#: builtin/pack-objects.c:2498 +msgid "unpack unreachable objects newer than