Merge branch 'wk/submodule-update-remote'
authorJunio C Hamano <gitster@pobox.com>
Sun, 6 Jan 2013 07:42:11 +0000 (23:42 -0800)
committerJunio C Hamano <gitster@pobox.com>
Sun, 6 Jan 2013 07:42:11 +0000 (23:42 -0800)
The beginning of 'integrate with the tip of the remote branch, not
the commit recorded in the superproject gitlink' support.

* wk/submodule-update-remote:
  submodule add: If --branch is given, record it in .gitmodules
  submodule update: add --remote for submodule's upstream changes
  submodule: add get_submodule_config helper funtion

165 files changed:
.gitignore
.mailmap
Documentation/CodingGuidelines
Documentation/Makefile
Documentation/RelNotes/1.8.0.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.8.0.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.8.1.txt
Documentation/RelNotes/1.8.2.txt [new file with mode: 0644]
Documentation/SubmittingPatches
Documentation/config.txt
Documentation/diff-config.txt
Documentation/diff-options.txt
Documentation/fetch-options.txt
Documentation/git-checkout.txt
Documentation/git-diff.txt
Documentation/git-fast-import.txt
Documentation/git-log.txt
Documentation/git-push.txt
Documentation/git-remote-helpers.txt
Documentation/git-remote-testgit.txt
Documentation/git-stash.txt
Documentation/git-svn.txt
Documentation/git-tag.txt
Documentation/git.txt
Documentation/howto/new-command.txt [new file with mode: 0644]
Documentation/mailmap.txt
Documentation/pretty-formats.txt
Documentation/technical/api-history-graph.txt
Documentation/technical/api-index-skel.txt
Documentation/technical/api-string-list.txt
Documentation/technical/index-format.txt
GIT-VERSION-GEN
Makefile
README
RelNotes
abspath.c
advice.c
advice.h
archive.c
attr.c
builtin/commit.c
builtin/fast-export.c
builtin/ls-files.c
builtin/ls-tree.c
builtin/pack-redundant.c
builtin/push.c
builtin/rm.c
builtin/send-pack.c
builtin/shortlog.c
cache-tree.c
cache-tree.h
cache.h
commit.h
compat/fnmatch/fnmatch.c
compat/mingw.c
compat/mingw.h
compat/nedmalloc/nedmalloc.c
compat/terminal.c
config.c
config.mak.in
configure.ac
contrib/completion/git-completion.bash
contrib/completion/git-completion.tcsh
contrib/completion/git-completion.zsh [new file with mode: 0644]
contrib/completion/git-prompt.sh
contrib/mw-to-git/git-remote-mediawiki
contrib/mw-to-git/t/t9362-mw-to-git-utf8.sh
contrib/remote-helpers/Makefile [new file with mode: 0644]
contrib/remote-helpers/git-remote-hg [new file with mode: 0755]
contrib/remote-helpers/test-hg-bidi.sh [new file with mode: 0755]
contrib/remote-helpers/test-hg-hg-git.sh [new file with mode: 0755]
contrib/remote-helpers/test-hg.sh [new file with mode: 0755]
contrib/stats/mailmap.pl
contrib/subtree/.gitignore
contrib/subtree/git-subtree.txt
diff.c
diffcore-pickaxe.c
dir.c
dir.h
editor.c
fetch-pack.c
fsck.c
git-compat-util.h
git-p4.py
git-remote-testgit [new file with mode: 0755]
git-remote-testpy.py [moved from git-remote-testgit.py with 99% similarity]
git-send-email.perl
git-sh-setup.sh
git.c
git_remote_helpers/git/importer.py
gitweb/gitweb.perl
graph.c
http-push.c
http.c
ident.c
imap-send.c
log-tree.c
mailmap.c
mergetools/p4merge
parse-options.c
parse-options.h
path.c
perl/Git.pm
perl/Git/SVN/Prompt.pm
po/de.po
po/git.pot
po/sv.po
po/vi.po
pretty.c
refs.c
remote-testsvn.c
remote.c
run-command.c
send-pack.c
setup.c
strbuf.c
strbuf.h
string-list.c
string-list.h
t/Makefile
t/lib-gettext.sh
t/t0000-basic.sh
t/t0007-git-var.sh [new file with mode: 0755]
t/t0060-path-utils.sh
t/t0063-string-list.sh
t/t1450-fsck.sh
t/t2020-checkout-detach.sh
t/t2203-add-intent.sh
t/t3600-rm.sh
t/t4014-format-patch.sh
t/t4030-diff-textconv.sh
t/t4041-diff-submodule-option.sh
t/t4049-diff-stat-count.sh
t/t4201-shortlog.sh
t/t4202-log.sh
t/t4203-mailmap.sh
t/t5002-archive-attr-pattern.sh [new file with mode: 0755]
t/t5516-fetch-push.sh
t/t5535-fetch-push-symref.sh [new file with mode: 0755]
t/t5800-remote-testpy.sh [moved from t/t5800-remote-helpers.sh with 87% similarity]
t/t5801-remote-helpers.sh [new file with mode: 0755]
t/t6006-rev-list-format.sh
t/t6130-pathspec-noglob.sh [new file with mode: 0755]
t/t7004-tag.sh
t/t7502-commit.sh
t/t9001-send-email.sh
t/t9020-remote-svn.sh
t/t9200-git-cvsexportcommit.sh
t/t9350-fast-export.sh
t/t9502-gitweb-standalone-parse-output.sh
t/t9800-git-p4-basic.sh
t/t9814-git-p4-rename.sh
t/t9902-completion.sh
t/test-lib-functions.sh
t/test-lib.sh
test-path-utils.c
test-string-list.c
transport-helper.c
transport.c
transport.h
tree-walk.c
usage.c
utf8.c
utf8.h
wrapper.c

index f702415..956ae22 100644 (file)
 /git-remote-ftps
 /git-remote-fd
 /git-remote-ext
-/git-remote-testgit
+/git-remote-testpy
 /git-remote-testsvn
 /git-repack
 /git-replace
index bcf4f87..c7e8618 100644 (file)
--- a/.mailmap
+++ b/.mailmap
@@ -9,7 +9,9 @@ Alex Bennée <kernel-hacker@bennee.com>
 Alexander Gavrilov <angavrilov@gmail.com>
 Aneesh Kumar K.V <aneesh.kumar@gmail.com>
 Brian M. Carlson <sandals@crustytoothpaste.ath.cx>
+Cheng Renquan <crquan@gmail.com>
 Chris Shoemaker <c.shoemaker@cox.net>
+Dan Johnson <computerdruid@gmail.com>
 Dana L. How <danahow@gmail.com>
 Dana L. How <how@deathvalley.cswitch.com>
 Daniel Barkalow <barkalow@iabervon.org>
@@ -18,14 +20,18 @@ David Kågedal <davidk@lysator.liu.se>
 David S. Miller <davem@davemloft.net>
 Deskin Miller <deskinm@umich.edu>
 Dirk Süsserott <newsletter@dirk.my1.cc>
+Eric S. Raymond <esr@thyrsus.com>
 Erik Faye-Lund <kusmabite@gmail.com> <kusmabite@googlemail.com>
 Fredrik Kuivinen <freku045@student.liu.se>
+Frédéric Heitzmann <frederic.heitzmann@gmail.com>
 H. Peter Anvin <hpa@bonde.sc.orionmulti.com>
 H. Peter Anvin <hpa@tazenda.sc.orionmulti.com>
 H. Peter Anvin <hpa@trantor.hos.anvin.org>
 Horst H. von Brand <vonbrand@inf.utfsm.cl>
 İsmail Dönmez <ismail@pardus.org.tr>
+Jakub Narębski <jnareb@gmail.com>
 Jay Soffian <jaysoffian+git@gmail.com>
+Jeff King <peff@peff.net> <peff@github.com>
 Joachim Berdal Haga <cjhaga@fys.uio.no>
 Johannes Sixt <j6t@kdbg.org> <johannes.sixt@telecom.at>
 Johannes Sixt <j6t@kdbg.org> <j.sixt@viscovery.net>
@@ -41,12 +47,21 @@ Junio C Hamano <gitster@pobox.com> <junio@hera.kernel.org>
 Junio C Hamano <gitster@pobox.com> <junio@kernel.org>
 Junio C Hamano <gitster@pobox.com> <junkio@cox.net>
 Karl Hasselström <kha@treskal.com>
+Kevin Leung <kevinlsk@gmail.com>
 Kent Engstrom <kent@lysator.liu.se>
 Lars Doelle <lars.doelle@on-line ! de>
 Lars Doelle <lars.doelle@on-line.de>
 Li Hong <leehong@pku.edu.cn>
+Linus Torvalds <torvalds@linux-foundation.org> <torvalds@woody.linux-foundation.org>
+Linus Torvalds <torvalds@linux-foundation.org> <torvalds@osdl.org>
+Linus Torvalds <torvalds@linux-foundation.org> <torvalds@g5.osdl.org>
+Linus Torvalds <torvalds@linux-foundation.org> <torvalds@evo.osdl.org>
+Linus Torvalds <torvalds@linux-foundation.org> <torvalds@ppc970.osdl.org>
+Linus Torvalds <torvalds@linux-foundation.org> <torvalds@ppc970.osdl.org.(none)>
 Lukas Sandström <lukass@etek.chalmers.se>
-Martin Langhoff <martin@laptop.org>
+Marc-André Lureau <marcandre.lureau@gmail.com>
+Mark Rada <marada@uwaterloo.ca>
+Martin Langhoff <martin@laptop.org> <martin@catalyst.net.nz>
 Martin von Zweigbergk <martinvonz@gmail.com> <martin.von.zweigbergk@gmail.com>
 Michael Coleman <tutufan@gmail.com>
 Michael J Gruber <git@drmicha.warpmail.net> <michaeljgruber+gmane@fastmail.fm>
@@ -63,11 +78,13 @@ Ralf Thielow <ralf.thielow@gmail.com> <ralf.thielow@googlemail.com>
 Ramsay Allan Jones <ramsay@ramsay1.demon.co.uk>
 René Scharfe <rene.scharfe@lsrfire.ath.cx>
 Robert Fitzsimons <robfitz@273k.net>
+Robert Zeh <robert.a.zeh@gmail.com>
 Sam Vilain <sam@vilain.net>
 Santi Béjar <sbejar@gmail.com>
 Sean Estabrooks <seanlkml@sympatico.ca>
 Shawn O. Pearce <spearce@spearce.org>
 Steven Grimm <koreth@midwinter.com>
+Tay Ray Chuan <rctay89@gmail.com>
 Theodore Ts'o <tytso@mit.edu>
 Thomas Rast <trast@inf.ethz.ch> <trast@student.ethz.ch>
 Tony Luck <tony.luck@intel.com>
index 57da6aa..69f7e9b 100644 (file)
@@ -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
index 3615504..e53d333 100644 (file)
@@ -21,6 +21,7 @@ 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
@@ -31,7 +32,6 @@ SP_ARTICLES += howto/separating-topic-branches
 SP_ARTICLES += howto/revert-a-faulty-merge
 SP_ARTICLES += howto/recover-corrupted-blob-object
 SP_ARTICLES += howto/rebuild-from-update-hook
-SP_ARTICLES += howto/rebuild-from-update-hook
 SP_ARTICLES += howto/rebase-from-internal-branch
 SP_ARTICLES += howto/maintain-git
 API_DOCS = $(patsubst %.txt,%,$(filter-out technical/api-index-skel.txt technical/api-index.txt, $(wildcard technical/api-*.txt)))
@@ -331,7 +331,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 (file)
index 0000000..8497e05
--- /dev/null
@@ -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 (file)
index 0000000..92b1e4b
--- /dev/null
@@ -0,0 +1,14 @@
+Git v1.8.0.3 Release Notes
+==========================
+
+Fixes since v1.8.0.2
+--------------------
+
+ * "git log -p -S<string>" did not apply the textconv filter while
+   looking for the <string>.
+
+ * 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.
index 34c5b26..d6f9555 100644 (file)
@@ -19,7 +19,7 @@ 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.
+been introduced with a saner order of arguments to replace it.
 
 
 Updates since v1.8.0
@@ -27,22 +27,24 @@ Updates since v1.8.0
 
 UI, Workflows & Features
 
- * Command-line completion for tcsh has been added.
+ * Command-line completion scripts for tcsh and zsh have been added.
 
- * 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.
+ * "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.
 
- * "git-prompt" scriptlet (in contrib/completion) can be told to paint
-   pieces of the hints in the prompt string in colors.
+ * 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.
 
- * A new configuration variable "diff.context" can be used to
-   give the default number of context lines in the patch output, to
-   override the hardcoded default of 3 lines.
+ * When "git checkout" checks out a branch, it tells the user how far
+   behind (or ahead) the new branch is relative to the remote tracking
+   branch it builds upon.  The message now also advises how to sync
+   them up by pushing or pulling.  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
@@ -51,20 +53,43 @@ UI, Workflows & Features
    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=<ref>" option to give
    notes for the commit after the three-dash lines in its output.
 
+ * "git log -p -S<string>" now looks for the <string> after applying
+   the textconv filter (if defined); earlier it inspected the contents
+   of the blobs without filtering.
+
  * "git log --grep=<pcre>" learned to honor the "grep.patterntype"
    configuration set to "perl".
 
- * "git replace -d <object>" now interprets <object>, instead of only
-   accepting full hex object name.
+ * "git replace -d <object>" now interprets <object> 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.
+   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 <stable@k.org> # 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
@@ -77,7 +102,7 @@ UI, Workflows & Features
 
  * "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".
+   "update-ref -d --no-deref $symref".
 
 
 Foreign Interface
@@ -88,15 +113,24 @@ Foreign Interface
  * 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.
 
- * The logic to generate the initial advertisement from
-   "upload-pack" (what is invoked by "git fetch" on the other side
-   of the connection) to list what refs are available in the
-   repository has been optimized.
+ * 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.
@@ -118,55 +152,44 @@ details).
 
  * The configuration parser had an unnecessary hardcoded limit on
    variable names that was not checked consistently.
-   (merge 0971e99 bw/config-lift-variable-name-length-limit later to maint).
 
  * The "say" function in the test scaffolding incorrectly allowed
    "echo" to interpret "\a" as if it were a C-string asking for a
    BEL output.
-   (merge 7bc0911 jc/test-say-color-avoid-echo-escape later to maint).
 
  * "git mergetool" feeds /dev/null as a common ancestor when dealing
    with an add/add conflict, but p4merge backend cannot handle
    it. Work it around by passing a temporary empty file.
-   (merge 3facc60 da/mergetools-p4 later to maint).
 
  * "git log -F -E --grep='<ere>'" failed to use the given <ere>
    pattern as extended regular expression, and instead looked for the
    string literally.
-   (merge 727b6fc jc/grep-pcre-loose-ends~1 later to maint).
 
  * "git grep -e pattern <tree>" asked the attribute system to read
    "<tree>:.gitattributes" file in the working tree, which was
    nonsense.
-   (merge 55c6168 nd/grep-true-path later to maint).
 
  * A symbolic ref refs/heads/SYM was not correctly removed with "git
    branch -d SYM"; the command removed the ref pointed by SYM
    instead.
-   (merge 13baa9f rs/branch-del-symref later to maint).
 
  * Update "remote tracking branch" in the documentation to
    "remote-tracking branch".
-   (merge a6d3bde mm/maint-doc-remote-tracking later to maint).
 
  * "git pull --rebase" run while the HEAD is detached tried to find
    the upstream branch of the detached HEAD (which by definition
    does not exist) and emitted unnecessary error messages.
-   (merge e980765 ph/pull-rebase-detached later to maint).
 
  * The refs/replace hierarchy was not mentioned in the
    repository-layout docs.
-   (merge 11fbe18 po/maint-refs-replace-docs later to maint).
 
  * Various rfc2047 quoting issues around a non-ASCII name on the
    From: line in the output from format-patch have been corrected.
-   (merge 25dc8da js/format-2047 later to maint).
 
  * Sometimes curl_multi_timeout() function suggested a wrong timeout
    value when there is no file descriptor to wait on and the http
    transport ended up sleeping for minutes in select(2) system call.
    A workaround has been added for this.
-   (merge 7202b81 sz/maint-curl-multi-timeout later to maint).
 
  * For a fetch refspec (or the result of applying wildcard on one),
    we always want the RHS to map to something inside "refs/"
@@ -175,7 +198,6 @@ details).
 
  * "git diff -G<pattern>" did not honor textconv filter when looking
    for changes.
-   (merge b1c2f57 jk/maint-diff-grep-textconv later to maint).
 
  * Some HTTP servers ask for auth only during the actual packing phase
    (not in ls-remote phase); this is not really a recommended
@@ -185,27 +207,35 @@ details).
 
  * "git p4" used to try expanding malformed "$keyword$" that spans
    across multiple lines.
-   (merge 6b2bf41 pw/maint-p4-rcs-expansion-newline later to maint).
 
  * Syntax highlighting in "gitweb" was not quite working.
-   (merge 048b399 rh/maint-gitweb-highlight-ext later to maint).
 
  * RSS feed from "gitweb" had a xss hole in its title output.
-   (merge 0f0ecf6 jk/maint-gitweb-xss later to maint).
 
  * "git config --path $key" segfaulted on "[section] key" (a boolean
    "true" spelled without "=", not "[section] key = true").
-   (merge 962c38e cn/config-missing-path later to maint).
 
  * "git checkout -b foo" while on an unborn branch did not say
    "Switched to a new branch 'foo'" like other cases.
-   (merge afa8c07 jk/checkout-out-of-unborn later to maint).
+
+ * 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.
-   (merge 74faaa1 lt/diff-stat-show-0-lines later to maint).
+
+ * When "--stat-count" hides a diffstat for binary contents, the total
+   number of added and removed lines at the bottom was computed
+   incorrectly.
+
+ * 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.
-   (merge b274a71 jh/update-ref-d-through-symref later to maint).
diff --git a/Documentation/RelNotes/1.8.2.txt b/Documentation/RelNotes/1.8.2.txt
new file mode 100644 (file)
index 0000000..6c85bf4
--- /dev/null
@@ -0,0 +1,106 @@
+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.
+
+Foreign Interface
+
+ *
+
+Performance, Internal Implementation, etc.
+
+ *
+
+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).
+
+ * 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).
+
+ * 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).
+
+ * 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 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).
+
+ * "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).
+
+ * 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).
+
+ * 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).
index 3d8b2fe..75935d5 100644 (file)
@@ -9,6 +9,14 @@ Checklist (and a short version for the impatient):
        - 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)
        - 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.
@@ -119,19 +127,6 @@ 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
-
-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.
-
-Also, variables have to be declared at the beginning of the block
-(you can check this with gcc, using the -Wdeclaration-after-statement
-option).
-
-Another thing: NULL pointers shall be written as NULL, not as 0.
-
-
 (2) Generate your patch using git tools out of your commits.
 
 git based diff tools generate unidiff which is the preferred format.
index 3a44fd7..53c4ca1 100644 (file)
@@ -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.
@@ -1516,6 +1521,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].
index 89dd634..4314ad0 100644 (file)
@@ -57,8 +57,8 @@ diff.statGraphWidth::
        to all commands generating --stat output except format-patch.
 
 diff.context::
-       Generate diffs with <n> lines of context instead of the default of
-       3. This value is overridden by the -U option.
+       Generate diffs with <n> 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
index f4f7e25..39f2c50 100644 (file)
@@ -309,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[<n>]::
 --find-copies[=<n>]::
index b4d6476..6e98bdf 100644 (file)
@@ -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.<name>.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
index 7958a47..6f04d22 100644 (file)
@@ -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' [<branch>]::
+'git checkout' <branch>::
+       To prepare for working on <branch>, 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
+       <branch>.
++
+If <branch> is not found but there does exist a tracking branch in
+exactly one remote (call it <remote>) with a matching name, treat as
+equivalent to
++
+------------
+$ git checkout -b <branch> --track <remote>/<branch>
+------------
++
+You could omit <branch>, 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 <new_branch> [<start point>]::
-'git checkout' [--detach] [<commit>]::
 
-       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, <new_branch> is created if it doesn't exist; otherwise, it
 is reset. This is the transactional equivalent of
@@ -45,6 +61,21 @@ $ git checkout <branch>
 that is to say, the branch is not reset/created unless "git checkout" is
 successful.
 
+'git checkout' --detach [<branch>]::
+'git checkout' <commit>::
+
+       Prepare to work on top of <commit>, 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 <branch> (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 <branch> is specified.
+
 'git checkout' [-p|--patch] [<tree-ish>] [--] <pathspec>...::
 
        When <paths> or `--patch` are given, 'git checkout' does *not*
index f8d0819..f8c0601 100644 (file)
@@ -12,6 +12,7 @@ SYNOPSIS
 'git diff' [options] [<commit>] [--] [<path>...]
 'git diff' [options] --cached [<commit>] [--] [<path>...]
 'git diff' [options] <commit> <commit> [--] [<path>...]
+'git diff' [options] <blob> <blob>
 'git diff' [options] [--no-index] [--] <path> <path>
 
 DESCRIPTION
@@ -55,6 +56,11 @@ directories. This behavior can be forced by --no-index.
        This is to view the changes between two arbitrary
        <commit>.
 
+'git diff' [options] <blob> <blob>::
+
+       This form is to view the differences between the raw
+       contents of two blob objects.
+
 'git diff' [--options] <commit>..<commit> [--] [<path>...]::
 
        This is synonymous to the previous form.  If <commit> 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 <commit> in the above description, except
 in the last two forms that use ".." notations, can be any
-<tree>.  The third form ('git diff <commit> <commit>') can also
-be used to compare two <blob> objects.
+<tree>.
 
 For a more complete list of ways to spell <commit>, see
 "SPECIFYING REVISIONS" section in linkgit:gitrevisions[7].
index 959e4d3..68bca1a 100644 (file)
@@ -427,7 +427,7 @@ they made it.
 
 Here `<name>` is the person's display name (for example
 ``Com M Itter'') and `<email>` 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
 `<name>` and `<email>` are free-form and may contain any sequence
@@ -562,8 +562,12 @@ A `<path>` 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 `<path>` 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 `<path>` must be in canonical form. That is it must not:
 
index 585dac4..08a185d 100644 (file)
@@ -167,7 +167,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::
index 6d19d59..c964b79 100644 (file)
@@ -51,10 +51,11 @@ be named. If `:`<dst> is omitted, the same ref as <src> will be
 updated.
 +
 The object referenced by <src> is used to update the <dst> reference
-on the remote side, but by default this is only allowed if the
-update can fast-forward <dst>.  By having the optional leading `+`,
-you can tell git to update the <dst> ref even when the update is not a
-fast-forward.  This does *not* attempt to merge <src> into <dst>.  See
+on the remote side.  By default this is only allowed if <dst> is not
+a tag (annotated or lightweight), and then only if it can fast-forward
+<dst>.  By having the optional leading `+`, you can tell git to update
+the <dst> ref even if it is not allowed by default (e.g., it is not a
+fast-forward.)  This does *not* attempt to merge <src> into <dst>.  See
 EXAMPLES below for details.
 +
 `tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.
@@ -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
index 5ce4cda..6d696e0 100644 (file)
@@ -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
+'<transport>://<address>', 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 '<transport>://<address>', where
+'<transport>' is a protocol that it cannot handle natively, it
+automatically invokes 'git remote-<transport>' 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 '<transport>::<address>' explicitly instructs git to
+invoke 'git remote-<transport>' with '<address>' as the second
+argument. If such a URL is encountered directly on the command line,
+the first argument is '<address>', 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.<name>.vcs' set to
+'<transport>', git explicitly invokes 'git remote-<transport>' with
+'<name>' as the first argument. If set, the second argument is
+'remote.<name>.url'; otherwise, the second argument is omitted.
+
 INPUT FORMAT
 ------------
 
@@ -57,67 +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' <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 *:*`.
-
-'bidi-import'::
-       The fast-import commands 'cat-blob' and 'ls' can be used by remote-helpers
-       to retrieve information about blobs and trees that already exist in
-       fast-import's memory. This requires a channel from fast-import to the
-       remote-helper.
-       If it is advertised in addition to "import", git establishes a pipe from
-       fast-import to the remote-helper's stdin.
-       It follows that git and fast-import are both connected to the
-       remote-helper's stdin. Because git can send multiple commands to
-       the remote-helper it is required that helpers that use 'bidi-import'
-       buffer all 'import' commands of a batch before sending data to fast-import.
-       This is to prevent mixing commands and fast-import responses on the
-       helper's stdin.
+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'.
 
@@ -127,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'.
 
@@ -158,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' <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
@@ -173,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
-'<transport>://<address>', 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' <file>::
+       This modifies the 'export' capability, instructing git to dump the
+       internal marks table to <file> when complete. For details,
+       read up on '--export-marks=<file>' in linkgit:git-fast-export[1].
+
+'import-marks' <file>::
+       This modifies the 'export' capability, instructing git to load the
+       marks specified in <file> before processing any input. For details,
+       read up on '--import-marks=<file>' in linkgit:git-fast-export[1].
 
-When git encounters a URL of the form '<transport>://<address>', where
-'<transport>' is a protocol that it cannot handle natively, it
-automatically invokes 'git remote-<transport>' 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 '<transport>::<address>' explicitly instructs git to
-invoke 'git remote-<transport>' with '<address>' as the second
-argument. If such a URL is encountered directly on the command line,
-the first argument is '<address>', 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.<name>.vcs' set to
-'<transport>', git explicitly invokes 'git remote-<transport>' with
-'<name>' as the first argument. If set, the second argument is
-'remote.<name>.url'; otherwise, the second argument is omitted.
 
 COMMANDS
 --------
@@ -212,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 "<value> <name>
@@ -224,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' <name> <value>::
        Sets the transport helper option <name> to <value>.  Outputs a
@@ -236,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' <sha1> <name>::
@@ -244,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 <file>' line indicating a file under
 GIT_DIR/objects/pack which is keeping a pack until refs can be
@@ -305,7 +321,23 @@ 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.
+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' <service>::
        Connects to given service. Standard input and standard output
@@ -332,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
@@ -343,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' <n>::
        Changes the verbosity of messages displayed by the helper.
        A value of 0 for <n> means that processes operate
index 2a67d45..612a625 100644 (file)
@@ -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
 --------
index 0aa4e20..711ffe1 100644 (file)
@@ -144,7 +144,7 @@ drop [-q|--quiet] [<stash>]::
 
        Remove a single stashed state from the stash list. When no `<stash>`
        is given, it removes the latest one. i.e. `stash@{0}`, otherwise
-       `<stash>` must a valid stash log reference of the form
+       `<stash>` must be a valid stash log reference of the form
        `stash@{<revision>}`.
 
 create::
index 8b0d3ad..69decb1 100644 (file)
@@ -628,10 +628,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]
@@ -739,7 +748,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):
@@ -764,8 +774,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
@@ -830,6 +842,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: <branchname>".
+
+Additionally, it will create a special branch named
+'<branchname>@<SVN-Revision>', where <SVN-Revision> 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
 -------
 
@@ -871,6 +929,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,
@@ -894,6 +967,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
 -------------
 
index 247534e..6470cff 100644 (file)
@@ -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 <your@email.address>") 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:
 
index cbe0883..05c0b94 100644 (file)
@@ -43,9 +43,16 @@ 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/git.html[documentation for release 1.8.1]
 
 * release notes for
+  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 +428,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 +661,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
@@ -788,6 +801,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 +891,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
-<git@vger.kernel.org>. For a more complete list of contributors, see
-http://git-scm.com/about. If you have a clone of git.git itself, the
+<git@vger.kernel.org>.  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/howto/new-command.txt b/Documentation/howto/new-command.txt
new file mode 100644 (file)
index 0000000..36502f6
--- /dev/null
@@ -0,0 +1,104 @@
+From: Eric S. Raymond <esr@thyrsus.com>
+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.
index 288f04e..4a8c276 100644 (file)
@@ -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         <jane@desktop.(none)>
 Joe R. Developer <joe@example.com>
 ------------
 
-Note how there is no need for an entry for <jane@laptop.(none)>, because the
+Note how there is no need for an entry for `<jane@laptop.(none)>`, because the
 real name of that author is already correct.
 
 Example 2: Your repository contains commits from the following
index d9edded..105f18a 100644 (file)
@@ -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 '%'
index d6fc90a..18142b6 100644 (file)
@@ -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.
index af7cc2e..730cfac 100644 (file)
@@ -11,5 +11,3 @@ documents them.
 ////////////////////////////////////////////////////////////////
 // table of contents end
 ////////////////////////////////////////////////////////////////
-
-2007-11-24
index 7386bca..20be348 100644 (file)
@@ -82,14 +82,6 @@ Functions
        call free() on the util members of any items that have to be
        deleted.  Preserve the order of the items that are retained.
 
-`string_list_longest_prefix`::
-
-       Return the longest string within a string_list that is a
-       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).
-
 `print_string_list`::
 
        Dump a string_list to stdout, useful mainly for debugging purposes. It
index 57d6f91..7324154 100644 (file)
@@ -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
index d66e54f..e9f7abc 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.8.0.1
+DEF_VER=v1.8.1.GIT
 
 LF='
 '
index 4ad6fbd..8e225ca 100644 (file)
--- 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.
@@ -374,7 +382,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 +478,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)) \
@@ -575,9 +583,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
@@ -1139,7 +1147,7 @@ ifeq ($(uname_S),NetBSD)
 endif
 ifeq ($(uname_S),AIX)
        DEFAULT_PAGER = more
-       NO_STRCASESTR=YesPlease
+       NO_STRCASESTR = YesPlease
        NO_MEMMEM = YesPlease
        NO_MKDTEMP = YesPlease
        NO_MKSTEMPS = YesPlease
@@ -1147,7 +1155,7 @@ ifeq ($(uname_S),AIX)
        NO_NSEC = YesPlease
        FREAD_READS_DIRECTORIES = UnfortunatelyYes
        INTERNAL_QSORT = UnfortunatelyYes
-       NEEDS_LIBICONV=YesPlease
+       NEEDS_LIBICONV = YesPlease
        BASIC_CFLAGS += -D_LARGE_FILES
        ifeq ($(shell expr "$(uname_V)" : '[1234]'),1)
                NO_PTHREADS = YesPlease
@@ -1155,13 +1163,13 @@ ifeq ($(uname_S),AIX)
                PTHREAD_LIBS = -lpthread
        endif
        ifeq ($(shell expr "$(uname_V).$(uname_R)" : '5\.1'),3)
-               INLINE=''
+               INLINE = ''
        endif
        GIT_TEST_CMP = cmp
 endif
 ifeq ($(uname_S),GNU)
        # GNU/Hurd
-       NO_STRLCPY=YesPlease
+       NO_STRLCPY = YesPlease
        NO_MKSTEMPS = YesPlease
        HAVE_PATHS_H = YesPlease
        LIBC_CONTAINS_LIBINTL = YesPlease
@@ -1187,9 +1195,9 @@ ifeq ($(uname_S),IRIX)
        NEEDS_LIBGEN = YesPlease
 endif
 ifeq ($(uname_S),IRIX64)
-       NO_SETENV=YesPlease
+       NO_SETENV = YesPlease
        NO_UNSETENV = YesPlease
-       NO_STRCASESTR=YesPlease
+       NO_STRCASESTR = YesPlease
        NO_MEMMEM = YesPlease
        NO_MKSTEMPS = YesPlease
        NO_MKDTEMP = YesPlease
@@ -1203,14 +1211,14 @@ ifeq ($(uname_S),IRIX64)
        NO_REGEX = YesPlease
        NO_FNMATCH_CASEFOLD = YesPlease
        SNPRINTF_RETURNS_BOGUS = YesPlease
-       SHELL_PATH=/usr/gnu/bin/bash
+       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_IPV6 = YesPlease
+       NO_SETENV = YesPlease
+       NO_STRCASESTR = YesPlease
        NO_MEMMEM = YesPlease
        NO_MKSTEMPS = YesPlease
        NO_STRLCPY = YesPlease
@@ -1353,6 +1361,7 @@ ifeq ($(uname_S),NONSTOP_KERNEL)
        # 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
@@ -1386,10 +1395,10 @@ ifeq ($(uname_S),NONSTOP_KERNEL)
        MKDIR_WO_TRAILING_SLASH = YesPlease
        # RFE 10-120912-4693 submitted to HP NonStop development.
        NO_SETITIMER = UnfortunatelyYes
-       SANE_TOOL_PATH=/usr/coreutils/bin:/usr/local/bin
-       SHELL_PATH=/usr/local/bin/bash
+       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
+       #SHELL_PATH = /usr/coreutils/bin/bash
 endif
 ifneq (,$(findstring MINGW,$(uname_S)))
        pathsep = ;
@@ -1437,7 +1446,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))
        X = .exe
        SPARSE_FLAGS = -Wno-one-bit-signed-bitfield
 ifneq (,$(wildcard ../THIS_IS_MSYSGIT))
-       htmldir=doc/git/html/
+       htmldir = doc/git/html/
        prefix =
        INSTALL = /bin/install
        EXTLIBS += /mingw/lib/libz.a
@@ -1448,6 +1457,22 @@ 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
 
 -include config.mak.autogen
 -include config.mak
@@ -1559,7 +1584,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
@@ -1655,6 +1680,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
@@ -1748,6 +1776,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
@@ -1768,7 +1799,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
@@ -1859,6 +1890,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
@@ -1884,6 +1918,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
@@ -1920,15 +1958,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
@@ -1975,13 +2013,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
 
@@ -2830,7 +2868,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
diff --git a/README b/README
index d2690ec..49713ea 100644 (file)
--- 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.
 
index 2860714..bdce313 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/1.8.1.txt
\ No newline at end of file
+Documentation/RelNotes/1.8.2.txt
\ No newline at end of file
index 05f2d79..40cdc46 100644 (file)
--- 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)
index edfbd4a..d287927 100644 (file)
--- 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, ...)
index f3cdbbf..8bf6356 100644 (file)
--- 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;
index 4666404..93e00bb 100644 (file)
--- 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/attr.c b/attr.c
index 097ae87..466c93f 100644 (file)
--- a/attr.c
+++ b/attr.c
@@ -564,17 +564,24 @@ static void bootstrap_attr_stack(void)
        attr_stack = elem;
 }
 
+static const char *find_basename(const char *path)
+{
+       const char *cp, *last_slash = NULL;
+
+       for (cp = path; *cp; cp++) {
+               if (*cp == '/' && cp[1])
+                       last_slash = cp;
+       }
+       return last_slash ? last_slash + 1 : path;
+}
+
 static void prepare_attr_stack(const char *path)
 {
        struct attr_stack *elem, *info;
        int dirlen, len;
        const char *cp;
 
-       cp = strrchr(path, '/');
-       if (!cp)
-               dirlen = 0;
-       else
-               dirlen = cp - path;
+       dirlen = find_basename(path) - path;
 
        /*
         * At the bottom of the attribute stack is the built-in
@@ -668,6 +675,10 @@ static int path_matches(const char *pathname, int pathlen,
        const char *pattern = pat->pattern;
        int prefix = pat->nowildcardlen;
 
+       if ((pat->flags & EXC_FLAG_MUSTBEDIR) &&
+           ((!pathlen) || (pathname[pathlen-1] != '/')))
+               return 0;
+
        if (pat->flags & EXC_FLAG_NODIR) {
                return match_basename(basename,
                                      pathlen - (basename - pathname),
@@ -758,9 +769,7 @@ static void collect_all_attrs(const char *path)
        for (i = 0; i < attr_nr; i++)
                check_all_attr[i].value = ATTR__UNKNOWN;
 
-       basename = strrchr(path, '/');
-       basename = basename ? basename + 1 : path;
-
+       basename = find_basename(path);
        pathlen = strlen(path);
        rem = attr_nr;
        for (stk = attr_stack; 0 < rem && stk; stk = stk->prev)
index 1dd2ec5..d6dd3df 100644 (file)
@@ -755,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"),
@@ -1265,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) {
index 12220ad..77dffd1 100644 (file)
@@ -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");
index b5434af..4a9ee69 100644 (file)
@@ -337,7 +337,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))
index 235c17c..fb76e38 100644 (file)
@@ -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)
index f5c6afc..649c3aa 100644 (file)
@@ -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;
index db9ba30..8491e43 100644 (file)
@@ -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;
index 2aea3b5..dabfcf6 100644 (file)
@@ -234,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);
 
index d342013..f849e0a 100644 (file)
@@ -44,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";
@@ -85,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++;
@@ -223,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;
index b316cf3..8360514 100644 (file)
@@ -306,9 +306,8 @@ parse_done:
 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)
index 28ed657..37e4d00 100644 (file)
@@ -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;
index d8cb2e9..55d0f59 100644 (file)
@@ -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 dbd8018..c257953 100644 (file)
--- 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,19 @@ 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,
+               not_forwardable: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 +1148,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 *);
+#ifdef __GNUC__
+#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 +1164,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 *);
index b6ad8f3..0f469e5 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -89,6 +89,7 @@ struct pretty_print_context {
        char *notes_message;
        struct reflog_walk_info *reflog_info;
        const char *output_encoding;
+       int color;
 };
 
 struct userformat_want {
index 0ff1d27..b8b7dc2 100644 (file)
@@ -135,9 +135,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 +159,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 +481,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);
 }
index 4e63838..b673625 100644 (file)
@@ -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)
 {
@@ -1546,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
@@ -1560,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;
        }
@@ -1654,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];
@@ -1721,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,
index eeb08d1..685cd2c 100644 (file)
@@ -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,6 +177,9 @@ 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
 
@@ -290,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
  */
index d9a17a8..91c4e7f 100644 (file)
@@ -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);
index bbb038d..9b5e3d1 100644 (file)
@@ -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;
index fb3f868..053970f 100644 (file)
--- a/config.c
+++ b/config.c
@@ -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;
@@ -1660,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);
index 69d4838..e8a9bb4 100644 (file)
@@ -18,7 +18,7 @@ datarootdir = @datarootdir@
 template_dir = @datadir@/git-core/templates
 sysconfdir = @sysconfdir@
 
-mandir=@mandir@
+mandir = @mandir@
 
 srcdir = @srcdir@
 VPATH = @srcdir@
index ad215cc..1991258 100644 (file)
@@ -753,6 +753,14 @@ AC_CHECK_MEMBER(struct dirent.d_type,
 [#include <dirent.h>])
 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 <pwd.h>])
+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,7 +1035,17 @@ 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([for POSIX Threads with '$opt'])
index 0960acc..a4c48e1 100644 (file)
 #    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
@@ -989,6 +958,8 @@ _git_clone ()
                        --upload-pack
                        --template=
                        --depth
+                       --single-branch
+                       --branch
                        "
                return
                ;;
@@ -1000,6 +971,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
@@ -2430,20 +2408,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
index dc5678c..8aafb63 100644 (file)
 #       (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_original_script = ${HOME}/.git-completion.bash
 set __git_tcsh_completion_script = ${HOME}/.git-completion.tcsh.bash
 
+# Check that the user put the script in the right place
+if ( ! -e ${__git_tcsh_completion_original_script} ) then
+       echo "git-completion.tcsh: Cannot find: ${__git_tcsh_completion_original_script}.  Git completion will not work."
+       exit
+endif
+
 cat << EOF > ${__git_tcsh_completion_script}
 #!bash
 #
 # This script is GENERATED and will be overwritten automatically.
-# Do not modify it directly.  Instead, modify the git-completion.tcsh
-# script provided by Git core.
-#
+# 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=(\$1)
+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 [ "\${1: -1}" == " " ]; then
-       # The last character is a space, so our location is at the end
-       # of the command-line array
-       COMP_CWORD=\${#COMP_WORDS[@]}
+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))
+       # 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
-# element of the command-line
-_\${COMP_WORDS[0]}
+# Call _git() or _gitk() of the bash script, based on the first argument
+_\${1}
 
 IFS=\$'\n'
-echo "\${COMPREPLY[*]}" | sort | uniq
+if [ \${#COMPREPLY[*]} -gt 0 ]; then
+       echo "\${COMPREPLY[*]}" | sort | uniq
+else
+       # 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.
+                       RESULT=(\`ls -dp \${TO_COMPLETE}* 2> /dev/null\`)
+                       echo \${RESULT[*]}
+
+                       # 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 [ \${#RESULT[*]} -eq 1 ] && [ "\${RESULT[0]: -1}" == "/" ]; then
+                               echo \${RESULT[*]}
+                       fi
+               fi
+       fi
+fi
+
 EOF
 
-complete git  'p/*/`bash ${__git_tcsh_completion_script} "${COMMAND_LINE}"`/'
-complete gitk 'p/*/`bash ${__git_tcsh_completion_script} "${COMMAND_LINE}"`/'
+# 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 (file)
index 0000000..4577502
--- /dev/null
@@ -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
index 00fc099..9bef053 100644 (file)
 #    1) Copy this file to somewhere (e.g. ~/.git-prompt.sh).
 #    2) Add the following line to your .bashrc/.zshrc:
 #        source ~/.git-prompt.sh
-#    3a) In ~/.bashrc set PROMPT_COMMAND=__git_ps1
-#        To customize the prompt, provide start/end arguments
-#        PROMPT_COMMAND='__git_ps1 "\u@\h:\w" "\\\$ "'
-#    3b) Alternatively change your PS1 to call __git_ps1 as
+#    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
+#        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, <pre> and
+#        <post>, 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
 # 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
 __gitdir ()
@@ -207,10 +224,12 @@ __git_ps1_show_upstream ()
 # when called from PS1 using command substitution
 # in this mode it prints text to add to bash PS1 prompt (includes branch name)
 #
-# __git_ps1 requires 2 arguments when called from PROMPT_COMMAND (pc)
+# __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 both arguments are given, the first is prepended and the second appended
+# 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 ()
 {
@@ -221,9 +240,10 @@ __git_ps1 ()
        local printf_format=' (%s)'
 
        case "$#" in
-               2)      pcmode=yes
+               2|3)    pcmode=yes
                        ps1pc_start="$1"
                        ps1pc_end="$2"
+                       printf_format="${3:-$printf_format}"
                ;;
                0|1)    printf_format="${1:-$printf_format}"
                ;;
@@ -324,6 +344,7 @@ __git_ps1 ()
 
                local f="$w$i$s$u"
                if [ $pcmode = yes ]; then
+                       local gitstring=
                        if [ -n "${GIT_PS1_SHOWCOLORHINTS-}" ]; then
                                local c_red='\e[31m'
                                local c_green='\e[32m'
@@ -341,29 +362,31 @@ __git_ps1 ()
                                        branch_color="$bad_color"
                                fi
 
-                               # Setting PS1 directly with \[ and \] around colors
+                               # Setting gitstring directly with \[ and \] around colors
                                # is necessary to prevent wrapping issues!
-                               PS1="$ps1pc_start (\[$branch_color\]$branchstring\[$c_clear\]"
+                               gitstring="\[$branch_color\]$branchstring\[$c_clear\]"
 
                                if [ -n "$w$i$s$u$r$p" ]; then
-                                       PS1="$PS1 "
+                                       gitstring="$gitstring "
                                fi
                                if [ "$w" = "*" ]; then
-                                       PS1="$PS1\[$bad_color\]$w"
+                                       gitstring="$gitstring\[$bad_color\]$w"
                                fi
                                if [ -n "$i" ]; then
-                                       PS1="$PS1\[$ok_color\]$i"
+                                       gitstring="$gitstring\[$ok_color\]$i"
                                fi
                                if [ -n "$s" ]; then
-                                       PS1="$PS1\[$flags_color\]$s"
+                                       gitstring="$gitstring\[$flags_color\]$s"
                                fi
                                if [ -n "$u" ]; then
-                                       PS1="$PS1\[$bad_color\]$u"
+                                       gitstring="$gitstring\[$bad_color\]$u"
                                fi
-                               PS1="$PS1\[$c_clear\]$r$p)$ps1pc_end"
+                               gitstring="$gitstring\[$c_clear\]$r$p"
                        else
-                               PS1="$ps1pc_start ($c${b##refs/heads/}${f:+ $f}$r$p)$ps1pc_end"
+                               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"
index 68555d4..094129d 100755 (executable)
@@ -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
index 246d47d..b6405ce 100755 (executable)
@@ -318,4 +318,30 @@ test_expect_success 'git push with \ in format control' '
 '
 
 
+test_expect_success 'fast-import meta-characters in page name (mw -> git)' '
+       wiki_reset &&
+       wiki_editpage \"file\"_\\_foo "expect to be called \"file\"_\\_foo" false &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir_21 &&
+       test_path_is_file mw_dir_21/\"file\"_\\_foo.mw &&
+       wiki_getallpage ref_page_21 &&
+       test_diff_directories mw_dir_21 ref_page_21
+'
+
+
+test_expect_success 'fast-import meta-characters in page name (git -> mw) ' '
+       wiki_reset &&
+       git clone mediawiki::'"$WIKI_URL"' mw_dir_22 &&
+       (
+               cd mw_dir_22 &&
+               echo "this file is called \"file\"_\\_foo.mw" >\"file\"_\\_foo &&
+               git add . &&
+               git commit -am "file \"file\"_\\_foo" &&
+               git pull &&
+               git push
+       ) &&
+       wiki_getallpage ref_page_22 &&
+       test_diff_directories mw_dir_22 ref_page_22
+'
+
+
 test_done
diff --git a/contrib/remote-helpers/Makefile b/contrib/remote-helpers/Makefile
new file mode 100644 (file)
index 0000000..9a76575
--- /dev/null
@@ -0,0 +1,13 @@
+TESTS := $(wildcard test*.sh)
+
+export T := $(addprefix $(CURDIR)/,$(TESTS))
+export MAKE := $(MAKE) -e
+export PATH := $(CURDIR):$(PATH)
+
+test:
+       $(MAKE) -C ../../t $@
+
+$(TESTS):
+       $(MAKE) -C ../../t $(CURDIR)/$@
+
+.PHONY: $(TESTS)
diff --git a/contrib/remote-helpers/git-remote-hg b/contrib/remote-helpers/git-remote-hg
new file mode 100755 (executable)
index 0000000..016cdad
--- /dev/null
@@ -0,0 +1,794 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2012 Felipe Contreras
+#
+
+# Inspired by Rocco Rutte's hg-fast-export
+
+# Just copy to your ~/bin, or anywhere in your $PATH.
+# Then you can clone with:
+# git clone hg::/path/to/mercurial/repo/
+
+from mercurial import hg, ui, bookmarks, context, util, encoding
+
+import re
+import sys
+import os
+import json
+import shutil
+import subprocess
+import urllib
+
+#
+# If you want to switch to hg-git compatibility mode:
+# git config --global remote-hg.hg-git-compat true
+#
+# git:
+# Sensible defaults for git.
+# hg bookmarks are exported as git branches, hg branches are prefixed
+# with 'branches/', HEAD is a special case.
+#
+# hg:
+# Emulate hg-git.
+# Only hg bookmarks are exported as git branches.
+# Commits are modified to preserve hg information and allow biridectionality.
+#
+
+NAME_RE = re.compile('^([^<>]+)')
+AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]*)>$')
+AUTHOR_HG_RE = re.compile('^(.*?) ?<(.*?)(?:>(.+)?)?$')
+RAW_AUTHOR_RE = re.compile('^(\w+) (?:(.+)? )?<(.*)> (\d+) ([+-]\d+)')
+
+def die(msg, *args):
+    sys.stderr.write('ERROR: %s\n' % (msg % args))
+    sys.exit(1)
+
+def warn(msg, *args):
+    sys.stderr.write('WARNING: %s\n' % (msg % args))
+
+def gitmode(flags):
+    return 'l' in flags and '120000' or 'x' in flags and '100755' or '100644'
+
+def gittz(tz):
+    return '%+03d%02d' % (-tz / 3600, -tz % 3600 / 60)
+
+def hgmode(mode):
+    m = { '0100755': 'x', '0120000': 'l' }
+    return m.get(mode, '')
+
+def get_config(config):
+    cmd = ['git', 'config', '--get', config]
+    process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
+    output, _ = process.communicate()
+    return output
+
+class Marks:
+
+    def __init__(self, path):
+        self.path = path
+        self.tips = {}
+        self.marks = {}
+        self.rev_marks = {}
+        self.last_mark = 0
+
+        self.load()
+
+    def load(self):
+        if not os.path.exists(self.path):
+            return
+
+        tmp = json.load(open(self.path))
+
+        self.tips = tmp['tips']
+        self.marks = tmp['marks']
+        self.last_mark = tmp['last-mark']
+
+        for rev, mark in self.marks.iteritems():
+            self.rev_marks[mark] = int(rev)
+
+    def dict(self):
+        return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark }
+
+    def store(self):
+        json.dump(self.dict(), open(self.path, 'w'))
+
+    def __str__(self):
+        return str(self.dict())
+
+    def from_rev(self, rev):
+        return self.marks[str(rev)]
+
+    def to_rev(self, mark):
+        return self.rev_marks[mark]
+
+    def get_mark(self, rev):
+        self.last_mark += 1
+        self.marks[str(rev)] = self.last_mark
+        return self.last_mark
+
+    def new_mark(self, rev, mark):
+        self.marks[str(rev)] = mark
+        self.rev_marks[mark] = rev
+        self.last_mark = mark
+
+    def is_marked(self, rev):
+        return self.marks.has_key(str(rev))
+
+    def get_tip(self, branch):
+        return self.tips.get(branch, 0)
+
+    def set_tip(self, branch, tip):
+        self.tips[branch] = tip
+
+class Parser:
+
+    def __init__(self, repo):
+        self.repo = repo
+        self.line = self.get_line()
+
+    def get_line(self):
+        return sys.stdin.readline().strip()
+
+    def __getitem__(self, i):
+        return self.line.split()[i]
+
+    def check(self, word):
+        return self.line.startswith(word)
+
+    def each_block(self, separator):
+        while self.line != separator:
+            yield self.line
+            self.line = self.get_line()
+
+    def __iter__(self):
+        return self.each_block('')
+
+    def next(self):
+        self.line = self.get_line()
+        if self.line == 'done':
+            self.line = None
+
+    def get_mark(self):
+        i = self.line.index(':') + 1
+        return int(self.line[i:])
+
+    def get_data(self):
+        if not self.check('data'):
+            return None
+        i = self.line.index(' ') + 1
+        size = int(self.line[i:])
+        return sys.stdin.read(size)
+
+    def get_author(self):
+        global bad_mail
+
+        ex = None
+        m = RAW_AUTHOR_RE.match(self.line)
+        if not m:
+            return None
+        _, name, email, date, tz = m.groups()
+        if name and 'ext:' in name:
+            m = re.match('^(.+?) ext:\((.+)\)$', name)
+            if m:
+                name = m.group(1)
+                ex = urllib.unquote(m.group(2))
+
+        if email != bad_mail:
+            if name:
+                user = '%s <%s>' % (name, email)
+            else:
+                user = '<%s>' % (email)
+        else:
+            user = name
+
+        if ex:
+            user += ex
+
+        tz = int(tz)
+        tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
+        return (user, int(date), -tz)
+
+def export_file(fc):
+    d = fc.data()
+    print "M %s inline %s" % (gitmode(fc.flags()), fc.path())
+    print "data %d" % len(d)
+    print d
+
+def get_filechanges(repo, ctx, parent):
+    modified = set()
+    added = set()
+    removed = set()
+
+    cur = ctx.manifest()
+    prev = repo[parent].manifest().copy()
+
+    for fn in cur:
+        if fn in prev:
+            if (cur.flags(fn) != prev.flags(fn) or cur[fn] != prev[fn]):
+                modified.add(fn)
+            del prev[fn]
+        else:
+            added.add(fn)
+    removed |= set(prev.keys())
+
+    return added | modified, removed
+
+def fixup_user_git(user):
+    name = mail = None
+    user = user.replace('"', '')
+    m = AUTHOR_RE.match(user)
+    if m:
+        name = m.group(1)
+        mail = m.group(2).strip()
+    else:
+        m = NAME_RE.match(user)
+        if m:
+            name = m.group(1).strip()
+    return (name, mail)
+
+def fixup_user_hg(user):
+    def sanitize(name):
+        # stole this from hg-git
+        return re.sub('[<>\n]', '?', name.lstrip('< ').rstrip('> '))
+
+    m = AUTHOR_HG_RE.match(user)
+    if m:
+        name = sanitize(m.group(1))
+        mail = sanitize(m.group(2))
+        ex = m.group(3)
+        if ex:
+            name += ' ext:(' + urllib.quote(ex) + ')'
+    else:
+        name = sanitize(user)
+        if '@' in user:
+            mail = name
+        else:
+            mail = None
+
+    return (name, mail)
+
+def fixup_user(user):
+    global mode, bad_mail
+
+    if mode == 'git':
+        name, mail = fixup_user_git(user)
+    else:
+        name, mail = fixup_user_hg(user)
+
+    if not name:
+        name = bad_name
+    if not mail:
+        mail = bad_mail
+
+    return '%s <%s>' % (name, mail)
+
+def get_repo(url, alias):
+    global dirname, peer
+
+    myui = ui.ui()
+    myui.setconfig('ui', 'interactive', 'off')
+
+    if hg.islocal(url):
+        repo = hg.repository(myui, url)
+    else:
+        local_path = os.path.join(dirname, 'clone')
+        if not os.path.exists(local_path):
+            peer, dstpeer = hg.clone(myui, {}, url, local_path, update=False, pull=True)
+            repo = dstpeer.local()
+        else:
+            repo = hg.repository(myui, local_path)
+            peer = hg.peer(myui, {}, url)
+            repo.pull(peer, heads=None, force=True)
+
+    return repo
+
+def rev_to_mark(rev):
+    global marks
+    return marks.from_rev(rev)
+
+def mark_to_rev(mark):
+    global marks
+    return marks.to_rev(mark)
+
+def export_ref(repo, name, kind, head):
+    global prefix, marks, mode
+
+    ename = '%s/%s' % (kind, name)
+    tip = marks.get_tip(ename)
+
+    # mercurial takes too much time checking this
+    if tip and tip == head.rev():
+        # nothing to do
+        return
+    revs = xrange(tip, head.rev() + 1)
+    count = 0
+
+    revs = [rev for rev in revs if not marks.is_marked(rev)]
+
+    for rev in revs:
+
+        c = repo[rev]
+        (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(c.node())
+        rev_branch = extra['branch']
+
+        author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
+        if 'committer' in extra:
+            user, time, tz = extra['committer'].rsplit(' ', 2)
+            committer = "%s %s %s" % (user, time, gittz(int(tz)))
+        else:
+            committer = author
+
+        parents = [p for p in repo.changelog.parentrevs(rev) if p >= 0]
+
+        if len(parents) == 0:
+            modified = c.manifest().keys()
+            removed = []
+        else:
+            modified, removed = get_filechanges(repo, c, parents[0])
+
+        if mode == 'hg':
+            extra_msg = ''
+
+            if rev_branch != 'default':
+                extra_msg += 'branch : %s\n' % rev_branch
+
+            renames = []
+            for f in c.files():
+                if f not in c.manifest():
+                    continue
+                rename = c.filectx(f).renamed()
+                if rename:
+                    renames.append((rename[0], f))
+
+            for e in renames:
+                extra_msg += "rename : %s => %s\n" % e
+
+            for key, value in extra.iteritems():
+                if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
+                    continue
+                else:
+                    extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
+
+            desc += '\n'
+            if extra_msg:
+                desc += '\n--HG--\n' + extra_msg
+
+        if len(parents) == 0 and rev:
+            print 'reset %s/%s' % (prefix, ename)
+
+        print "commit %s/%s" % (prefix, ename)
+        print "mark :%d" % (marks.get_mark(rev))
+        print "author %s" % (author)
+        print "committer %s" % (committer)
+        print "data %d" % (len(desc))
+        print desc
+
+        if len(parents) > 0:
+            print "from :%s" % (rev_to_mark(parents[0]))
+            if len(parents) > 1:
+                print "merge :%s" % (rev_to_mark(parents[1]))
+
+        for f in modified:
+            export_file(c.filectx(f))
+        for f in removed:
+            print "D %s" % (f)
+        print
+
+        count += 1
+        if (count % 100 == 0):
+            print "progress revision %d '%s' (%d/%d)" % (rev, name, count, len(revs))
+            print "#############################################################"
+
+    # make sure the ref is updated
+    print "reset %s/%s" % (prefix, ename)
+    print "from :%u" % rev_to_mark(rev)
+    print
+
+    marks.set_tip(ename, rev)
+
+def export_tag(repo, tag):
+    export_ref(repo, tag, 'tags', repo[tag])
+
+def export_bookmark(repo, bmark):
+    head = bmarks[bmark]
+    export_ref(repo, bmark, 'bookmarks', head)
+
+def export_branch(repo, branch):
+    tip = get_branch_tip(repo, branch)
+    head = repo[tip]
+    export_ref(repo, branch, 'branches', head)
+
+def export_head(repo):
+    global g_head
+    export_ref(repo, g_head[0], 'bookmarks', g_head[1])
+
+def do_capabilities(parser):
+    global prefix, dirname
+
+    print "import"
+    print "export"
+    print "refspec refs/heads/branches/*:%s/branches/*" % prefix
+    print "refspec refs/heads/*:%s/bookmarks/*" % prefix
+    print "refspec refs/tags/*:%s/tags/*" % prefix
+
+    path = os.path.join(dirname, 'marks-git')
+
+    if os.path.exists(path):
+        print "*import-marks %s" % path
+    print "*export-marks %s" % path
+
+    print
+
+def get_branch_tip(repo, branch):
+    global branches
+
+    heads = branches.get(branch, None)
+    if not heads:
+        return None
+
+    # verify there's only one head
+    if (len(heads) > 1):
+        warn("Branch '%s' has more than one head, consider merging" % branch)
+        # older versions of mercurial don't have this
+        if hasattr(repo, "branchtip"):
+            return repo.branchtip(branch)
+
+    return heads[0]
+
+def list_head(repo, cur):
+    global g_head, bmarks
+
+    head = bookmarks.readcurrent(repo)
+    if head:
+        node = repo[head]
+    else:
+        # fake bookmark from current branch
+        head = cur
+        node = repo['.']
+        if not node:
+            node = repo['tip']
+        if not node:
+            return
+        if head == 'default':
+            head = 'master'
+        bmarks[head] = node
+
+    print "@refs/heads/%s HEAD" % head
+    g_head = (head, node)
+
+def do_list(parser):
+    global branches, bmarks, mode, track_branches
+
+    repo = parser.repo
+    for bmark, node in bookmarks.listbookmarks(repo).iteritems():
+        bmarks[bmark] = repo[node]
+
+    cur = repo.dirstate.branch()
+
+    list_head(repo, cur)
+
+    if track_branches:
+        for branch in repo.branchmap():
+            heads = repo.branchheads(branch)
+            if len(heads):
+                branches[branch] = heads
+
+        for branch in branches:
+            print "? refs/heads/branches/%s" % branch
+
+    for bmark in bmarks:
+        print "? refs/heads/%s" % bmark
+
+    for tag, node in repo.tagslist():
+        if tag == 'tip':
+            continue
+        print "? refs/tags/%s" % tag
+
+    print
+
+def do_import(parser):
+    repo = parser.repo
+
+    path = os.path.join(dirname, 'marks-git')
+
+    print "feature done"
+    if os.path.exists(path):
+        print "feature import-marks=%s" % path
+    print "feature export-marks=%s" % path
+    sys.stdout.flush()
+
+    tmp = encoding.encoding
+    encoding.encoding = 'utf-8'
+
+    # lets get all the import lines
+    while parser.check('import'):
+        ref = parser[1]
+
+        if (ref == 'HEAD'):
+            export_head(repo)
+        elif ref.startswith('refs/heads/branches/'):
+            branch = ref[len('refs/heads/branches/'):]
+            export_branch(repo, branch)
+        elif ref.startswith('refs/heads/'):
+            bmark = ref[len('refs/heads/'):]
+            export_bookmark(repo, bmark)
+        elif ref.startswith('refs/tags/'):
+            tag = ref[len('refs/tags/'):]
+            export_tag(repo, tag)
+
+        parser.next()
+
+    encoding.encoding = tmp
+
+    print 'done'
+
+def parse_blob(parser):
+    global blob_marks
+
+    parser.next()
+    mark = parser.get_mark()
+    parser.next()
+    data = parser.get_data()
+    blob_marks[mark] = data
+    parser.next()
+    return
+
+def get_merge_files(repo, p1, p2, files):
+    for e in repo[p1].files():
+        if e not in files:
+            if e not in repo[p1].manifest():
+                continue
+            f = { 'ctx' : repo[p1][e] }
+            files[e] = f
+
+def parse_commit(parser):
+    global marks, blob_marks, bmarks, parsed_refs
+    global mode
+
+    from_mark = merge_mark = None
+
+    ref = parser[1]
+    parser.next()
+
+    commit_mark = parser.get_mark()
+    parser.next()
+    author = parser.get_author()
+    parser.next()
+    committer = parser.get_author()
+    parser.next()
+    data = parser.get_data()
+    parser.next()
+    if parser.check('from'):
+        from_mark = parser.get_mark()
+        parser.next()
+    if parser.check('merge'):
+        merge_mark = parser.get_mark()
+        parser.next()
+        if parser.check('merge'):
+            die('octopus merges are not supported yet')
+
+    files = {}
+
+    for line in parser:
+        if parser.check('M'):
+            t, m, mark_ref, path = line.split(' ', 3)
+            mark = int(mark_ref[1:])
+            f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
+        elif parser.check('D'):
+            t, path = line.split(' ')
+            f = { 'deleted' : True }
+        else:
+            die('Unknown file command: %s' % line)
+        files[path] = f
+
+    def getfilectx(repo, memctx, f):
+        of = files[f]
+        if 'deleted' in of:
+            raise IOError
+        if 'ctx' in of:
+            return of['ctx']
+        is_exec = of['mode'] == 'x'
+        is_link = of['mode'] == 'l'
+        rename = of.get('rename', None)
+        return context.memfilectx(f, of['data'],
+                is_link, is_exec, rename)
+
+    repo = parser.repo
+
+    user, date, tz = author
+    extra = {}
+
+    if committer != author:
+        extra['committer'] = "%s %u %u" % committer
+
+    if from_mark:
+        p1 = repo.changelog.node(mark_to_rev(from_mark))
+    else:
+        p1 = '\0' * 20
+
+    if merge_mark:
+        p2 = repo.changelog.node(mark_to_rev(merge_mark))
+    else:
+        p2 = '\0' * 20
+
+    #
+    # If files changed from any of the parents, hg wants to know, but in git if
+    # nothing changed from the first parent, nothing changed.
+    #
+    if merge_mark:
+        get_merge_files(repo, p1, p2, files)
+
+    if mode == 'hg':
+        i = data.find('\n--HG--\n')
+        if i >= 0:
+            tmp = data[i + len('\n--HG--\n'):].strip()
+            for k, v in [e.split(' : ') for e in tmp.split('\n')]:
+                if k == 'rename':
+                    old, new = v.split(' => ', 1)
+                    files[new]['rename'] = old
+                elif k == 'branch':
+                    extra[k] = v
+                elif k == 'extra':
+                    ek, ev = v.split(' : ', 1)
+                    extra[ek] = urllib.unquote(ev)
+            data = data[:i]
+
+    ctx = context.memctx(repo, (p1, p2), data,
+            files.keys(), getfilectx,
+            user, (date, tz), extra)
+
+    tmp = encoding.encoding
+    encoding.encoding = 'utf-8'
+
+    node = repo.commitctx(ctx)
+
+    encoding.encoding = tmp
+
+    rev = repo[node].rev()
+
+    parsed_refs[ref] = node
+
+    marks.new_mark(rev, commit_mark)
+
+def parse_reset(parser):
+    ref = parser[1]
+    parser.next()
+    # ugh
+    if parser.check('commit'):
+        parse_commit(parser)
+        return
+    if not parser.check('from'):
+        return
+    from_mark = parser.get_mark()
+    parser.next()
+
+    node = parser.repo.changelog.node(mark_to_rev(from_mark))
+    parsed_refs[ref] = node
+
+def parse_tag(parser):
+    name = parser[1]
+    parser.next()
+    from_mark = parser.get_mark()
+    parser.next()
+    tagger = parser.get_author()
+    parser.next()
+    data = parser.get_data()
+    parser.next()
+
+    # nothing to do
+
+def do_export(parser):
+    global parsed_refs, bmarks, peer
+
+    parser.next()
+
+    for line in parser.each_block('done'):
+        if parser.check('blob'):
+            parse_blob(parser)
+        elif parser.check('commit'):
+            parse_commit(parser)
+        elif parser.check('reset'):
+            parse_reset(parser)
+        elif parser.check('tag'):
+            parse_tag(parser)
+        elif parser.check('feature'):
+            pass
+        else:
+            die('unhandled export command: %s' % line)
+
+    for ref, node in parsed_refs.iteritems():
+        if ref.startswith('refs/heads/branches'):
+            pass
+        elif ref.startswith('refs/heads/'):
+            bmark = ref[len('refs/heads/'):]
+            if bmark in bmarks:
+                old = bmarks[bmark].hex()
+            else:
+                old = ''
+            if not bookmarks.pushbookmark(parser.repo, bmark, old, node):
+                continue
+        elif ref.startswith('refs/tags/'):
+            tag = ref[len('refs/tags/'):]
+            parser.repo.tag([tag], node, None, True, None, {})
+        else:
+            # transport-helper/fast-export bugs
+            continue
+        print "ok %s" % ref
+
+    print
+
+    if peer:
+        parser.repo.push(peer, force=False)
+
+def main(args):
+    global prefix, dirname, branches, bmarks
+    global marks, blob_marks, parsed_refs
+    global peer, mode, bad_mail, bad_name
+    global track_branches
+
+    alias = args[1]
+    url = args[2]
+    peer = None
+
+    hg_git_compat = False
+    track_branches = True
+    try:
+        if get_config('remote-hg.hg-git-compat') == 'true\n':
+            hg_git_compat = True
+            track_branches = False
+        if get_config('remote-hg.track-branches') == 'false\n':
+            track_branches = False
+    except subprocess.CalledProcessError:
+        pass
+
+    if hg_git_compat:
+        mode = 'hg'
+        bad_mail = 'none@none'
+        bad_name = ''
+    else:
+        mode = 'git'
+        bad_mail = 'unknown'
+        bad_name = 'Unknown'
+
+    if alias[4:] == url:
+        is_tmp = True
+        alias = util.sha1(alias).hexdigest()
+    else:
+        is_tmp = False
+
+    gitdir = os.environ['GIT_DIR']
+    dirname = os.path.join(gitdir, 'hg', alias)
+    branches = {}
+    bmarks = {}
+    blob_marks = {}
+    parsed_refs = {}
+
+    repo = get_repo(url, alias)
+    prefix = 'refs/hg/%s' % alias
+
+    if not os.path.exists(dirname):
+        os.makedirs(dirname)
+
+    marks_path = os.path.join(dirname, 'marks-hg')
+    marks = Marks(marks_path)
+
+    parser = Parser(repo)
+    for line in parser:
+        if parser.check('capabilities'):
+            do_capabilities(parser)
+        elif parser.check('list'):
+            do_list(parser)
+        elif parser.check('import'):
+            do_import(parser)
+        elif parser.check('export'):
+            do_export(parser)
+        else:
+            die('unhandled command: %s' % line)
+        sys.stdout.flush()
+
+    if not is_tmp:
+        marks.store()
+    else:
+        shutil.rmtree(dirname)
+
+sys.exit(main(sys.argv))
diff --git a/contrib/remote-helpers/test-hg-bidi.sh b/contrib/remote-helpers/test-hg-bidi.sh
new file mode 100755 (executable)
index 0000000..a94eb28
--- /dev/null
@@ -0,0 +1,243 @@
+#!/bin/sh
+#
+# Copyright (c) 2012 Felipe Contreras
+#
+# Base commands from hg-git tests:
+# https://bitbucket.org/durin42/hg-git/src
+#
+
+test_description='Test biridectionality of remote-hg'
+
+. ./test-lib.sh
+
+if ! test_have_prereq PYTHON; then
+       skip_all='skipping remote-hg tests; python not available'
+       test_done
+fi
+
+if ! "$PYTHON_PATH" -c 'import mercurial'; then
+       skip_all='skipping remote-hg tests; mercurial not available'
+       test_done
+fi
+
+# clone to a git repo
+git_clone () {
+       hg -R $1 bookmark -f -r tip master &&
+       git clone -q "hg::$PWD/$1" $2
+}
+
+# clone to an hg repo
+hg_clone () {
+       (
+       hg init $2 &&
+       cd $1 &&
+       git push -q "hg::$PWD/../$2" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*'
+       ) &&
+
+       (cd $2 && hg -q update)
+}
+
+# push an hg repo
+hg_push () {
+       (
+       cd $2
+       old=$(git symbolic-ref --short HEAD)
+       git checkout -q -b tmp &&
+       git fetch -q "hg::$PWD/../$1" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' &&
+       git checkout -q $old &&
+       git branch -q -D tmp 2> /dev/null || true
+       )
+}
+
+hg_log () {
+       hg -R $1 log --graph --debug | grep -v 'tag: *default/'
+}
+
+setup () {
+       (
+       echo "[ui]"
+       echo "username = A U Thor <author@example.com>"
+       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 (executable)
index 0000000..3e76d9f
--- /dev/null
@@ -0,0 +1,466 @@
+#!/bin/sh
+#
+# Copyright (c) 2012 Felipe Contreras
+#
+# Base commands from hg-git tests:
+# https://bitbucket.org/durin42/hg-git/src
+#
+
+test_description='Test remote-hg output compared to hg-git'
+
+. ./test-lib.sh
+
+if ! test_have_prereq PYTHON; then
+       skip_all='skipping remote-hg tests; python not available'
+       test_done
+fi
+
+if ! "$PYTHON_PATH" -c 'import mercurial'; then
+       skip_all='skipping remote-hg tests; mercurial not available'
+       test_done
+fi
+
+if ! "$PYTHON_PATH" -c 'import hggit'; then
+       skip_all='skipping remote-hg tests; hg-git not available'
+       test_done
+fi
+
+# clone to a git repo with git
+git_clone_git () {
+       hg -R $1 bookmark -f -r tip master &&
+       git clone -q "hg::$PWD/$1" $2
+}
+
+# clone to an hg repo with git
+hg_clone_git () {
+       (
+       hg init $2 &&
+       cd $1 &&
+       git push -q "hg::$PWD/../$2" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*'
+       ) &&
+
+       (cd $2 && hg -q update)
+}
+
+# clone to a git repo with hg
+git_clone_hg () {
+       (
+       git init -q $2 &&
+       cd $1 &&
+       hg bookmark -f -r tip master &&
+       hg -q push -r master ../$2 || true
+       )
+}
+
+# clone to an hg repo with hg
+hg_clone_hg () {
+       hg -q clone $1 $2
+}
+
+# push an hg repo with git
+hg_push_git () {
+       (
+       cd $2
+       old=$(git symbolic-ref --short HEAD)
+       git checkout -q -b tmp &&
+       git fetch -q "hg::$PWD/../$1" 'refs/tags/*:refs/tags/*' 'refs/heads/*:refs/heads/*' &&
+       git checkout -q $old &&
+       git branch -q -D tmp 2> /dev/null || true
+       )
+}
+
+# push an hg git repo with hg
+hg_push_hg () {
+       (
+       cd $1 &&
+       hg -q push ../$2 || true
+       )
+}
+
+hg_log () {
+       hg -R $1 log --graph --debug | grep -v 'tag: *default/'
+}
+
+git_log () {
+       git --git-dir=$1/.git fast-export --branches
+}
+
+setup () {
+       (
+       echo "[ui]"
+       echo "username = A U Thor <author@example.com>"
+       echo "[defaults]"
+       echo "backout = -d \"0 0\""
+       echo "commit = -d \"0 0\""
+       echo "debugrawcommit = -d \"0 0\""
+       echo "tag = -d \"0 0\""
+       echo "[extensions]"
+       echo "hgext.bookmarks ="
+       echo "hggit ="
+       ) >> "$HOME"/.hgrc &&
+       git config --global receive.denycurrentbranch warn
+       git config --global remote-hg.hg-git-compat true
+
+       export HGEDITOR=/usr/bin/true
+
+       export GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0230"
+       export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
+}
+
+setup
+
+test_expect_success 'merge conflict 1' '
+       mkdir -p tmp && cd tmp &&
+       test_when_finished "cd .. && rm -rf tmp" &&
+
+       (
+       hg init hgrepo1 &&
+       cd hgrepo1 &&
+       echo A > afile &&
+       hg add afile &&
+       hg ci -m "origin" &&
+
+       echo B > afile &&
+       hg ci -m "A->B" &&
+
+       hg up -r0 &&
+       echo C > afile &&
+       hg ci -m "A->C" &&
+
+       hg merge -r1 || true &&
+       echo C > afile &&
+       hg resolve -m afile &&
+       hg ci -m "merge to C"
+       ) &&
+
+       for x in hg git; do
+               git_clone_$x hgrepo1 gitrepo-$x &&
+               hg_clone_$x gitrepo-$x hgrepo2-$x &&
+               hg_log hgrepo2-$x > hg-log-$x &&
+               git_log gitrepo-$x > git-log-$x
+       done &&
+
+       test_cmp hg-log-hg hg-log-git &&
+       test_cmp git-log-hg git-log-git
+'
+
+test_expect_success 'merge conflict 2' '
+       mkdir -p tmp && cd tmp &&
+       test_when_finished "cd .. && rm -rf tmp" &&
+
+       (
+       hg init hgrepo1 &&
+       cd hgrepo1 &&
+       echo A > afile &&
+       hg add afile &&
+       hg ci -m "origin" &&
+
+       echo B > afile &&
+       hg ci -m "A->B" &&
+
+       hg up -r0 &&
+       echo C > afile &&
+       hg ci -m "A->C" &&
+
+       hg merge -r1 || true &&
+       echo B > afile &&
+       hg resolve -m afile &&
+       hg ci -m "merge to B"
+       ) &&
+
+       for x in hg git; do
+               git_clone_$x hgrepo1 gitrepo-$x &&
+               hg_clone_$x gitrepo-$x hgrepo2-$x &&
+               hg_log hgrepo2-$x > hg-log-$x &&
+               git_log gitrepo-$x > git-log-$x
+       done &&
+
+       test_cmp hg-log-hg hg-log-git &&
+       test_cmp git-log-hg git-log-git
+'
+
+test_expect_success 'converged merge' '
+       mkdir -p tmp && cd tmp &&
+       test_when_finished "cd .. && rm -rf tmp" &&
+
+       (
+       hg init hgrepo1 &&
+       cd hgrepo1 &&
+       echo A > afile &&
+       hg add afile &&
+       hg ci -m "origin" &&
+
+       echo B > afile &&
+       hg ci -m "A->B" &&
+
+       echo C > afile &&
+       hg ci -m "B->C" &&
+
+       hg up -r0 &&
+       echo C > afile &&
+       hg ci -m "A->C" &&
+
+       hg merge -r2 || true &&
+       hg ci -m "merge"
+       ) &&
+
+       for x in hg git; do
+               git_clone_$x hgrepo1 gitrepo-$x &&
+               hg_clone_$x gitrepo-$x hgrepo2-$x &&
+               hg_log hgrepo2-$x > hg-log-$x &&
+               git_log gitrepo-$x > git-log-$x
+       done &&
+
+       test_cmp hg-log-hg hg-log-git &&
+       test_cmp git-log-hg git-log-git
+'
+
+test_expect_success 'encoding' '
+       mkdir -p tmp && cd tmp &&
+       test_when_finished "cd .. && rm -rf tmp" &&
+
+       (
+       git init -q gitrepo &&
+       cd gitrepo &&
+
+       echo alpha > alpha &&
+       git add alpha &&
+       git commit -m "add älphà" &&
+
+       export GIT_AUTHOR_NAME="tést èncödîng" &&
+       echo beta > beta &&
+       git add beta &&
+       git commit -m "add beta" &&
+
+       echo gamma > gamma &&
+       git add gamma &&
+       git commit -m "add gämmâ" &&
+
+       : TODO git config i18n.commitencoding latin-1 &&
+       echo delta > delta &&
+       git add delta &&
+       git commit -m "add déltà"
+       ) &&
+
+       for x in hg git; do
+               hg_clone_$x gitrepo hgrepo-$x &&
+               git_clone_$x hgrepo-$x gitrepo2-$x &&
+
+               HGENCODING=utf-8 hg_log hgrepo-$x > hg-log-$x &&
+               git_log gitrepo2-$x > git-log-$x
+       done &&
+
+       test_cmp hg-log-hg hg-log-git &&
+       test_cmp git-log-hg git-log-git
+'
+
+test_expect_success 'file removal' '
+       mkdir -p tmp && cd tmp &&
+       test_when_finished "cd .. && rm -rf tmp" &&
+
+       (
+       git init -q gitrepo &&
+       cd gitrepo &&
+       echo alpha > alpha &&
+       git add alpha &&
+       git commit -m "add alpha" &&
+       echo beta > beta &&
+       git add beta &&
+       git commit -m "add beta"
+       mkdir foo &&
+       echo blah > foo/bar &&
+       git add foo &&
+       git commit -m "add foo" &&
+       git rm alpha &&
+       git commit -m "remove alpha" &&
+       git rm foo/bar &&
+       git commit -m "remove foo/bar"
+       ) &&
+
+       for x in hg git; do
+               (
+               hg_clone_$x gitrepo hgrepo-$x &&
+               cd hgrepo-$x &&
+               hg_log . &&
+               hg manifest -r 3 &&
+               hg manifest
+               ) > output-$x &&
+
+               git_clone_$x hgrepo-$x gitrepo2-$x &&
+               git_log gitrepo2-$x > log-$x
+       done &&
+
+       test_cmp output-hg output-git &&
+       test_cmp log-hg log-git
+'
+
+test_expect_success 'git tags' '
+       mkdir -p tmp && cd tmp &&
+       test_when_finished "cd .. && rm -rf tmp" &&
+
+       (
+       git init -q gitrepo &&
+       cd gitrepo &&
+       git config receive.denyCurrentBranch ignore &&
+       echo alpha > alpha &&
+       git add alpha &&
+       git commit -m "add alpha" &&
+       git tag alpha &&
+
+       echo beta > beta &&
+       git add beta &&
+       git commit -m "add beta" &&
+       git tag -a -m "added tag beta" beta
+       ) &&
+
+       for x in hg git; do
+               hg_clone_$x gitrepo hgrepo-$x &&
+               hg_log hgrepo-$x > log-$x
+       done &&
+
+       test_cmp log-hg log-git
+'
+
+test_expect_success 'hg author' '
+       mkdir -p tmp && cd tmp &&
+       test_when_finished "cd .. && rm -rf tmp" &&
+
+       for x in hg git; do
+               (
+               git init -q gitrepo-$x &&
+               cd gitrepo-$x &&
+
+               echo alpha > alpha &&
+               git add alpha &&
+               git commit -m "add alpha" &&
+               git checkout -q -b not-master
+               ) &&
+
+               (
+               hg_clone_$x gitrepo-$x hgrepo-$x &&
+               cd hgrepo-$x &&
+
+               hg co master &&
+               echo beta > beta &&
+               hg add beta &&
+               hg commit -u "test" -m "add beta" &&
+
+               echo gamma >> beta &&
+               hg commit -u "test <test@example.com> (comment)" -m "modify beta" &&
+
+               echo gamma > gamma &&
+               hg add gamma &&
+               hg commit -u "<test@example.com>" -m "add gamma" &&
+
+               echo delta > delta &&
+               hg add delta &&
+               hg commit -u "name<test@example.com>" -m "add delta" &&
+
+               echo epsilon > epsilon &&
+               hg add epsilon &&
+               hg commit -u "name <test@example.com" -m "add epsilon" &&
+
+               echo zeta > 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 <test <at> example <dot> 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 (executable)
index 0000000..5f81dfa
--- /dev/null
@@ -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 <wells@example.com>"
+       ) >> "$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
index 4b852e2..9513f5e 100755 (executable)
@@ -1,38 +1,70 @@
-#!/usr/bin/perl -w
-my %mailmap = ();
-open I, "<", ".mailmap";
-while (<I>) {
-       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 (<I>) {
-       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";
+}
index 7e77c9d..91360a3 100644 (file)
@@ -1,4 +1,5 @@
 *~
+git-subtree
 git-subtree.xml
 git-subtree.1
 mainline
index 0c44fda..c5bce41 100644 (file)
@@ -93,7 +93,7 @@ pull::
        repository.
        
 push::
-       Does a 'split' (see above) using the <prefix> supplied
+       Does a 'split' (see below) using the <prefix> 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/diff.c b/diff.c
index cb1fd9a..732d4c2 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -1497,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_interesting &&
-                        (change == 0)) {
+
+               if (!file->is_interesting && (change == 0)) {
                        count++; /* not shown == room for one more */
                        continue;
                }
@@ -1525,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.
@@ -1613,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_interesting &&
-                        (added + deleted == 0)) {
-                       total_files--;
+               if (!file->is_interesting && (added + deleted == 0))
                        continue;
-               }
+
                /*
                 * "scale" the filename
                 */
@@ -1638,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");
@@ -1655,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");
@@ -1667,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;
@@ -1694,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_interesting &&
-                        (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;
@@ -1723,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_interesting && (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;
@@ -2438,7 +2442,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
        }
 
        data = diffstat_add(diffstat, name_a, name_b);
-       data->is_interesting = p->status != 0;
+       data->is_interesting = p->status != DIFF_STATUS_UNKNOWN;
 
        if (!one || !two) {
                data->is_unmerged = 1;
index a209376..b097fa7 100644 (file)
@@ -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 5a83aa7..095ea7e 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -34,10 +34,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 +70,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 +140,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 +150,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 +162,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 +172,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;
@@ -230,7 +256,10 @@ 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;
@@ -1429,9 +1458,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,
@@ -1445,3 +1483,11 @@ 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;
+}
diff --git a/dir.h b/dir.h
index f5c89e3..ab5af42 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -139,4 +139,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
index d834003..065a7ab 100644 (file)
--- 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);
        }
index 099ff4d..f0acdf7 100644 (file)
@@ -874,8 +874,6 @@ static int fetch_pack_config(const char *var, const char *value, void *cb)
        return git_default_config(var, value, cb);
 }
 
-static struct lock_file lock;
-
 static void fetch_pack_setup(void)
 {
        static int did_setup;
@@ -917,6 +915,7 @@ struct ref *fetch_pack(struct fetch_pack_args *args,
        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");
diff --git a/fsck.c b/fsck.c
index 7395ef6..99c0497 100644 (file)
--- 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)
index 2e79b8a..9661bc9 100644 (file)
@@ -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
 #include <stdlib.h>
 #include <stdarg.h>
 #include <string.h>
-#ifdef __TANDEM /* or HAVE_STRINGS_H or !NO_STRINGS_H? */
+#ifdef HAVE_STRINGS_H
 #include <strings.h> /* for strcasecmp() */
 #endif
 #include <errno.h>
 #include <limits.h>
+#ifdef NEEDS_SYS_PARAM_H
 #include <sys/param.h>
+#endif
 #include <sys/types.h>
 #include <dirent.h>
 #include <sys/time.h>
@@ -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.
+ */
+#ifdef __GNUC__
+#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
index 7d6c928..551aec9 100755 (executable)
--- a/git-p4.py
+++ b/git-p4.py
@@ -129,6 +129,25 @@ def p4_has_command(cmd):
     p.communicate()
     return p.returncode == 0
 
+def p4_has_move_command():
+    """See if the move command exists, that it supports -k, and that
+       it has not been administratively disabled.  The arguments
+       must be correct, but the filenames do not have to exist.  Use
+       ones with wildcards so even if they exist, it will fail."""
+
+    if not p4_has_command("move"):
+        return False
+    cmd = p4_build_cmd(["move", "-k", "@from", "@to"])
+    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    (out, err) = p.communicate()
+    # return code will be 1 in either case
+    if err.find("Invalid option") >= 0:
+        return False
+    if err.find("disabled") >= 0:
+        return False
+    # assume it failed because @... was invalid changelist
+    return True
+
 def system(cmd):
     expand = isinstance(cmd,basestring)
     if verbose:
@@ -169,6 +188,29 @@ def p4_reopen(type, f):
 def p4_move(src, dest):
     p4_system(["move", "-k", wildcard_encode(src), wildcard_encode(dest)])
 
+def p4_describe(change):
+    """Make sure it returns a valid result by checking for
+       the presence of field "time".  Return a dict of the
+       results."""
+
+    ds = p4CmdList(["describe", "-s", str(change)])
+    if len(ds) != 1:
+        die("p4 describe -s %d did not return 1 result: %s" % (change, str(ds)))
+
+    d = ds[0]
+
+    if "p4ExitCode" in d:
+        die("p4 describe -s %d exited with %d: %s" % (change, d["p4ExitCode"],
+                                                      str(d)))
+    if "code" in d:
+        if d["code"] == "error":
+            die("p4 describe -s %d returned error code: %s" % (change, str(d)))
+
+    if "time" not in d:
+        die("p4 describe -s %d returned no \"time\": %s" % (change, str(d)))
+
+    return d
+
 #
 # Canonicalize the p4 type and return a tuple of the
 # base type, plus any modifiers.  See "p4 help filetypes"
@@ -871,7 +913,7 @@ class P4Submit(Command, P4UserMap):
         self.conflict_behavior = None
         self.isWindows = (platform.system() == "Windows")
         self.exportLabels = False
-        self.p4HasMoveCommand = p4_has_command("move")
+        self.p4HasMoveCommand = p4_has_move_command()
 
     def check(self):
         if len(p4CmdList("opened ...")) > 0:
@@ -2097,6 +2139,29 @@ class P4Sync(Command, P4UserMap):
     # handle another chunk of streaming data
     def streamP4FilesCb(self, marshalled):
 
+        # catch p4 errors and complain
+        err = None
+        if "code" in marshalled:
+            if marshalled["code"] == "error":
+                if "data" in marshalled:
+                    err = marshalled["data"].rstrip()
+        if err:
+            f = None
+            if self.stream_have_file_info:
+                if "depotFile" in self.stream_file:
+                    f = self.stream_file["depotFile"]
+            # force a failure in fast-import, else an empty
+            # commit will be made
+            self.gitStream.write("\n")
+            self.gitStream.write("die-now\n")
+            self.gitStream.close()
+            # ignore errors, but make sure it exits first
+            self.importProcess.wait()
+            if f:
+                die("Error from p4 print for %s: %s" % (f, err))
+            else:
+                die("Error from p4 print: %s" % err)
+
         if marshalled.has_key('depotFile') and self.stream_have_file_info:
             # start of a new file - output the old one first
             self.streamOneP4File(self.stream_file, self.stream_contents)
@@ -2341,7 +2406,7 @@ class P4Sync(Command, P4UserMap):
                     try:
                         tmwhen = time.strptime(labelDetails['Update'], "%Y/%m/%d %H:%M:%S")
                     except ValueError:
-                        print "Could not convert label time %s" % labelDetail['Update']
+                        print "Could not convert label time %s" % labelDetails['Update']
                         tmwhen = 1
 
                     when = int(time.mktime(tmwhen))
@@ -2543,7 +2608,7 @@ class P4Sync(Command, P4UserMap):
     def importChanges(self, changes):
         cnt = 1
         for change in changes:
-            description = p4Cmd(["describe", str(change)])
+            description = p4_describe(change)
             self.updateOptionDict(description)
 
             if not self.silent:
@@ -2667,14 +2732,8 @@ class P4Sync(Command, P4UserMap):
 
         # Use time from top-most change so that all git p4 clones of
         # the same p4 repo have the same commit SHA1s.
-        res = p4CmdList("describe -s %d" % newestRevision)
-        newestTime = None
-        for r in res:
-            if r.has_key('time'):
-                newestTime = int(r['time'])
-        if newestTime is None:
-            die("\"describe -s\" on newest change %d did not give a time")
-        details["time"] = newestTime
+        res = p4_describe(newestRevision)
+        details["time"] = res["time"]
 
         self.updateOptionDict(details)
         try:
@@ -2864,12 +2923,13 @@ class P4Sync(Command, P4UserMap):
 
         self.tz = "%+03d%02d" % (- time.timezone / 3600, ((- time.timezone % 3600) / 60))
 
-        importProcess = subprocess.Popen(["git", "fast-import"],
-                                         stdin=subprocess.PIPE, stdout=subprocess.PIPE,
-                                         stderr=subprocess.PIPE);
-        self.gitOutput = importProcess.stdout
-        self.gitStream = importProcess.stdin
-        self.gitError = importProcess.stderr
+        self.importProcess = subprocess.Popen(["git", "fast-import"],
+                                              stdin=subprocess.PIPE,
+                                              stdout=subprocess.PIPE,
+                                              stderr=subprocess.PIPE);
+        self.gitOutput = self.importProcess.stdout
+        self.gitStream = self.importProcess.stdin
+        self.gitError = self.importProcess.stderr
 
         if revision:
             self.importHeadRevision(revision)
@@ -2929,7 +2989,7 @@ class P4Sync(Command, P4UserMap):
             self.importP4Labels(self.gitStream, missingP4Labels)
 
         self.gitStream.close()
-        if importProcess.wait() != 0:
+        if self.importProcess.wait() != 0:
             die("fast-import failed: %s" % self.gitError.read())
         self.gitOutput.close()
         self.gitError.close()
@@ -3128,7 +3188,6 @@ def main():
         printUsage(commands.keys())
         sys.exit(2)
 
-    cmd = ""
     cmdName = sys.argv[1]
     try:
         klass = commands[cmdName]
diff --git a/git-remote-testgit b/git-remote-testgit
new file mode 100755 (executable)
index 0000000..b395c8d
--- /dev/null
@@ -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
similarity index 99%
rename from git-remote-testgit.py
rename to git-remote-testpy.py
index 5f3ebd2..ade797b 100644 (file)
@@ -91,7 +91,7 @@ def do_capabilities(repo, args):
     if not os.path.exists(dirname):
         os.makedirs(dirname)
 
-    path = os.path.join(dirname, 'testgit.marks')
+    path = os.path.join(dirname, 'git.marks')
 
     print "*export-marks %s" % path
     if os.path.exists(path):
index 5a7c29d..94c7f76 100755 (executable)
@@ -753,16 +753,11 @@ if (!$force) {
        }
 }
 
-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 => "",
@@ -786,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(
@@ -831,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.
@@ -924,6 +954,10 @@ sub quote_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) {
@@ -951,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
@@ -1013,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../) {
@@ -1263,7 +1300,7 @@ foreach my $t (@files) {
                                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+(.*)$/) {
@@ -1372,6 +1409,9 @@ foreach my $t (@files) {
                ($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);
 
@@ -1427,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;
 }
index 22f0aed..795edd2 100644 (file)
 # 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
diff --git a/git.c b/git.c
index d33f9b3..ed66c66 100644 (file)
--- a/git.c
+++ b/git.c
@@ -135,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);
index 5c6b595..e28cc8f 100644 (file)
@@ -39,7 +39,7 @@ class GitImporter(object):
             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)
index e8812fa..656b324 100755 (executable)
@@ -540,7 +540,7 @@ our %feature = (
        # $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,
@@ -2696,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) {
@@ -5525,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 e864fe2..391a712 100644 (file)
--- 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)
index 8701c12..9923441 100644 (file)
@@ -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, "&quot;");
-                       break;
-               case '<':
-                       strbuf_addstr(&buf, "&lt;");
-                       break;
-               case '>':
-                       strbuf_addstr(&buf, "&gt;");
-                       break;
-               case '&':
-                       strbuf_addstr(&buf, "&amp;");
-                       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 0a8abf3..44f3525 100644 (file)
--- 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);
        }
diff --git a/ident.c b/ident.c
index a4bf206..ac9672f 100644 (file)
--- a/ident.c
+++ b/ident.c
 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;
        }
 
index d42e471..e521e2f 100644 (file)
@@ -69,8 +69,7 @@ struct store {
 };
 
 struct msg_data {
-       char *data;
-       int len;
+