Code

Merge branch 'jc/index-pack-reject-dups'
authorJunio C Hamano <gitster@pobox.com>
Mon, 5 Dec 2011 23:13:42 +0000 (15:13 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 5 Dec 2011 23:13:42 +0000 (15:13 -0800)
* jc/index-pack-reject-dups:
  receive-pack, fetch-pack: reject bogus pack that records objects twice

300 files changed:
Documentation/Makefile
Documentation/RelNotes/1.7.7.1.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.7.2.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.7.3.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.7.4.txt [new file with mode: 0644]
Documentation/RelNotes/1.7.8.txt [new file with mode: 0644]
Documentation/blame-options.txt
Documentation/config.txt
Documentation/diff-options.txt
Documentation/git-branch.txt
Documentation/git-check-attr.txt
Documentation/git-check-ref-format.txt
Documentation/git-cherry-pick.txt
Documentation/git-commit-tree.txt
Documentation/git-daemon.txt
Documentation/git-difftool.txt
Documentation/git-for-each-ref.txt
Documentation/git-fsck.txt
Documentation/git-grep.txt
Documentation/git-instaweb.txt
Documentation/git-mergetool.txt
Documentation/git-read-tree.txt
Documentation/git-reset.txt
Documentation/git-rev-parse.txt
Documentation/git-revert.txt
Documentation/git-submodule.txt
Documentation/git-svn.txt
Documentation/git-tag.txt
Documentation/git-update-index.txt
Documentation/git.txt
Documentation/gitweb.conf.txt [new file with mode: 0644]
Documentation/gitweb.txt [new file with mode: 0644]
Documentation/install-doc-quick.sh
Documentation/merge-options.txt
Documentation/sequencer.txt
Documentation/technical/api-argv-array.txt [new file with mode: 0644]
Documentation/technical/api-parse-options.txt
Documentation/technical/api-sha1-array.txt [new file with mode: 0644]
Documentation/technical/pack-protocol.txt
GIT-VERSION-GEN
INSTALL
Makefile
RelNotes
archive.c
argv-array.c [new file with mode: 0644]
argv-array.h [new file with mode: 0644]
attr.c
bisect.c
branch.c
builtin/apply.c
builtin/archive.c
builtin/blame.c
builtin/branch.c
builtin/check-attr.c
builtin/check-ref-format.c
builtin/checkout.c
builtin/clone.c
builtin/commit.c
builtin/config.c
builtin/diff.c
builtin/fetch-pack.c
builtin/fetch.c
builtin/fmt-merge-msg.c
builtin/for-each-ref.c
builtin/fsck.c
builtin/gc.c
builtin/grep.c
builtin/ls-files.c
builtin/ls-remote.c
builtin/merge.c
builtin/mktree.c
builtin/mv.c
builtin/name-rev.c
builtin/pack-objects.c
builtin/prune.c
builtin/receive-pack.c
builtin/reflog.c
builtin/remote.c
builtin/replace.c
builtin/rev-parse.c
builtin/revert.c
builtin/send-pack.c
builtin/show-ref.c
builtin/stripspace.c
builtin/tag.c
bundle.c
bundle.h
cache.h
command-list.txt
commit.c
commit.h
compat/inet_ntop.c
compat/mingw.c
compat/mingw.h
compat/msvc.h
compat/obstack.c
compat/qsort.c
compat/strtoimax.c [new file with mode: 0644]
compat/vcbuild/include/arpa/inet.h [deleted file]
compat/vcbuild/include/grp.h [deleted file]
compat/vcbuild/include/inttypes.h [deleted file]
compat/vcbuild/include/netdb.h [deleted file]
compat/vcbuild/include/netinet/in.h [deleted file]
compat/vcbuild/include/netinet/tcp.h [deleted file]
compat/vcbuild/include/pwd.h [deleted file]
compat/vcbuild/include/sys/ioctl.h [deleted file]
compat/vcbuild/include/sys/select.h [deleted file]
compat/vcbuild/include/sys/socket.h [deleted file]
compat/vcbuild/include/sys/wait.h [deleted file]
compat/vcbuild/include/termios.h [deleted file]
compat/win32/poll.c [new file with mode: 0644]
compat/win32/poll.h [new file with mode: 0644]
compat/win32/sys/poll.c [deleted file]
compat/win32/sys/poll.h [deleted file]
compat/win32/syslog.c
config.c
connect.c
contrib/completion/git-completion.bash
contrib/diff-highlight/README [new file with mode: 0644]
contrib/diff-highlight/diff-highlight [new file with mode: 0755]
contrib/fast-import/git-p4
contrib/fast-import/git-p4.txt
contrib/git-jump/README [new file with mode: 0644]
contrib/git-jump/git-jump [new file with mode: 0755]
contrib/mw-to-git/git-remote-mediawiki [new file with mode: 0755]
contrib/mw-to-git/git-remote-mediawiki.txt [new file with mode: 0644]
convert.c
daemon.c
date.c
diff-lib.c
diff.c
diff.h
diffcore-pickaxe.c
dir.c
dir.h
environment.c
fast-import.c
git-bisect.sh
git-compat-util.h
git-cvsserver.perl
git-difftool--helper.sh
git-difftool.perl
git-gui/git-gui.sh
git-gui/lib/blame.tcl
git-gui/lib/choose_rev.tcl
git-gui/lib/commit.tcl
git-gui/lib/index.tcl
git-gui/lib/line.tcl [new file with mode: 0644]
git-gui/lib/search.tcl
git-gui/po/README
git-gui/po/sv.po
git-merge-one-file.sh
git-mergetool.sh
git-pull.sh
git-rebase--interactive.sh
git-rebase.sh
git-send-email.perl
git-sh-setup.sh
git-submodule.sh
git-svn.perl
git-web--browse.sh
git.spec.in
git_remote_helpers/git/git.py
gitweb/INSTALL
gitweb/Makefile
gitweb/README
gitweb/gitweb.perl
gitweb/static/js/javascript-detection.js
hex.c
http-fetch.c
http-push.c
http.c
http.h
ident.c
kwset.c
list-objects.c
log-tree.c
mailmap.c
merge-recursive.c
mergetools/bc3
name-hash.c
notes-merge.c
pack-check.c
pack-refs.c
pack.h
parse-options-cb.c
parse-options.c
parse-options.h
path.c
perl/.gitignore
perl/Git.pm
pretty.c
quote.c
quote.h
reachable.c
reachable.h
read-cache.c
refs.c
refs.h
remote-curl.c
remote.c
remote.h
revision.c
revision.h
run-command.c
sequencer.h
setup.c
sha1_file.c
sha1_name.c
show-index.c
strbuf.c
submodule.c
submodule.h
t/gitweb-lib.sh
t/lib-git-p4.sh [new file with mode: 0644]
t/lib-gpg.sh [new file with mode: 0755]
t/lib-gpg/pubring.gpg [new file with mode: 0644]
t/lib-gpg/random_seed [new file with mode: 0644]
t/lib-gpg/secring.gpg [new file with mode: 0644]
t/lib-gpg/trustdb.gpg [new file with mode: 0644]
t/lib-httpd.sh
t/t0003-attributes.sh
t/t0006-date.sh
t/t0040-parse-options.sh
t/t1011-read-tree-sparse-checkout.sh
t/t1020-subdirectory.sh
t/t1300-repo-config.sh
t/t1304-default-acl.sh
t/t1402-check-ref-format.sh
t/t2020-checkout-detach.sh
t/t2022-checkout-paths.sh [new file with mode: 0755]
t/t3000-ls-files-others.sh
t/t3200-branch.sh
t/t3203-branch-output.sh
t/t3507-cherry-pick-conflict.sh
t/t3510-cherry-pick-sequence.sh
t/t4051-diff-function-context.sh [new file with mode: 0755]
t/t4254-am-corrupt.sh [new file with mode: 0755]
t/t5001-archive-attr.sh
t/t5403-post-checkout-hook.sh
t/t5501-fetch-push-alternates.sh
t/t5505-remote.sh
t/t5510-fetch.sh
t/t5516-fetch-push.sh
t/t5550-http-fetch.sh
t/t5601-clone.sh
t/t5700-clone-reference.sh
t/t6030-bisect-porcelain.sh
t/t6040-tracking-info.sh
t/t6300-for-each-ref.sh
t/t7004-tag.sh
t/t7004/pubring.gpg [deleted file]
t/t7004/random_seed [deleted file]
t/t7004/secring.gpg [deleted file]
t/t7004/trustdb.gpg [deleted file]
t/t7106-reset-sequence.sh
t/t7400-submodule-basic.sh
t/t7403-submodule-sync.sh
t/t7405-submodule-merge.sh
t/t7406-submodule-update.sh
t/t7407-submodule-foreach.sh
t/t7408-submodule-reference.sh
t/t7508-status.sh
t/t7511-status-index.sh [new file with mode: 0755]
t/t7600-merge.sh
t/t7607-merge-overwrite.sh
t/t7609-merge-co-error-msgs.sh
t/t7610-mergetool.sh
t/t7800-difftool.sh
t/t7810-grep.sh
t/t8006-blame-textconv.sh
t/t9001-send-email.sh
t/t9159-git-svn-no-parent-mergeinfo.sh
t/t9162-git-svn-dcommit-interactive.sh [new file with mode: 0755]
t/t9300-fast-import.sh
t/t9700-perl-git.sh
t/t9700/test.pl
t/t9800-git-p4.sh
t/t9801-git-p4-branch.sh [new file with mode: 0755]
t/t9802-git-p4-filetype.sh [new file with mode: 0755]
t/t9803-git-shell-metachars.sh [new file with mode: 0755]
t/t9805-skip-submit-edit.sh [new file with mode: 0755]
t/t9901-git-web--browse.sh [new file with mode: 0755]
templates/hooks--pre-commit.sample
test-ctype.c
test-parse-options.c
transport-helper.c
transport.c
tree-diff.c
tree-walk.c
tree-walk.h
tree.c
unpack-trees.c
url.c
url.h
walker.c
wt-status.c
xdiff/xdiff.h
xdiff/xemit.c
xdiff/xprepare.c

index 6346a75dda72533667a96eea2bd65bb280383f99..304b31edee2e5c4e998c48cc571c66e8264cc581 100644 (file)
@@ -1,9 +1,9 @@
 MAN1_TXT= \
        $(filter-out $(addsuffix .txt, $(ARTICLES) $(SP_ARTICLES)), \
                $(wildcard git-*.txt)) \
-       gitk.txt git.txt
+       gitk.txt gitweb.txt git.txt
 MAN5_TXT=gitattributes.txt gitignore.txt gitmodules.txt githooks.txt \
-       gitrepository-layout.txt
+       gitrepository-layout.txt gitweb.conf.txt
 MAN7_TXT=gitcli.txt gittutorial.txt gittutorial-2.txt \
        gitcvs-migration.txt gitcore-tutorial.txt gitglossary.txt \
        gitdiffcore.txt gitnamespaces.txt gitrevisions.txt gitworkflows.txt
@@ -46,8 +46,8 @@ MANPAGE_XSL = manpage-normal.xsl
 XMLTO_EXTRA =
 INSTALL?=install
 RM ?= rm -f
-DOC_REF = origin/man
-HTML_REF = origin/html
+MAN_REPO = ../../git-manpages
+HTML_REPO = ../../git-htmldocs
 
 infodir?=$(prefix)/share/info
 MAKEINFO=makeinfo
@@ -327,12 +327,23 @@ $(patsubst %.txt,%.html,$(wildcard howto/*.txt)): %.html : %.txt
 install-webdoc : html
        '$(SHELL_PATH_SQ)' ./install-webdoc.sh $(WEBDOC_DEST)
 
+# You must have a clone of git-htmldocs and git-manpages repositories
+# next to the git repository itself for the following to work.
+
 quick-install: quick-install-man
 
-quick-install-man:
-       '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(DOC_REF) $(DESTDIR)$(mandir)
+require-manrepo::
+       @if test ! -d $(MAN_REPO); \
+       then echo "git-manpages repository must exist at $(MAN_REPO)"; exit 1; fi
+
+quick-install-man: require-manrepo
+       '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(MAN_REPO) $(DESTDIR)$(mandir)
+
+require-htmlrepo::
+       @if test ! -d $(HTML_REPO); \
+       then echo "git-htmldocs repository must exist at $(HTML_REPO)"; exit 1; fi
 
-quick-install-html:
-       '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(HTML_REF) $(DESTDIR)$(htmldir)
+quick-install-html: require-htmlrepo
+       '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(HTML_REPO) $(DESTDIR)$(htmldir)
 
 .PHONY: FORCE
diff --git a/Documentation/RelNotes/1.7.7.1.txt b/Documentation/RelNotes/1.7.7.1.txt
new file mode 100644 (file)
index 0000000..ac9b838
--- /dev/null
@@ -0,0 +1,60 @@
+Git v1.7.7.1 Release Notes
+==========================
+
+Fixes since v1.7.7
+------------------
+
+ * On some BSD systems, adding +s bit on directories is detrimental
+   (it is not necessary on BSD to begin with). "git init --shared"
+   has been updated to take this into account without extra makefile
+   settings on platforms the Makefile knows about.
+
+ * After incorrectly written third-party tools store a tag object in
+   HEAD, git diagnosed it as a repository corruption and refused to
+   proceed in order to avoid spreading the damage. We now gracefully
+   recover from such a situation by pretending as if the commit that
+   is pointed at by the tag were in HEAD.
+
+ * "git apply --whitespace=error" did not bother to report the exact
+   line number in the patch that introduced new blank lines at the end
+   of the file.
+
+ * "git apply --index" did not check corrupted patch.
+
+ * "git checkout $tree $directory/" resurrected paths locally removed or
+   modified only in the working tree in $directory/ that did not appear
+   in $directory of the given $tree. They should have been kept intact.
+
+ * "git diff $tree $path" used to apply the pathspec at the output stage,
+   reading the whole tree, wasting resources.
+
+ * The code to check for updated submodules during a "git fetch" of the
+   superproject had an unnecessary quadratic loop.
+
+ * "git fetch" from a large bundle did not enable the progress output.
+
+ * When "git fsck --lost-and-found" found that an empty blob object in the
+   object store is unreachable, it incorrectly reported an error after
+   writing the lost blob out successfully.
+
+ * "git filter-branch" did not refresh the index before checking that the
+   working tree was clean.
+
+ * "git grep $tree" when run with multiple threads had an unsafe access to
+   the object database that should have been protected with mutex.
+
+ * The "--ancestry-path" option to "git log" and friends misbehaved in a
+   history with complex criss-cross merges and showed an uninteresting
+   side history as well.
+
+ * Test t1304 assumed LOGNAME is always set, which may not be true on
+   some systems.
+
+ * Tests with --valgrind failed to find "mergetool" scriptlets.
+
+ * "git patch-id" miscomputed the patch-id in a patch that has a line longer
+   than 1kB.
+
+ * When an "exec" insn failed after modifying the index and/or the working
+   tree during "rebase -i", we now check and warn that the changes need to
+   be cleaned up.
diff --git a/Documentation/RelNotes/1.7.7.2.txt b/Documentation/RelNotes/1.7.7.2.txt
new file mode 100644 (file)
index 0000000..e6bbef2
--- /dev/null
@@ -0,0 +1,44 @@
+Git v1.7.7.2 Release Notes
+==========================
+
+Fixes since v1.7.7.1
+--------------------
+
+ * We used to drop error messages from libcurl on certain kinds of
+   errors.
+
+ * Error report from smart HTTP transport, when the connection was
+   broken in the middle of a transfer, showed a useless message on
+   a corrupt packet.
+
+ * "git fetch --prune" was unsafe when used with refspecs from the
+   command line.
+
+ * The attribute mechanism did not use case insensitive match when
+   core.ignorecase was set.
+
+ * "git bisect" did not notice when it failed to update the working tree
+   to the next commit to be tested.
+
+ * "git config --bool --get-regexp" failed to separate the variable name
+   and its value "true" when the variable is defined without "= true".
+
+ * "git remote rename $a $b" were not careful to match the remote name
+   against $a (i.e. source side of the remote nickname).
+
+ * "git mergetool" did not use its arguments as pathspec, but as a path to
+   the file that may not even have any conflict.
+
+ * "git diff --[num]stat" used to use the number of lines of context
+   different from the default, potentially giving different results from
+   "git diff | diffstat" and confusing the users.
+
+ * "git pull" and "git rebase" did not work well even when GIT_WORK_TREE is
+   set correctly with GIT_DIR if the current directory is outside the working
+   tree.
+
+ * "git send-email" did not honor the configured hostname when restarting
+   the HELO/EHLO exchange after switching TLS on.
+
+ * "gitweb" used to produce a non-working link while showing the contents
+   of a blob, when JavaScript actions are enabled.
diff --git a/Documentation/RelNotes/1.7.7.3.txt b/Documentation/RelNotes/1.7.7.3.txt
new file mode 100644 (file)
index 0000000..09301f0
--- /dev/null
@@ -0,0 +1,19 @@
+Git v1.7.7.3 Release Notes
+==========================
+
+Fixes since v1.7.7.2
+--------------------
+
+ * Adjust the "quick-install-doc" procedures as preformatted
+   html/manpage are no longer in the source repository.
+
+ * The logic to optimize the locality of the data in a pack introduced in
+   1.7.7 was grossly inefficient.
+
+ * The logic to filter out forked projects in the project list in
+   "gitweb" was broken for some time.
+
+ * "git branch -m/-M" advertised to update RENAME_REF ref in the
+   commit log message that introduced the feature but not anywhere in
+   the documentation, and never did update such a ref anyway. This
+   undocumented misfeature that did not exist has been excised.
diff --git a/Documentation/RelNotes/1.7.7.4.txt b/Documentation/RelNotes/1.7.7.4.txt
new file mode 100644 (file)
index 0000000..e523448
--- /dev/null
@@ -0,0 +1,14 @@
+Git v1.7.7.4 Release Notes
+==========================
+
+Fixes since v1.7.7.3
+--------------------
+
+ * A few header dependencies were missing from the Makefile.
+
+ * Some newer parts of the code used C99 __VA_ARGS__ while we still
+   try to cater to older compilers.
+
+ * "git name-rev --all" tried to name all _objects_, naturally failing to
+   describe many blobs and trees, instead of showing only commits as
+   advertised in its documentation.
diff --git a/Documentation/RelNotes/1.7.8.txt b/Documentation/RelNotes/1.7.8.txt
new file mode 100644 (file)
index 0000000..b4d90bb
--- /dev/null
@@ -0,0 +1,161 @@
+Git v1.7.8 Release Notes
+========================
+
+Updates since v1.7.7
+--------------------
+
+ * Some git-svn, git-gui, git-p4 (in contrib) and msysgit updates.
+
+ * Updates to bash completion scripts.
+
+ * The build procedure has been taught to take advantage of computed
+   dependency automatically when the complier supports it.
+
+ * The date parser now accepts timezone designators that lack minutes
+   part and also has a colon between "hh:mm".
+
+ * The contents of the /etc/mailname file, if exists, is used as the
+   default value of the hostname part of the committer/author e-mail.
+
+ * "git am" learned how to read from patches generated by Hg.
+
+ * "git archive" talking with a remote repository can report errors
+   from the remote side in a more informative way.
+
+ * "git branch" learned an explicit --list option to ask for branches
+   listed, optionally with a glob matching pattern to limit its output.
+
+ * "git check-attr" learned "--cached" option to look at .gitattributes
+   files from the index, not from the working tree.
+
+ * Variants of "git cherry-pick" and "git revert" that take multiple
+   commits learned to "--continue" and "--abort".
+
+ * "git daemon" gives more human readble error messages to clients
+   using ERR packets when appropriate.
+
+ * Errors at the network layer is logged by "git daemon".
+
+ * "git diff" learned "--minimal" option to spend extra cycles to come
+   up with a minimal patch output.
+
+ * "git diff" learned "--function-context" option to show the whole
+   function as context that was affected by a change.
+
+ * "git difftool" can be told to skip launching the tool for a path by
+   answering 'n' to its prompt.
+
+ * "git fetch" learned to honor transfer.fsckobjects configuration to
+   validate the objects that were received from the other end, just like
+   "git receive-pack" (the receiving end of "git push") does.
+
+ * "git fetch" makes sure that the set of objects it received from the
+   other end actually completes the history before updating the refs.
+   "git receive-pack" (the receiving end of "git push") learned to do the
+   same.
+
+ * "git fetch" learned that fetching/cloning from a regular file on the
+   filesystem is not necessarily a request to unpack a bundle file; the
+   file could be ".git" with "gitdir: <path>" in it.
+
+ * "git for-each-ref" learned "%(contents:subject)", "%(contents:body)"
+   and "%(contents:signature)". The last one is useful for signed tags.
+
+ * "git grep" used to incorrectly pay attention to .gitignore files
+   scattered in the directory it was working in even when "--no-index"
+   option was used. It no longer does this. The "--exclude-standard"
+   option needs to be given to explicitly activate the ignore
+   mechanism.
+
+ * "git grep" learned "--untracked" option, where given patterns are
+    searched in untracked (but not ignored) files as well as tracked
+    files in the working tree, so that matches in new but not yet
+    added files do not get missed.
+
+ * The recursive merge backend no longer looks for meaningless
+   existing merges in submodules unless in the outermost merge.
+
+ * "git log" and friends learned "--children" option.
+
+ * "git ls-remote" learned to respond to "-h"(elp) requests.
+
+ * "mediawiki" remote helper can interact with (surprise!) MediaWiki
+   with "git fetch" & "git push".
+
+ * "git merge" learned the "--edit" option to allow users to edit the
+   merge commit log message.
+
+ * "git rebase -i" can be told to use special purpose editor suitable
+   only for its insn sheet via sequence.editor configuration variable.
+
+ * "git send-email" learned to respond to "-h"(elp) requests.
+
+ * "git send-email" allows the value given to sendemail.aliasfile to begin
+   with "~/" to refer to the $HOME directory.
+
+ * "git send-email" forces use of Authen::SASL::Perl to work around
+   issues between Authen::SASL::Cyrus and AUTH PLAIN/LOGIN.
+
+ * "git stash" learned "--include-untracked" option to stash away
+   untracked/ignored cruft from the working tree.
+
+ * "git submodule clone" does not leak an error message to the UI
+   level unnecessarily anymore.
+
+ * "git submodule update" learned to honor "none" as the value for
+   submodule.<name>.update to specify that the named submodule should
+   not be checked out by default.
+
+ * When populating a new submodule directory with "git submodule init",
+   the $GIT_DIR metainformation directory for submodules is created inside
+   $GIT_DIR/modules/<name>/ directory of the superproject and referenced
+   via the gitfile mechanism. This is to make it possible to switch
+   between commits in the superproject that has and does not have the
+   submodule in the tree without re-cloning.
+
+ * "gitweb" leaked unescaped control characters from syntax hiliter
+   outputs.
+
+ * "gitweb" can be told to give custom string at the end of the HTML
+   HEAD element.
+
+ * "gitweb" now has its own manual pages.
+
+
+Also contains other documentation updates and minor code cleanups.
+
+
+Fixes since v1.7.7
+------------------
+
+Unless otherwise noted, all fixes in the 1.7.7.X maintenance track are
+included in this release.
+
+ * HTTP transport did not use pushurl correctly, and also did not tell
+   what host it is trying to authenticate with when asking for
+   credentials.
+   (merge deba493 jk/http-auth later to maint).
+
+ * "git blame" was aborted if started from an uncommitted content and
+   the path had the textconv filter in effect.
+   (merge 8518088 ss/blame-textconv-fake-working-tree later to maint).
+
+ * Adding many refs to the local repository in one go (e.g. "git fetch"
+   that fetches many tags) and looking up a ref by name in a repository
+   with too many refs were unnecessarily slow.
+   (merge 17d68a54d jp/get-ref-dir-unsorted later to maint).
+
+ * Report from "git commit" on untracked files was confused under
+   core.ignorecase option.
+   (merge 395c7356 jk/name-hash-dirent later to maint).
+
+ * "git merge" did not understand ":/<pattern>" as a way to name a commit.
+
+ " "git push" on the receiving end used to call post-receive and post-update
+   hooks for attempted removal of non-existing refs.
+   (merge 160b81ed ph/push-to-delete-nothing later to maint).
+
+ * Help text for "git remote set-url" and "git remote set-branches"
+   were misspelled.
+   (merge c49904e fc/remote-seturl-usage-fix later to maint).
+   (merge 656cdf0 jc/remote-setbranches-usage-fix later to maint).
index e76195ac9721277e9ef270be8eb97dba2462bc37..d4a51da464f5cefea9a5feb4c048b0b0f970b169 100644 (file)
@@ -117,5 +117,4 @@ commit. And the default value is 40. If there are more than one
 take effect.
 
 -h::
---help::
        Show help message.
index 98bac550736f5fd54032371c7f8acbcc8730116c..5a841da6d4cd86c4340abf001f735a37035d198f 100644 (file)
@@ -45,9 +45,10 @@ lines.  Variables may belong directly to a section or to a given subsection.
 You can have `[section]` if you have `[section "subsection"]`, but you
 don't need to.
 
-There is also a case insensitive alternative `[section.subsection]` syntax.
-In this syntax, subsection names follow the same restrictions as for section
-names.
+There is also a deprecated `[section.subsection]` syntax. With this
+syntax, the subsection name is converted to lower-case and is also
+compared case sensitively. These subsection names follow the same
+restrictions as section names.
 
 All the other lines (and the remainder of the line after the section
 header) are recognized as setting variables, in the form
@@ -147,7 +148,7 @@ advice.*::
 
 core.fileMode::
        If false, the executable bit differences between the index and
-       the working copy are ignored; useful on broken filesystems like FAT.
+       the working tree are ignored; useful on broken filesystems like FAT.
        See linkgit:git-update-index[1].
 +
 The default is true, except linkgit:git-clone[1] or linkgit:git-init[1]
@@ -179,7 +180,7 @@ is created.
 
 core.trustctime::
        If false, the ctime differences between the index and the
-       working copy are ignored; useful when the inode change time
+       working tree are ignored; useful when the inode change time
        is regularly modified by something outside Git (file system
        crawlers and some backup systems).
        See linkgit:git-update-index[1]. True by default.
@@ -292,7 +293,7 @@ core.ignoreStat::
        If true, commands which modify both the working tree and the index
        will mark the updated paths with the "assume unchanged" bit in the
        index. These marked files are then assumed to stay unchanged in the
-       working copy, until you mark them otherwise manually - Git will not
+       working tree, until you mark them otherwise manually - Git will not
        detect the file changes by lstat() calls. This is useful on systems
        where those are very slow, such as Microsoft Windows.
        See linkgit:git-update-index[1].
@@ -473,6 +474,12 @@ core.editor::
        variable when it is set, and the environment variable
        `GIT_EDITOR` is not set.  See linkgit:git-var[1].
 
+sequence.editor::
+       Text editor used by `git rebase -i` for editing the rebase insn file.
+       The value is meant to be interpreted by the shell when it is used.
+       It can be overridden by the `GIT_SEQUENCE_EDITOR` environment variable.
+       When not configured the default commit message editor is used instead.
+
 core.pager::
        The command that git will use to paginate output.  Can
        be overridden with the `GIT_PAGER` environment
@@ -1071,6 +1078,23 @@ All gitcvs variables except for 'gitcvs.usecrlfattr' and
 is one of "ext" and "pserver") to make them apply only for the given
 access method.
 
+gitweb.category::
+gitweb.description::
+gitweb.owner::
+gitweb.url::
+       See linkgit:gitweb[1] for description.
+
+gitweb.avatar::
+gitweb.blame::
+gitweb.grep::
+gitweb.highlight::
+gitweb.patches::
+gitweb.pickaxe::
+gitweb.remote_heads::
+gitweb.showsizes::
+gitweb.snapshot::
+       See linkgit:gitweb.conf[5] for description.
+
 grep.lineNumber::
        If set to true, enable '-n' option by default.
 
@@ -1460,7 +1484,8 @@ notes.rewriteRef::
        You may also specify this configuration several times.
 +
 Does not have a default value; you must configure this variable to
-enable note rewriting.
+enable note rewriting.  Set it to `refs/notes/commits` to enable
+rewriting for the default commit notes.
 +
 This setting can be overridden with the `GIT_NOTES_REWRITE_REF`
 environment variable, which must be a colon separated list of refs or
index b620b3afeca14166840fb76b4cefe1e4038fb47e..9f7cba2be6a97cb85072d22b3317d37d1afe80bf 100644 (file)
@@ -45,6 +45,10 @@ ifndef::git-format-patch[]
        Synonym for `-p --raw`.
 endif::git-format-patch[]
 
+--minimal::
+       Spend extra time to make sure the smallest possible
+       diff is produced.
+
 --patience::
        Generate a diff using the "patience diff" algorithm.
 
@@ -404,7 +408,12 @@ endif::git-format-patch[]
        Show the context between diff hunks, up to the specified number
        of lines, thereby fusing hunks that are close to each other.
 
+-W::
+--function-context::
+       Show whole surrounding functions of changes.
+
 ifndef::git-format-patch[]
+ifndef::git-log[]
 --exit-code::
        Make the program exit with codes similar to diff(1).
        That is, it exits with 1 if there were differences and
@@ -412,6 +421,7 @@ ifndef::git-format-patch[]
 
 --quiet::
        Disable all output of the program. Implies `--exit-code`.
+endif::git-log[]
 endif::git-format-patch[]
 
 --ext-diff::
index 507b8d0ab2a2d524a607c5b91e6d17f6d2f36392..f46013c91fcbbe4eecffe09d9b43c132daea093f 100644 (file)
@@ -9,8 +9,8 @@ SYNOPSIS
 --------
 [verse]
 'git branch' [--color[=<when>] | --no-color] [-r | -a]
-       [-v [--abbrev=<length> | --no-abbrev]]
-       [(--merged | --no-merged | --contains) [<commit>]]
+       [--list] [-v [--abbrev=<length> | --no-abbrev]]
+       [(--merged | --no-merged | --contains) [<commit>]] [<pattern>...]
 'git branch' [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>]
 'git branch' (-m | -M) [<oldbranch>] <newbranch>
 'git branch' (-d | -D) [-r] <branchname>...
@@ -20,7 +20,11 @@ DESCRIPTION
 
 With no arguments, existing branches are listed and the current branch will
 be highlighted with an asterisk.  Option `-r` causes the remote-tracking
-branches to be listed, and option `-a` shows both.
+branches to be listed, and option `-a` shows both. This list mode is also
+activated by the `--list` option (see below).
+<pattern> restricts the output to matching branches, the pattern is a shell
+wildcard (i.e., matched using fnmatch(3))
+Multiple patterns may be given; if any of them matches, the tag is shown.
 
 With `--contains`, shows only the branches that contain the named commit
 (in other words, the branches whose tip commits are descendants of the
@@ -64,6 +68,7 @@ way to clean up all obsolete remote-tracking branches.
 OPTIONS
 -------
 -d::
+--delete::
        Delete a branch. The branch must be fully merged in its
        upstream branch, or in `HEAD` if no upstream was set with
        `--track` or `--set-upstream`.
@@ -72,6 +77,7 @@ OPTIONS
        Delete a branch irrespective of its merged status.
 
 -l::
+--create-reflog::
        Create the branch's reflog.  This activates recording of
        all changes made to the branch ref, enabling use of date
        based sha1 expressions such as "<branchname>@\{yesterday}".
@@ -84,6 +90,7 @@ OPTIONS
        already. Without `-f` 'git branch' refuses to change an existing branch.
 
 -m::
+--move::
        Move/rename a branch and the corresponding reflog.
 
 -M::
@@ -100,14 +107,21 @@ OPTIONS
        Same as `--color=never`.
 
 -r::
+--remotes::
        List or delete (if used with -d) the remote-tracking branches.
 
 -a::
+--all::
        List both remote-tracking branches and local branches.
 
+--list::
+       Activate the list mode. `git branch <pattern>` would try to create a branch,
+       use `git branch --list <pattern>` to list matching branches.
+
 -v::
 --verbose::
-       Show sha1 and commit subject line for each head, along with
+       When in list mode,
+       show sha1 and commit subject line for each head, along with
        relationship to upstream branch (if any). If given twice, print
        the name of the upstream branch, as well.
 
index 1f7312a1895ae66901e5472132406d01303da1f8..5abdbaa51cf58e216bbc63d28039a5abfba01669 100644 (file)
@@ -24,6 +24,9 @@ OPTIONS
        paths.  If this option is used, then 'unspecified' attributes
        will not be included in the output.
 
+--cached::
+       Consider `.gitattributes` in the index only, ignoring the working tree.
+
 --stdin::
        Read file names from stdin instead of from the command-line.
 
index c9fdf84a08847bd8cb8c9ce1e2f50241975e267c..103e7b128d3a0d2b1c06da89d4a577f9548ba4f4 100644 (file)
@@ -8,8 +8,9 @@ git-check-ref-format - Ensures that a reference name is well formed
 SYNOPSIS
 --------
 [verse]
-'git check-ref-format' <refname>
-'git check-ref-format' --print <refname>
+'git check-ref-format' [--normalize]
+       [--[no-]allow-onelevel] [--refspec-pattern]
+       <refname>
 'git check-ref-format' --branch <branchname-shorthand>
 
 DESCRIPTION
@@ -28,22 +29,28 @@ git imposes the following rules on how references are named:
 
 . They can include slash `/` for hierarchical (directory)
   grouping, but no slash-separated component can begin with a
-  dot `.`.
+  dot `.` or end with the sequence `.lock`.
 
 . They must contain at least one `/`. This enforces the presence of a
   category like `heads/`, `tags/` etc. but the actual names are not
-  restricted.
+  restricted.  If the `--allow-onelevel` option is used, this rule
+  is waived.
 
 . They cannot have two consecutive dots `..` anywhere.
 
 . They cannot have ASCII control characters (i.e. bytes whose
   values are lower than \040, or \177 `DEL`), space, tilde `~`,
-  caret `{caret}`, colon `:`, question-mark `?`, asterisk `*`,
-  or open bracket `[` anywhere.
+  caret `{caret}`, or colon `:` anywhere.
 
-. They cannot end with a slash `/` nor a dot `.`.
+. They cannot have question-mark `?`, asterisk `{asterisk}`, or open
+  bracket `[` anywhere.  See the `--refspec-pattern` option below for
+  an exception to this rule.
 
-. They cannot end with the sequence `.lock`.
+. They cannot begin or end with a slash `/` or contain multiple
+  consecutive slashes (see the `--normalize` option below for an
+  exception to this rule)
+
+. They cannot end with a dot `.`.
 
 . They cannot contain a sequence `@{`.
 
@@ -68,16 +75,36 @@ reference name expressions (see linkgit:gitrevisions[7]):
 
 . at-open-brace `@{` is used as a notation to access a reflog entry.
 
-With the `--print` option, if 'refname' is acceptable, it prints the
-canonicalized name of a hypothetical reference with that name.  That is,
-it prints 'refname' with any extra `/` characters removed.
-
 With the `--branch` option, it expands the ``previous branch syntax''
 `@{-n}`.  For example, `@{-1}` is a way to refer the last branch you
 were on.  This option should be used by porcelains to accept this
 syntax anywhere a branch name is expected, so they can act as if you
 typed the branch name.
 
+OPTIONS
+-------
+--allow-onelevel::
+--no-allow-onelevel::
+       Controls whether one-level refnames are accepted (i.e.,
+       refnames that do not contain multiple `/`-separated
+       components).  The default is `--no-allow-onelevel`.
+
+--refspec-pattern::
+       Interpret <refname> as a reference name pattern for a refspec
+       (as used with remote repositories).  If this option is
+       enabled, <refname> is allowed to contain a single `{asterisk}`
+       in place of a one full pathname component (e.g.,
+       `foo/{asterisk}/bar` but not `foo/bar{asterisk}`).
+
+--normalize::
+       Normalize 'refname' by removing any leading slash (`/`)
+       characters and collapsing runs of adjacent slashes between
+       name components into a single slash.  Iff the normalized
+       refname is valid then print it to standard output and exit
+       with a status of 0.  (`--print` is a deprecated way to spell
+       `--normalize`.)
+
+
 EXAMPLES
 --------
 
@@ -90,7 +117,7 @@ $ git check-ref-format --branch @{-1}
 * Determine the reference name to use for a new branch:
 +
 ------------
-$ ref=$(git check-ref-format --print "refs/heads/$newbranch") ||
+$ ref=$(git check-ref-format --normalize "refs/heads/$newbranch") ||
 die "we do not like '$newbranch' as a branch name."
 ------------
 
index 2660a842fc2ac76660963bc65c95ca47cb0e97cb..fed5097e00b4a031c2992ac3d421f6df975e6152 100644 (file)
@@ -9,8 +9,9 @@ SYNOPSIS
 --------
 [verse]
 'git cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] [--ff] <commit>...
-'git cherry-pick' --reset
 'git cherry-pick' --continue
+'git cherry-pick' --quit
+'git cherry-pick' --abort
 
 DESCRIPTION
 -----------
index 0fdb82ee8697a53beab6defd4db9eca8840b1dd0..02133d5fc9a14df7f4fe24d97bdf9980e77f807a 100644 (file)
@@ -68,7 +68,9 @@ if set:
 
 In case (some of) these environment variables are not set, the information
 is taken from the configuration items user.name and user.email, or, if not
-present, system user name and fully qualified hostname.
+present, system user name and the hostname used for outgoing mail (taken
+from `/etc/mailname` and falling back to the fully qualified hostname when
+that file does not exist).
 
 A commit comment is read from stdin. If a changelog
 entry is not provided via "<" redirection, 'git commit-tree' will just wait
@@ -90,6 +92,10 @@ Discussion
 
 include::i18n.txt[]
 
+FILES
+-----
+/etc/mailname
+
 SEE ALSO
 --------
 linkgit:git-write-tree[1]
index 69a1e4af9ec007a6cc59cce07298a6a825369ef1..31b28fc29fc6e01e8505f2179b08082cf734377c 100644 (file)
@@ -161,6 +161,16 @@ the facility of inet daemon to achieve the same before spawning
        repository configuration.  By default, all the services
        are overridable.
 
+--informative-errors::
+--no-informative-errors::
+       When informative errors are turned on, git-daemon will report
+       more verbose errors to the client, differentiating conditions
+       like "no such repository" from "repository not exported". This
+       is more convenient for clients, but may leak information about
+       the existence of unexported repositories.  When informative
+       errors are not enabled, all errors report "access denied" to the
+       client. The default is --no-informative-errors.
+
 <directory>::
        A directory to add to the whitelist of allowed directories. Unless
        --strict-paths is specified this will also include subdirectories
index a03515f1eccddded2efbd46ca522f3bac8896d90..19d473c070c4f82dc4b7f04f784bcae835dca6a2 100644 (file)
@@ -31,7 +31,7 @@ OPTIONS
 -t <tool>::
 --tool=<tool>::
        Use the diff tool specified by <tool>.
-       Valid merge tools are:
+       Valid diff tools are:
        araxis, bc3, diffuse, emerge, ecmerge, gvimdiff, kdiff3,
        kompare, meld, opendiff, p4merge, tkdiff, vimdiff and xxdiff.
 +
index 152e695c8178fee9daa478cdf67eae58a2f1af00..c872b883ba25144457eccb9967bc68003a3332ea 100644 (file)
@@ -101,9 +101,10 @@ Fields that have name-email-date tuple as its value (`author`,
 `committer`, and `tagger`) can be suffixed with `name`, `email`,
 and `date` to extract the named component.
 
-The first line of the message in a commit and tag object is
-`subject`, the remaining lines are `body`.  The whole message
-is `contents`.
+The complete message in a commit and tag object is `contents`.
+Its first line is `contents:subject`, the remaining lines
+are `contents:body` and the optional GPG signature
+is `contents:signature`.
 
 For sorting purposes, fields with numeric values sort in numeric
 order (`objectsize`, `authordate`, `committerdate`, `taggerdate`).
index a2a508dc2829b88e143ceb5c8e5edf0880263b2b..0a17b4258e2a69a1d2a5e931dd14ac1869876f33 100644 (file)
@@ -10,7 +10,8 @@ SYNOPSIS
 --------
 [verse]
 'git fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs]
-        [--[no-]full] [--strict] [--verbose] [--lost-found] [<object>*]
+        [--[no-]full] [--strict] [--verbose] [--lost-found]
+        [--[no-]progress] [<object>*]
 
 DESCRIPTION
 -----------
@@ -72,6 +73,14 @@ index file, all SHA1 references in .git/refs/*, and all reflogs (unless
        a blob, the contents are written into the file, rather than
        its object name.
 
+--progress::
+--no-progress::
+       Progress status is reported on the standard error stream by
+       default when it is attached to a terminal, unless
+       --no-progress or --verbose is specified. --progress forces
+       progress status even if the standard error stream is not
+       directed to a terminal.
+
 It tests SHA1 and general object sanity, and it does full tracking of
 the resulting reachability and everything else. It prints out any
 corruption it finds (missing or bad objects), and if you use the
index e44a4988b7a7ed7100da6e2ba098d7fda17a2f83..15d6711d46754e9659561b4372b46ec3c6654d6b 100644 (file)
@@ -23,7 +23,7 @@ SYNOPSIS
           [-A <post-context>] [-B <pre-context>] [-C <context>]
           [-f <file>] [-e] <pattern>
           [--and|--or|--not|(|)|-e <pattern>...]
-          [--cached | --no-index | <tree>...]
+          [ [--exclude-standard] [--cached | --no-index | --untracked] | <tree>...]
           [--] [<pathspec>...]
 
 DESCRIPTION
@@ -49,7 +49,20 @@ OPTIONS
        blobs registered in the index file.
 
 --no-index::
-       Search files in the current directory, not just those tracked by git.
+       Search files in the current directory that is not managed by git.
+
+--untracked::
+       In addition to searching in the tracked files in the working
+       tree, search also in untracked files.
+
+--no-exclude-standard::
+       Also search in ignored files by not honoring the `.gitignore`
+       mechanism. Only useful with `--untracked`.
+
+--exclude-standard::
+       Do not pay attention to ignored files specified via the `.gitignore`
+       mechanism.  Only useful when searching files in the current directory
+       with `--no-index`.
 
 -a::
 --text::
index ea95c90460b976ee187833c248366af579065f83..f3eef510f2ba906f64e802a4ea9e1d671530aca4 100644 (file)
@@ -84,6 +84,10 @@ If the configuration variable 'instaweb.browser' is not set,
 'web.browser' will be used instead if it is defined. See
 linkgit:git-web{litdd}browse[1] for more information about this.
 
+SEE ALSO
+--------
+linkgit:gitweb[1]
+
 GIT
 ---
 Part of the linkgit:git[1] suite
index 347091010995af95e43ced744b159b277bdb6cdc..2a49de7cfe5188b531bc02f1115e253bfa10e0e0 100644 (file)
@@ -17,9 +17,10 @@ Use `git mergetool` to run one of several merge utilities to resolve
 merge conflicts.  It is typically run after 'git merge'.
 
 If one or more <file> parameters are given, the merge tool program will
-be run to resolve differences on each file.  If no <file> names are
-specified, 'git mergetool' will run the merge tool program on every file
-with merge conflicts.
+be run to resolve differences on each file (skipping those without
+conflicts).  Specifying a directory will include all unresolved files in
+that path.  If no <file> names are specified, 'git mergetool' will run
+the merge tool program on every file with merge conflicts.
 
 OPTIONS
 -------
index c45d53c6e150479bd01bf3d643354348d8ce9273..5375549820bd6b9fecf3540b1e60ae50e74da0e6 100644 (file)
@@ -47,7 +47,7 @@ OPTIONS
 
 -i::
        Usually a merge requires the index file as well as the
-       files in the working tree are up to date with the
+       files in the working tree to be up to date with the
        current head commit, in order not to lose local
        changes.  This flag disables the check with the working
        tree and is meant to be used when creating a merge of
@@ -71,21 +71,21 @@ OPTIONS
 --aggressive::
        Usually a three-way merge by 'git read-tree' resolves
        the merge for really trivial cases and leaves other
-       cases unresolved in the index, so that Porcelains can
+       cases unresolved in the index, so that porcelains can
        implement different merge policies.  This flag makes the
-       command to resolve a few more cases internally:
+       command resolve a few more cases internally:
 +
 * when one side removes a path and the other side leaves the path
   unmodified.  The resolution is to remove that path.
 * when both sides remove a path.  The resolution is to remove that path.
-* when both sides adds a path identically.  The resolution
+* when both sides add a path identically.  The resolution
   is to add that path.
 
 --prefix=<prefix>/::
        Keep the current index contents, and read the contents
-       of named tree-ish under directory at `<prefix>`.  The
+       of the named tree-ish under the directory at `<prefix>`. The
        original index file cannot have anything at the path
-       `<prefix>` itself, and have nothing in `<prefix>/`
+       `<prefix>` itself, nor anything in the `<prefix>/`
        directory.  Note that the `<prefix>/` value must end
        with a slash.
 
@@ -379,45 +379,45 @@ have finished your work-in-progress), attempt the merge again.
 Sparse checkout
 ---------------
 
-"Sparse checkout" allows to sparsely populate working directory.
-It uses skip-worktree bit (see linkgit:git-update-index[1]) to tell
-Git whether a file on working directory is worth looking at.
+"Sparse checkout" allows populating the working directory sparsely.
+It uses the skip-worktree bit (see linkgit:git-update-index[1]) to tell
+Git whether a file in the working directory is worth looking at.
 
-"git read-tree" and other merge-based commands ("git merge", "git
-checkout"...) can help maintaining skip-worktree bitmap and working
+'git read-tree' and other merge-based commands ('git merge', 'git
+checkout'...) can help maintaining the skip-worktree bitmap and working
 directory update. `$GIT_DIR/info/sparse-checkout` is used to
-define the skip-worktree reference bitmap. When "git read-tree" needs
-to update working directory, it will reset skip-worktree bit in index
+define the skip-worktree reference bitmap. When 'git read-tree' needs
+to update the working directory, it resets the skip-worktree bit in the index
 based on this file, which uses the same syntax as .gitignore files.
-If an entry matches a pattern in this file, skip-worktree will be
-set on that entry. Otherwise, skip-worktree will be unset.
+If an entry matches a pattern in this file, skip-worktree will not be
+set on that entry. Otherwise, skip-worktree will be set.
 
 Then it compares the new skip-worktree value with the previous one. If
-skip-worktree turns from unset to set, it will add the corresponding
-file back. If it turns from set to unset, that file will be removed.
+skip-worktree turns from set to unset, it will add the corresponding
+file back. If it turns from unset to set, that file will be removed.
 
 While `$GIT_DIR/info/sparse-checkout` is usually used to specify what
-files are in. You can also specify what files are _not_ in, using
-negate patterns. For example, to remove file "unwanted":
+files are in, you can also specify what files are _not_ in, using
+negate patterns. For example, to remove the file `unwanted`:
 
 ----------------
-*
+/*
 !unwanted
 ----------------
 
-Another tricky thing is fully repopulating working directory when you
+Another tricky thing is fully repopulating the working directory when you
 no longer want sparse checkout. You cannot just disable "sparse
-checkout" because skip-worktree are still in the index and you working
-directory is still sparsely populated. You should re-populate working
+checkout" because skip-worktree bits are still in the index and your working
+directory is still sparsely populated. You should re-populate the working
 directory with the `$GIT_DIR/info/sparse-checkout` file content as
 follows:
 
 ----------------
-*
+/*
 ----------------
 
-Then you can disable sparse checkout. Sparse checkout support in "git
-read-tree" and similar commands is disabled by default. You need to
+Then you can disable sparse checkout. Sparse checkout support in 'git
+read-tree' and similar commands is disabled by default. You need to
 turn `core.sparseCheckout` on in order to have sparse checkout
 support.
 
index b2832fc7eb809af9865d83cdb20829354165f62e..b674866e6d166ccce096b58f7cecdbdb183c0194 100644 (file)
@@ -9,8 +9,8 @@ SYNOPSIS
 --------
 [verse]
 'git reset' [-q] [<commit>] [--] <paths>...
-'git reset' [--patch|-p] [<commit>] [--] [<paths>...]
-'git reset' [--soft | --mixed | --hard | --merge | --keep] [-q] [<commit>]
+'git reset' (--patch | -p) [<commit>] [--] [<paths>...]
+'git reset' (--soft | --mixed | --hard | --merge | --keep) [-q] [<commit>]
 
 DESCRIPTION
 -----------
@@ -34,7 +34,7 @@ Alternatively, using linkgit:git-checkout[1] and specifying a commit, you
 can copy the contents of a path out of a commit to the index and to the
 working tree in one go.
 
-'git reset' --patch|-p [<commit>] [--] [<paths>...]::
+'git reset' (--patch | -p) [<commit>] [--] [<paths>...]::
        Interactively select hunks in the difference between the index
        and <commit> (defaults to HEAD).  The chosen hunks are applied
        in reverse to the index.
@@ -43,7 +43,7 @@ This means that `git reset -p` is the opposite of `git add -p`, i.e.
 you can use it to selectively reset hunks. See the ``Interactive Mode''
 section of linkgit:git-add[1] to learn how to operate the `\--patch` mode.
 
-'git reset' [--<mode>] [<commit>]::
+'git reset' --<mode> [<commit>]::
        This form resets the current branch head to <commit> and
        possibly updates the index (resetting it to the tree of <commit>) and
        the working tree depending on <mode>, which
index 42c9676eaa40f2a3d43514c09509e319258ca8b4..8023dc086d044d0ac2cab685ac1c8b050adda95b 100644 (file)
@@ -180,6 +180,10 @@ print a message to stderr and exit with nonzero status.
 <args>...::
        Flags and parameters to be parsed.
 
+--resolve-git-dir <path>::
+       Check if <path> is a valid git-dir or a git-file pointing to a valid
+       git-dir. If <path> is a valid git-dir the resolved path to git-dir will
+       be printed.
 
 include::revisions.txt[]
 
index f3519413e7e8704deee0197df6876eaed97e28b0..b699a3458eff439b05049dab94610a57ac62fc0a 100644 (file)
@@ -9,8 +9,9 @@ SYNOPSIS
 --------
 [verse]
 'git revert' [--edit | --no-edit] [-n] [-m parent-number] [-s] <commit>...
-'git revert' --reset
 'git revert' --continue
+'git revert' --quit
+'git revert' --abort
 
 DESCRIPTION
 -----------
index 67cf5f0f8b8a280891e47ecb7705d260bfc4232b..6ec3fef0799222e67cb176d00aae2f583004032d 100644 (file)
@@ -120,6 +120,8 @@ too (and can also report changes to a submodule's work tree).
 init::
        Initialize the submodules, i.e. register each submodule name
        and url found in .gitmodules into .git/config.
+       It will also copy the value of `submodule.$name.update` into
+       .git/config.
        The key used in .git/config is `submodule.$name.url`.
        This command does not alter existing information in .git/config.
        You can then customize the submodule clone URLs in .git/config
@@ -133,7 +135,7 @@ update::
        checkout the commit specified in the index of the containing repository.
        This will make the submodules HEAD be detached unless `--rebase` or
        `--merge` is specified or the key `submodule.$name.update` is set to
-       `rebase` or `merge`.
+       `rebase`, `merge` or `none`.
 +
 If the submodule is not yet initialized, and you just want to use the
 setting as stored in .gitmodules, you can automatically initialize the
@@ -141,6 +143,10 @@ submodule with the `--init` option.
 +
 If `--recursive` is specified, this command will recurse into the
 registered submodules, and update any nested submodules within.
++
+If the configuration key `submodule.$name.update` is set to `none` the
+submodule with name `$name` will not be updated by default. This can be
+overriden by adding `--checkout` to the command.
 
 summary::
        Show commit summary between the given commit (defaults to HEAD) and
index 08cad6d2b676642b37b5a871cd52617c4308a748..34ee785064128bce0feb2cfc86d3040b42a8f428 100644 (file)
@@ -234,6 +234,14 @@ svn:mergeinfo property in the SVN repository when possible. Currently, this can
 only be done when dcommitting non-fast-forward merges where all parents but the
 first have already been pushed into SVN.
 
+--interactive;;
+       Ask the user to confirm that a patch set should actually be sent to SVN.
+       For each patch, one may answer "yes" (accept this patch), "no" (discard this
+       patch), "all" (accept all patches), or "quit".
+       +
+       'git svn dcommit' returns immediately if answer if "no" or "quit", without
+       commiting anything to SVN.
+
 'branch'::
        Create a branch in the SVN repository.
 
@@ -318,7 +326,7 @@ Any other arguments are passed directly to 'git log'
        Show what revision and author last modified each line of a file. The
        output of this mode is format-compatible with the output of
        `svn blame' by default. Like the SVN blame command,
-       local uncommitted changes in the working copy are ignored;
+       local uncommitted changes in the working tree are ignored;
        the version of the file in the HEAD revision is annotated. Unknown
        arguments are passed directly to 'git blame'.
 +
index fb1c0ac694bdac20e2fef74710df35c1c4b1e774..c83cb13de67943813edc99725b87cfe94beba87e 100644 (file)
@@ -43,12 +43,15 @@ GnuPG key for signing.
 OPTIONS
 -------
 -a::
+--annotate::
        Make an unsigned, annotated tag object
 
 -s::
+--sign::
        Make a GPG-signed tag, using the default e-mail address's key
 
 -u <key-id>::
+--local-user=<key-id>::
        Make a GPG-signed tag, using the given key
 
 -f::
@@ -56,9 +59,11 @@ OPTIONS
        Replace an existing tag with the given name (instead of failing)
 
 -d::
+--delete::
        Delete existing tags with the given names.
 
 -v::
+--verify::
        Verify the gpg signature of the given tag names.
 
 -n<num>::
@@ -69,6 +74,7 @@ OPTIONS
        If the tag is not annotated, the commit message is displayed instead.
 
 -l <pattern>::
+--list <pattern>::
        List tags with names that match the given pattern (or all if no
        pattern is given).  Running "git tag" without arguments also
        lists all tags. The pattern is a shell wildcard (i.e., matched
@@ -79,6 +85,7 @@ OPTIONS
        Only list tags which contain the specified commit.
 
 -m <msg>::
+--message=<msg>::
        Use the given tag message (instead of prompting).
        If multiple `-m` options are given, their values are
        concatenated as separate paragraphs.
@@ -86,6 +93,7 @@ OPTIONS
        is given.
 
 -F <file>::
+--file=<file>::
        Take the tag message from the given file.  Use '-' to
        read the message from the standard input.
        Implies `-a` if none of `-a`, `-s`, or `-u <key-id>`
index d3931294d174e5c06adb81e428e31e00fbfa3b5e..a3081f4e237747dc858fd105302cbb9a489de41b 100644 (file)
@@ -264,7 +264,9 @@ tree files, you have to explicitly tell git about it by dropping
 "assume unchanged" bit, either before or after you modify them.
 
 In order to set "assume unchanged" bit, use `--assume-unchanged`
-option.  To unset, use `--no-assume-unchanged`.
+option.  To unset, use `--no-assume-unchanged`. To see which files
+have the "assume unchanged" bit set, use `git ls-files -v`
+(see linkgit:git-ls-files[1]).
 
 The command looks at `core.ignorestat` configuration variable.  When
 this is true, paths updated with `git update-index paths...` and
@@ -363,7 +365,8 @@ ctime for marking files processed) (see linkgit:git-config[1]).
 SEE ALSO
 --------
 linkgit:git-config[1],
-linkgit:git-add[1]
+linkgit:git-add[1],
+linkgit:git-ls-files[1]
 
 GIT
 ---
index cbc51d5a949e60e023982efc61e2d80dd73abd37..e869032fc0703aed18d3cb1417eda4341c29e093 100644 (file)
@@ -44,9 +44,15 @@ unreleased) version of git, that is available from 'master'
 branch of the `git.git` repository.
 Documentation for older releases are available here:
 
-* link:v1.7.7/git.html[documentation for release 1.7.7]
+* link:v1.7.8/git.html[documentation for release 1.7.8]
 
 * release notes for
+  link:RelNotes/1.7.8.txt[1.7.8].
+
+* link:v1.7.7.1/git.html[documentation for release 1.7.7.1]
+
+* release notes for
+  link:RelNotes/1.7.7.1.txt[1.7.7.1],
   link:RelNotes/1.7.7.txt[1.7.7].
 
 * link:v1.7.6.4/git.html[documentation for release 1.7.6.4]
diff --git a/Documentation/gitweb.conf.txt b/Documentation/gitweb.conf.txt
new file mode 100644 (file)
index 0000000..7aba497
--- /dev/null
@@ -0,0 +1,889 @@
+gitweb.conf(5)
+==============
+
+NAME
+----
+gitweb.conf - Gitweb (git web interface) configuration file
+
+SYNOPSIS
+--------
+/etc/gitweb.conf, /etc/gitweb-common.conf, $GITWEBDIR/gitweb_config.perl
+
+DESCRIPTION
+-----------
+
+The gitweb CGI script for viewing Git repositories over the web uses a
+perl script fragment as its configuration file.  You can set variables
+using "`our $variable = value`"; text from a "#" character until the
+end of a line is ignored.  See *perlsyn*(1) for details.
+
+An example:
+
+    # gitweb configuration file for http://git.example.org
+    #
+    our $projectroot = "/srv/git"; # FHS recommendation
+    our $site_name = 'Example.org >> Repos';
+
+
+The configuration file is used to override the default settings that
+were built into gitweb at the time the 'gitweb.cgi' script was generated.
+
+While one could just alter the configuration settings in the gitweb
+CGI itself, those changes would be lost upon upgrade.  Configuration
+settings might also be placed into a file in the same directory as the
+CGI script with the default name 'gitweb_config.perl' -- allowing
+one to have multiple gitweb instances with different configurations by
+the use of symlinks.
+
+Note that some configuration can be controlled on per-repository rather than
+gitweb-wide basis: see "Per-repository gitweb configuration" subsection on
+linkgit:gitweb[1] manpage.
+
+
+DISCUSSION
+----------
+Gitweb reads configuration data from the following sources in the
+following order:
+
+ * built-in values (some set during build stage),
+
+ * common system-wide configuration file (defaults to
+   '/etc/gitweb-common.conf'),
+
+ * either per-instance configuration file (defaults to 'gitweb_config.perl'
+   in the same directory as the installed gitweb), or if it does not exists
+   then fallback system-wide configuration file (defaults to '/etc/gitweb.conf').
+
+Values obtained in later configuration files override values obtained earlier
+in the above sequence.
+
+Locations of the common system-wide configuration file, the fallback
+system-wide configuration file and the per-instance configuration file
+are defined at compile time using build-time Makefile configuration
+variables, respectively `GITWEB_CONFIG_COMMON`, `GITWEB_CONFIG_SYSTEM`
+and `GITWEB_CONFIG`.
+
+You can also override locations of gitweb configuration files during
+runtime by setting the following environment variables:
+`GITWEB_CONFIG_COMMON`, `GITWEB_CONFIG_SYSTEM` and `GITWEB_CONFIG`
+to a non-empty value.
+
+
+The syntax of the configuration files is that of Perl, since these files are
+handled by sourcing them as fragments of Perl code (the language that
+gitweb itself is written in). Variables are typically set using the
+`our` qualifier (as in "`our $variable = <value>;`") to avoid syntax
+errors if a new version of gitweb no longer uses a variable and therefore
+stops declaring it.
+
+You can include other configuration file using read_config_file()
+subroutine.  For example, one might want to put gitweb configuration
+related to access control for viewing repositories via Gitolite (one
+of git repository management tools) in a separate file, e.g. in
+'/etc/gitweb-gitolite.conf'.  To include it, put
+
+--------------------------------------------------
+read_config_file("/etc/gitweb-gitolite.conf");
+--------------------------------------------------
+
+somewhere in gitweb configuration file used, e.g. in per-installation
+gitweb configuration file.  Note that read_config_file() checks itself
+that the file it reads exists, and does nothing if it is not found.
+It also handles errors in included file.
+
+
+The default configuration with no configuration file at all may work
+perfectly well for some installations.  Still, a configuration file is
+useful for customizing or tweaking the behavior of gitweb in many ways, and
+some optional features will not be present unless explicitly enabled using
+the configurable `%features` variable (see also "Configuring gitweb
+features" section below).
+
+
+CONFIGURATION VARIABLES
+-----------------------
+Some configuration variables have their default values (embedded in the CGI
+script) set during building gitweb -- if that is the case, this fact is put
+in their description.  See gitweb's 'INSTALL' file for instructions on building
+and installing gitweb.
+
+
+Location of repositories
+~~~~~~~~~~~~~~~~~~~~~~~~
+The configuration variables described below control how gitweb finds
+git repositories, and how repositories are displayed and accessed.
+
+See also "Repositories" and later subsections in linkgit:gitweb[1] manpage.
+
+$projectroot::
+       Absolute filesystem path which will be prepended to project path;
+       the path to repository is `$projectroot/$project`.  Set to
+       `$GITWEB_PROJECTROOT` during installation.  This variable has to be
+       set correctly for gitweb to find repositories.
++
+For example, if `$projectroot` is set to "/srv/git" by putting the following
+in gitweb config file:
++
+----------------------------------------------------------------------------
+our $projectroot = "/srv/git";
+----------------------------------------------------------------------------
++
+then
++
+------------------------------------------------
+http://git.example.com/gitweb.cgi?p=foo/bar.git
+------------------------------------------------
++
+and its path_info based equivalent
++
+------------------------------------------------
+http://git.example.com/gitweb.cgi/foo/bar.git
+------------------------------------------------
++
+will map to the path '/srv/git/foo/bar.git' on the filesystem.
+
+$projects_list::
+       Name of a plain text file listing projects, or a name of directory
+       to be scanned for projects.
++
+Project list files should list one project per line, with each line
+having the following format
++
+-----------------------------------------------------------------------------
+<URI-encoded filesystem path to repository> SP <URI-encoded repository owner>
+-----------------------------------------------------------------------------
++
+The default value of this variable is determined by the `GITWEB_LIST`
+makefile variable at installation time.  If this variable is empty, gitweb
+will fall back to scanning the `$projectroot` directory for repositories.
+
+$project_maxdepth::
+       If `$projects_list` variable is unset, gitweb will recursively
+       scan filesystem for git repositories.  The `$project_maxdepth`
+       is used to limit traversing depth, relative to `$projectroot`
+       (starting point); it means that directories which are further
+       from `$projectroot` than `$project_maxdepth` will be skipped.
++
+It is purely performance optimization, originally intended for MacOS X,
+where recursive directory traversal is slow.  Gitweb follows symbolic
+links, but it detects cycles, ignoring any duplicate files and directories.
++
+The default value of this variable is determined by the build-time
+configuration variable `GITWEB_PROJECT_MAXDEPTH`, which defaults to
+2007.
+
+$export_ok::
+       Show repository only if this file exists (in repository).  Only
+       effective if this variable evaluates to true.  Can be set when
+       building gitweb by setting `GITWEB_EXPORT_OK`.  This path is
+       relative to `GIT_DIR`.  git-daemon[1] uses 'git-daemon-export-ok',
+       unless started with `--export-all`.  By default this variable is
+       not set, which means that this feature is turned off.
+
+$export_auth_hook::
+       Function used to determine which repositories should be shown.
+       This subroutine should take one parameter, the full path to
+       a project, and if it returns true, that project will be included
+       in the projects list and can be accessed through gitweb as long
+       as it fulfills the other requirements described by $export_ok,
+       $projects_list, and $projects_maxdepth.  Example:
++
+----------------------------------------------------------------------------
+our $export_auth_hook = sub { return -e "$_[0]/git-daemon-export-ok"; };
+----------------------------------------------------------------------------
++
+though the above might be done by using `$export_ok` instead
++
+----------------------------------------------------------------------------
+our $export_ok = "git-daemon-export-ok";
+----------------------------------------------------------------------------
++
+If not set (default), it means that this feature is disabled.
++
+See also more involved example in "Controlling access to git repositories"
+subsection on linkgit:gitweb[1] manpage.
+
+$strict_export::
+       Only allow viewing of repositories also shown on the overview page.
+       This for example makes `$gitweb_export_ok` file decide if repository is
+       available and not only if it is shown.  If `$gitweb_list` points to
+       file with list of project, only those repositories listed would be
+       available for gitweb.  Can be set during building gitweb via
+       `GITWEB_STRICT_EXPORT`.  By default this variable is not set, which
+       means that you can directly access those repositories that are hidden
+       from projects list page (e.g. the are not listed in the $projects_list
+       file).
+
+
+Finding files
+~~~~~~~~~~~~~
+The following configuration variables tell gitweb where to find files.
+The values of these variables are paths on the filesystem.
+
+$GIT::
+       Core git executable to use.  By default set to `$GIT_BINDIR/git`, which
+       in turn is by default set to `$(bindir)/git`.  If you use git installed
+       from a binary package, you should usually set this to "/usr/bin/git".
+       This can just be "git" if your web server has a sensible PATH; from
+       security point of view it is better to use absolute path to git binary.
+       If you have multiple git versions installed it can be used to choose
+       which one to use.  Must be (correctly) set for gitweb to be able to
+       work.
+
+$mimetypes_file::
+       File to use for (filename extension based) guessing of MIME types before
+       trying '/etc/mime.types'.  *NOTE* that this path, if relative, is taken
+       as relative to the current git repository, not to CGI script.  If unset,
+       only '/etc/mime.types' is used (if present on filesystem).  If no mimetypes
+       file is found, mimetype guessing based on extension of file is disabled.
+       Unset by default.
+
+$highlight_bin::
+       Path to the highlight executable to use (it must be the one from
+       http://www.andre-simon.de[] due to assumptions about parameters and output).
+       By default set to 'highlight'; set it to full path to highlight
+       executable if it is not installed on your web server's PATH.
+       Note that 'highlight' feature must be set for gitweb to actually
+       use syntax hightlighting.
++
+*NOTE*: if you want to add support for new file type (supported by
+"highlight" but not used by gitweb), you need to modify `%highlight_ext`
+or `%highlight_basename`, depending on whether you detect type of file
+based on extension (for example "sh") or on its basename (for example
+"Makefile").  The keys of these hashes are extension and basename,
+respectively, and value for given key is name of syntax to be passed via
+`--syntax <syntax>` to highlighter.
++
+For example if repositories you are hosting use "phtml" extension for
+PHP files, and you want to have correct syntax-highlighting for those
+files, you can add the following to gitweb configuration:
++
+---------------------------------------------------------
+our %highlight_ext;
+$highlight_ext{'phtml'} = 'php';
+---------------------------------------------------------
+
+
+Links and their targets
+~~~~~~~~~~~~~~~~~~~~~~~
+The configuration variables described below configure some of gitweb links:
+their target and their look (text or image), and where to find page
+prerequisites (stylesheet, favicon, images, scripts).  Usually they are left
+at their default values, with the possible exception of `@stylesheets`
+variable.
+
+@stylesheets::
+       List of URIs of stylesheets (relative to the base URI of a page). You
+       might specify more than one stylesheet, for example to use "gitweb.css"
+       as base with site specific modifications in a separate stylesheet
+       to make it easier to upgrade gitweb.  For example, you can add
+       a `site` stylesheet by putting
++
+----------------------------------------------------------------------------
+push @stylesheets, "gitweb-site.css";
+----------------------------------------------------------------------------
++
+in the gitweb config file.  Those values that are relative paths are
+relative to base URI of gitweb.
++
+This list should contain the URI of gitweb's standard stylesheet.  The default
+URI of gitweb stylesheet can be set at build time using the `GITWEB_CSS`
+makefile variable.  Its default value is 'static/gitweb.css'
+(or 'static/gitweb.min.css' if the `CSSMIN` variable is defined,
+i.e. if CSS minifier is used during build).
++
+*Note*: there is also a legacy `$stylesheet` configuration variable, which was
+used by older gitweb.  If `$stylesheet` variable is defined, only CSS stylesheet
+given by this variable is used by gitweb.
+
+$logo::
+       Points to the location where you put 'git-logo.png' on your web
+       server, or to be more the generic URI of logo, 72x27 size).  This image
+       is displayed in the top right corner of each gitweb page and used as
+       a logo for the Atom feed.  Relative to the base URI of gitweb (as a path).
+       Can be adjusted when building gitweb using `GITWEB_LOGO` variable
+       By default set to 'static/git-logo.png'.
+
+$favicon::
+       Points to the location where you put 'git-favicon.png' on your web
+       server, or to be more the generic URI of favicon, which will be served
+       as "image/png" type.  Web browsers that support favicons (website icons)
+       may display them in the browser's URL bar and next to the site name in
+       bookmarks.  Relative to the base URI of gitweb.  Can be adjusted at
+       build time using `GITWEB_FAVICON` variable.
+       By default set to 'static/git-favicon.png'.
+
+$javascript::
+       Points to the location where you put 'gitweb.js' on your web server,
+       or to be more generic the URI of JavaScript code used by gitweb.
+       Relative to the base URI of gitweb.  Can be set at build time using
+       the `GITWEB_JS` build-time configuration variable.
++
+The default value is either 'static/gitweb.js', or 'static/gitweb.min.js' if
+the `JSMIN` build variable was defined, i.e. if JavaScript minifier was used
+at build time.  *Note* that this single file is generated from multiple
+individual JavaScript "modules".
+
+$home_link::
+       Target of the home link on the top of all pages (the first part of view
+       "breadcrumbs").  By default it is set to the absolute URI of a current page
+       (to the value of `$my_uri` variable, or to "/" if `$my_uri` is undefined
+       or is an empty string).
+
+$home_link_str::
+       Label for the "home link" at the top of all pages, leading to `$home_link`
+       (usually the main gitweb page, which contains the projects list).  It is
+       used as the first component of gitweb's "breadcrumb trail":
+       `<home link> / <project> / <action>`.  Can be set at build time using
+       the `GITWEB_HOME_LINK_STR` variable.  By default it is set to "projects",
+       as this link leads to the list of projects.  Other popular choice it to
+       set it to the name of site.
+
+$logo_url::
+$logo_label::
+       URI and label (title) for the Git logo link (or your site logo,
+       if you chose to use different logo image). By default, these both
+       refer to git homepage, http://git-scm.com[]; in the past, they pointed
+       to git documentation at http://www.kernel.org[].
+
+
+Changing gitweb's look
+~~~~~~~~~~~~~~~~~~~~~~
+You can adjust how pages generated by gitweb look using the variables described
+below.  You can change the site name, add common headers and footers for all
+pages, and add a description of this gitweb installation on its main page
+(which is the projects list page), etc.
+
+$site_name::
+       Name of your site or organization, to appear in page titles.  Set it
+       to something descriptive for clearer bookmarks etc.  If this variable
+       is not set or is, then gitweb uses the value of the `SERVER_NAME`
+       CGI environment variable, setting site name to "$SERVER_NAME Git",
+       or "Untitled Git" if this variable is not set (e.g. if running gitweb
+       as standalone script).
++
+Can be set using the `GITWEB_SITENAME` at build time.  Unset by default.
+
+$site_html_head_string::
+       HTML snippet to be included in the <head> section of each page.
+       Can be set using `GITWEB_SITE_HTML_HEAD_STRING` at build time.
+       No default value.
+
+$site_header::
+       Name of a file with HTML to be included at the top of each page.
+       Relative to the directory containing the 'gitweb.cgi' script.
+       Can be set using `GITWEB_SITE_HEADER` at build time.  No default
+       value.
+
+$site_footer::
+       Name of a file with HTML to be included at the bottom of each page.
+       Relative to the directory containing the 'gitweb.cgi' script.
+       Can be set using `GITWEB_SITE_FOOTER` at build time.  No default
+       value.
+
+$home_text::
+       Name of a HTML file which, if it exists, is included on the
+       gitweb projects overview page ("projects_list" view).  Relative to
+       the directory containing the gitweb.cgi script.  Default value
+       can be adjusted during build time using `GITWEB_HOMETEXT` variable.
+       By default set to 'indextext.html'.
+
+$projects_list_description_width::
+       The width (in characters) of the "Description" column of the projects list.
+       Longer descriptions will be truncated (trying to cut at word boundary);
+       the full description is available in the 'title' attribute (usually shown on
+       mouseover).  The default is 25, which might be too small if you
+       use long project descriptions.
+
+$default_projects_order::
+       Default value of ordering of projects on projects list page, which
+       means the ordering used if you don't explicitly sort projects list
+       (if there is no "o" CGI query parameter in the URL).  Valid values
+       are "none" (unsorted), "project" (projects are by project name,
+       i.e. path to repository relative to `$projectroot`), "descr"
+       (project description), "owner", and "age" (by date of most current
+       commit).
++
+Default value is "project".  Unknown value means unsorted.
+
+
+Changing gitweb's behavior
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+These configuration variables control _internal_ gitweb behavior.
+
+$default_blob_plain_mimetype::
+       Default mimetype for the blob_plain (raw) view, if mimetype checking
+       doesn't result in some other type; by default "text/plain".
+       Gitweb guesses mimetype of a file to display based on extension
+       of its filename, using `$mimetypes_file` (if set and file exists)
+       and '/etc/mime.types' files (see *mime.types*(5) manpage; only
+       filename extension rules are supported by gitweb).
+
+$default_text_plain_charset::
+       Default charset for text files. If this is not set, the web server
+       configuration will be used.  Unset by default.
+
+$fallback_encoding::
+       Gitweb assumes this charset when a line contains non-UTF-8 characters.
+       The fallback decoding is used without error checking, so it can be even
+       "utf-8". The value must be a valid encoding; see the *Encoding::Supported*(3pm)
+       man page for a list. The default is "latin1", aka. "iso-8859-1".
+
+@diff_opts::
+       Rename detection options for git-diff and git-diff-tree. The default is
+       (\'-M'); set it to (\'-C') or (\'-C', \'-C') to also detect copies,
+       or set it to () i.e. empty list if you don't want to have renames
+       detection.
++
+*Note* that rename and especially copy detection can be quite
+CPU-intensive.  Note also that non git tools can have problems with
+patches generated with options mentioned above, especially when they
+involve file copies (\'-C') or criss-cross renames (\'-B').
+
+
+Some optional features and policies
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Most of features are configured via `%feature` hash; however some of extra
+gitweb features can be turned on and configured using variables described
+below.  This list beside configuration variables that control how gitweb
+looks does contain variables configuring administrative side of gitweb
+(e.g. cross-site scripting prevention; admittedly this as side effect
+affects how "summary" pages look like, or load limiting).
+
+@git_base_url_list::
+       List of git base URLs.  These URLs are used to generate URLs
+       describing from where to fetch a project, which are shown on
+       project summary page.  The full fetch URL is "`$git_base_url/$project`",
+       for each element of this list. You can set up multiple base URLs
+       (for example one for `git://` protocol, and one for `http://`
+       protocol).
++
+Note that per repository configuration can be set in '$GIT_DIR/cloneurl'
+file, or as values of multi-value `gitweb.url` configuration variable in
+project config.  Per-repository configuration takes precedence over value
+composed from `@git_base_url_list` elements and project name.
++
+You can setup one single value (single entry/item in this list) at build
+time by setting the `GITWEB_BASE_URL` built-time configuration variable.
+By default it is set to (), i.e. an empty list.  This means that gitweb
+would not try to create project URL (to fetch) from project name.
+
+$projects_list_group_categories::
+       Whether to enables the grouping of projects by category on the project
+       list page. The category of a project is determined by the
+       `$GIT_DIR/category` file or the `gitweb.category` variable in each
+       repository's configuration.  Disabled by default (set to 0).
+
+$project_list_default_category::
+       Default category for projects for which none is specified.  If this is
+       set to the empty string, such projects will remain uncategorized and
+       listed at the top, above categorized projects.  Used only if project
+       categories are enabled, which means if `$projects_list_group_categories`
+       is true.  By default set to "" (empty string).
+
+$prevent_xss::
+       If true, some gitweb features are disabled to prevent content in
+       repositories from launching cross-site scripting (XSS) attacks.  Set this
+       to true if you don't trust the content of your repositories.
+       False by default (set to 0).
+
+$maxload::
+       Used to set the maximum load that we will still respond to gitweb queries.
+       If the server load exceeds this value then gitweb will return
+       "503 Service Unavailable" error.  The server load is taken to be 0
+       if gitweb cannot determine its value.  Currently it works only on Linux,
+       where it uses '/proc/loadavg'; the load there is the number of active
+       tasks on the system -- processes that are actually running -- averaged
+       over the last minute.
++
+Set `$maxload` to undefined value (`undef`) to turn this feature off.
+The default value is 300.
+
+$per_request_config::
+       If this is set to code reference, it will be run once for each request.
+       You can set parts of configuration that change per session this way.
+       For example, one might use the following code in a gitweb configuration
+       file
++
+--------------------------------------------------------------------------------
+our $per_request_config = sub {
+       $ENV{GL_USER} = $cgi->remote_user || "gitweb";
+};
+--------------------------------------------------------------------------------
++
+If `$per_request_config` is not a code reference, it is interpreted as boolean
+value.  If it is true gitweb will process config files once per request,
+and if it is false gitweb will process config files only once, each time it
+is executed.  True by default (set to 1).
++
+*NOTE*: `$my_url`, `$my_uri`, and `$base_url` are overwritten with their default
+values before every request, so if you want to change them, be sure to set
+this variable to true or a code reference effecting the desired changes.
++
+This variable matters only when using persistent web environments that
+serve multiple requests using single gitweb instance, like mod_perl,
+FastCGI or Plackup.
+
+
+Other variables
+~~~~~~~~~~~~~~~
+Usually you should not need to change (adjust) any of configuration
+variables described below; they should be automatically set by gitweb to
+correct value.
+
+
+$version::
+       Gitweb version, set automatically when creating gitweb.cgi from
+       gitweb.perl. You might want to modify it if you are running modified
+       gitweb, for example
++
+---------------------------------------------------
+our $version .= " with caching";
+---------------------------------------------------
++
+if you run modified version of gitweb with caching support.  This variable
+is purely informational, used e.g. in the "generator" meta header in HTML
+header.
+
+$my_url::
+$my_uri::
+       Full URL and absolute URL of the gitweb script;
+       in earlier versions of gitweb you might have need to set those
+       variables, but now there should be no need to do it.  See
+       `$per_request_config` if you need to set them still.
+
+$base_url::
+       Base URL for relative URLs in pages generated by gitweb,
+       (e.g. `$logo`, `$favicon`, `@stylesheets` if they are relative URLs),
+       needed and used '<base href="$base_url">' only for URLs with nonempty
+       PATH_INFO.  Usually gitweb sets its value correctly,
+       and there is no need to set this variable, e.g. to $my_uri or "/".
+       See `$per_request_config` if you need to override it anyway.
+
+
+CONFIGURING GITWEB FEATURES
+---------------------------
+Many gitweb features can be enabled (or disabled) and configured using the
+`%feature` hash.  Names of gitweb features are keys of this hash.
+
+Each `%feature` hash element is a hash reference and has the following
+structure:
+----------------------------------------------------------------------
+"<feature_name>" => {
+       "sub" => <feature-sub (subroutine)>,
+       "override" => <allow-override (boolean)>,
+       "default" => [ <options>... ]
+},
+----------------------------------------------------------------------
+Some features cannot be overridden per project.  For those
+features the structure of appropriate `%feature` hash element has a simpler
+form:
+----------------------------------------------------------------------
+"<feature_name>" => {
+       "override" => 0,
+       "default" => [ <options>... ]
+},
+----------------------------------------------------------------------
+As one can see it lacks the \'sub' element.
+
+The meaning of each part of feature configuration is described
+below:
+
+default::
+       List (array reference) of feature parameters (if there are any),
+       used also to toggle (enable or disable) given feature.
++
+Note that it is currently *always* an array reference, even if
+feature doesn't accept any configuration parameters, and \'default'
+is used only to turn it on or off.  In such case you turn feature on
+by setting this element to `[1]`, and torn it off by setting it to
+`[0]`.  See also the passage about the "blame" feature in the "Examples"
+section.
++
+To disable features that accept parameters (are configurable), you
+need to set this element to empty list i.e. `[]`.
+
+override::
+       If this field has a true value then the given feature is
+       overriddable, which means that it can be configured
+       (or enabled/disabled) on a per-repository basis.
++
+Usually given "<feature>" is configurable via the `gitweb.<feature>`
+config variable in the per-repository git configuration file.
++
+*Note* that no feature is overriddable by default.
+
+sub::
+       Internal detail of implementation.  What is important is that
+       if this field is not present then per-repository override for
+       given feature is not supported.
++
+You wouldn't need to ever change it in gitweb config file.
+
+
+Features in `%feature`
+~~~~~~~~~~~~~~~~~~~~~~
+The gitweb features that are configurable via `%feature` hash are listed
+below.  This should be a complete list, but ultimately the authoritative
+and complete list is in gitweb.cgi source code, with features described
+in the comments.
+
+blame::
+       Enable the "blame" and "blame_incremental" blob views, showing for
+       each line the last commit that modified it; see linkgit:git-blame[1].
+       This can be very CPU-intensive and is therefore disabled by default.
++
+This feature can be configured on a per-repository basis via
+repository's `gitweb.blame` configuration variable (boolean).
+
+snapshot::
+       Enable and configure the "snapshot" action, which allows user to
+       download a compressed archive of any tree or commit, as produced
+       by linkgit:git-archive[1] and possibly additionally compressed.
+       This can potentially generate high traffic if you have large project.
++
+The value of \'default' is a list of names of snapshot formats,
+defined in `%known_snapshot_formats` hash, that you wish to offer.
+Supported formats include "tgz", "tbz2", "txz" (gzip/bzip2/xz
+compressed tar archive) and "zip"; please consult gitweb sources for
+a definitive list.  By default only "tgz" is offered.
++
+This feature can be configured on a per-repository basis via
+repository's `gitweb.blame` configuration variable, which contains
+a comma separated list of formats or "none" to disable snapshots.
+Unknown values are ignored.
+
+grep::
+       Enable grep search, which lists the files in currently selected
+       tree (directory) containing the given string; see linkgit:git-grep[1].
+       This can be potentially CPU-intensive, of course.  Enabled by default.
++
+This feature can be configured on a per-repository basis via
+repository's `gitweb.grep` configuration variable (boolean).
+
+pickaxe::
+       Enable the so called pickaxe search, which will list the commits
+       that introduced or removed a given string in a file.  This can be
+       practical and quite faster alternative to "blame" action, but it is
+       still potentially CPU-intensive.  Enabled by default.
++
+The pickaxe search is described in linkgit:git-log[1] (the
+description of `-S<string>` option, which refers to pickaxe entry in
+linkgit:gitdiffcore[7] for more details).
++
+This feature can be configured on a per-repository basis by setting
+repository's `gitweb.pickaxe` configuration variable (boolean).
+
+show-sizes::
+       Enable showing size of blobs (ordinary files) in a "tree" view, in a
+       separate column, similar to what `ls -l` does; see description of
+       `-l` option in linkgit:git-ls-tree[1] manpage.  This costs a bit of
+       I/O.  Enabled by default.
++
+This feature can be configured on a per-repository basis via
+repository's `gitweb.showsizes` configuration variable (boolean).
+
+patches::
+       Enable and configure "patches" view, which displays list of commits in email
+       (plain text) output format; see also linkgit:git-format-patch[1].
+       The value is the maximum number of patches in a patchset generated
+       in "patches" view.  Set the 'default' field to a list containing single
+       item of or to an empty list to disable patch view, or to a list
+       containing a single negative number to remove any limit.
+       Default value is 16.
++
+This feature can be configured on a per-repository basis via
+repository's `gitweb.patches` configuration variable (integer).
+
+avatar::
+       Avatar support.  When this feature is enabled, views such as
+       "shortlog" or "commit" will display an avatar associated with
+       the email of each committer and author.
++
+Currently available providers are *"gravatar"* and *"picon"*.
+Only one provider at a time can be selected ('default' is one element list).
+If an unknown provider is specified, the feature is disabled.
+*Note* that some providers might require extra Perl packages to be
+installed; see 'gitweb/INSTALL' for more details.
++
+This feature can be configured on a per-repository basis via
+repository's `gitweb.avatar` configuration variable.
++
+See also `%avatar_size` with pixel sizes for icons and avatars
+("default" is used for one-line like "log" and "shortlog", "double"
+is used for two-line like "commit", "commitdiff" or "tag").  If the
+default font sizes or lineheights are changed (e.g. via adding extra
+CSS stylesheet in `@stylesheets`), it may be appropriate to change
+these values.
+
+highlight::
+       Server-side syntax highlight support in "blob" view.  It requires
+       `$highlight_bin` program to be available (see the description of
+       this variable in the "Configuration variables" section above),
+       and therefore is disabled by default.
++
+This feature can be configured on a per-repository basis via
+repository's `gitweb.highlight` configuration variable (boolean).
+
+remote_heads::
+       Enable displaying remote heads (remote-tracking branches) in the "heads"
+       list.  In most cases the list of remote-tracking branches is an
+       unnecessary internal private detail, and this feature is therefore
+       disabled by default.  linkgit:git-instaweb[1], which is usually used
+       to browse local repositories, enables and uses this feature.
++
+This feature can be configured on a per-repository basis via
+repository's `gitweb.remote_heads` configuration variable (boolean).
+
+
+The remaining features cannot be overridden on a per project basis.
+
+search::
+       Enable text search, which will list the commits which match author,
+       committer or commit text to a given string; see the description of
+       `--author`, `--committer` and `--grep` options in linkgit:git-log[1]
+       manpage.  Enabled by default.
++
+Project specific override is not supported.
+
+forks::
+       If this feature is enabled, gitweb considers projects in
+       subdirectories of project root (basename) to be forks of existing
+       projects.  For each project `$projname.git`, projects in the
+       `$projname/` directory and its subdirectories will not be
+       shown in the main projects list.  Instead, a \'+' mark is shown
+       next to `$projname`, which links to a "forks" view that lists all
+       the forks (all projects in `$projname/` subdirectory).  Additionally
+       a "forks" view for a project is linked from project summary page.
++
+If the project list is taken from a file (`$projects_list` points to a
+file), forks are only recognized if they are listed after the main project
+in that file.
++
+Project specific override is not supported.
+
+actions::
+       Insert custom links to the action bar of all project pages.  This
+       allows you to link to third-party scripts integrating into gitweb.
++
+The "default" value consists of a list of triplets in the form
+`("<label>", "<link>", "<position>")` where "position" is the label
+after which to insert the link, "link" is a format string where `%n`
+expands to the project name, `%f` to the project path within the
+filesystem (i.e. "$projectroot/$project"), `%h` to the current hash
+(\'h' gitweb parameter) and `%b` to the current hash base
+(\'hb' gitweb parameter); `%%` expands to \'%'.
++
+For example, at the time this page was written, the http://repo.or.cz[]
+git hosting site set it to the following to enable graphical log
+(using the third party tool *git-browser*):
++
+----------------------------------------------------------------------
+$feature{'actions'}{'default'} =
+       [ ('graphiclog', '/git-browser/by-commit.html?r=%n', 'summary')];
+----------------------------------------------------------------------
++
+This adds a link titled "graphiclog" after the "summary" link, leading to
+`git-browser` script, passing `r=<project>` as a query parameter.
++
+Project specific override is not supported.
+
+timed::
+       Enable displaying how much time and how many git commands it took to
+       generate and display each page in the page footer (at the bottom of
+       page).  For example the footer might contain: "This page took 6.53325
+       seconds and 13 git commands to generate."  Disabled by default.
++
+Project specific override is not supported.
+
+javascript-timezone::
+       Enable and configure the ability to change a common timezone for dates
+       in gitweb output via JavaScript.  Dates in gitweb output include
+       authordate and committerdate in "commit", "commitdiff" and "log"
+       views, and taggerdate in "tag" view.  Enabled by default.
++
+The value is a list of three values: a default timezone (for if the client
+hasn't selected some other timezone and saved it in a cookie), a name of cookie
+where to store selected timezone, and a CSS class used to mark up
+dates for manipulation.  If you want to turn this feature off, set "default"
+to empty list: `[]`.
++
+Typical gitweb config files will only change starting (default) timezone,
+and leave other elements at their default values:
++
+---------------------------------------------------------------------------
+$feature{'javascript-timezone'}{'default'}[0] = "utc";
+---------------------------------------------------------------------------
++
+The example configuration presented here is guaranteed to be backwards
+and forward compatible.
++
+Timezone values can be "local" (for local timezone that browser uses), "utc"
+(what gitweb uses when JavaScript or this feature is disabled), or numerical
+timezones in the form of "+/-HHMM", such as "+0200".
++
+Project specific override is not supported.
+
+
+EXAMPLES
+--------
+
+To enable blame, pickaxe search, and snapshot support (allowing "tar.gz" and
+"zip" snapshots), while allowing individual projects to turn them off, put
+the following in your GITWEB_CONFIG file:
+
+       $feature{'blame'}{'default'} = [1];
+       $feature{'blame'}{'override'} = 1;
+
+       $feature{'pickaxe'}{'default'} = [1];
+       $feature{'pickaxe'}{'override'} = 1;
+
+       $feature{'snapshot'}{'default'} = ['zip', 'tgz'];
+       $feature{'snapshot'}{'override'} = 1;
+
+If you allow overriding for the snapshot feature, you can specify which
+snapshot formats are globally disabled. You can also add any command line
+options you want (such as setting the compression level). For instance, you
+can disable Zip compressed snapshots and set *gzip*(1) to run at level 6 by
+adding the following lines to your gitweb configuration file:
+
+       $known_snapshot_formats{'zip'}{'disabled'} = 1;
+       $known_snapshot_formats{'tgz'}{'compressor'} = ['gzip','-6'];
+
+ENVIRONMENT
+-----------
+The location of per-instance and system-wide configuration files can be
+overridden using the following environment variables:
+
+GITWEB_CONFIG::
+       Sets location of per-instance configuration file.
+GITWEB_CONFIG_SYSTEM::
+       Sets location of fallback system-wide configuration file.
+       This file is read only if per-instance one does not exist.
+GITWEB_CONFIG_COMMON::
+       Sets location of common system-wide configuration file.
+
+
+FILES
+-----
+gitweb_config.perl::
+       This is default name of per-instance configuration file.  The
+       format of this file is described above.
+/etc/gitweb.conf::
+       This is default name of fallback system-wide configuration
+       file.  This file is used only if per-instance configuration
+       variable is not found.
+/etc/gitweb-common.conf::
+       This is default name of common system-wide configuration
+       file.
+
+
+SEE ALSO
+--------
+linkgit:gitweb[1], linkgit:git-instaweb[1]
+
+'gitweb/README', 'gitweb/INSTALL'
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/gitweb.txt b/Documentation/gitweb.txt
new file mode 100644 (file)
index 0000000..605a085
--- /dev/null
@@ -0,0 +1,704 @@
+gitweb(1)
+=========
+
+NAME
+----
+gitweb - Git web interface (web frontend to Git repositories)
+
+SYNOPSIS
+--------
+To get started with gitweb, run linkgit:git-instaweb[1] from a git repository.
+This would configure and start your web server, and run web browser pointing to
+gitweb.
+
+
+DESCRIPTION
+-----------
+Gitweb provides a web interface to git repositories.  It's features include:
+
+* Viewing multiple Git repositories with common root.
+* Browsing every revision of the repository.
+* Viewing the contents of files in the repository at any revision.
+* Viewing the revision log of branches, history of files and directories,
+  see what was changed when, by who.
+* Viewing the blame/annotation details of any file (if enabled).
+* Generating RSS and Atom feeds of commits, for any branch.
+  The feeds are auto-discoverable in modern web browsers.
+* Viewing everything that was changed in a revision, and step through
+  revisions one at a time, viewing the history of the repository.
+* Finding commits which commit messages matches given search term.
+
+See http://git.kernel.org/?p=git/git.git;a=tree;f=gitweb[] or
+http://repo.or.cz/w/git.git/tree/HEAD:/gitweb/[] for gitweb source code,
+browsed using gitweb itself.
+
+
+CONFIGURATION
+-------------
+Various aspects of gitweb's behavior can be controlled through the configuration
+file 'gitweb_config.perl' or '/etc/gitweb.conf'.  See the linkgit:gitweb.conf[5]
+for details.
+
+Repositories
+~~~~~~~~~~~~
+Gitweb can show information from one or more Git repositories.  These
+repositories have to be all on local filesystem, and have to share common
+repository root, i.e. be all under a single parent repository (but see also
+"Advanced web server setup" section, "Webserver configuration with multiple
+projects' root" subsection).
+
+-----------------------------------------------------------------------
+our $projectroot = '/path/to/parent/directory';
+-----------------------------------------------------------------------
+
+The default value for `$projectroot` is '/pub/git'.  You can change it during
+building gitweb via `GITWEB_PROJECTROOT` build configuration variable.
+
+By default all git repositories under `$projectroot` are visible and available
+to gitweb.  The list of projects is generated by default by scanning the
+`$projectroot` directory for git repositories (for object databases to be
+more exact; gitweb is not interested in a working area, and is best suited
+to showing "bare" repositories).
+
+The name of repository in gitweb is path to it's `$GIT_DIR` (it's object
+database) relative to `$projectroot`.  Therefore the repository $repo can be
+found at "$projectroot/$repo".
+
+
+Projects list file format
+~~~~~~~~~~~~~~~~~~~~~~~~~
+Instead of having gitweb find repositories by scanning filesystem
+starting from $projectroot, you can provide a pre-generated list of
+visible projects by setting `$projects_list` to point to a plain text
+file with a list of projects (with some additional info).
+
+This file uses the following format:
+
+* One record (for project / repository) per line; does not support line
+continuation (newline escaping).
+
+* Leading and trailing whitespace are ignored.
+
+* Whitespace separated fields; any run of whitespace can be used as field
+separator (rules for Perl's "`split(" ", $line)`").
+
+* Fields use modified URI encoding, defined in RFC 3986, section 2.1
+(Percent-Encoding), or rather "Query string encoding" (see
+link:http://en.wikipedia.org/wiki/Query_string#URL_encoding[]), the difference
+being that SP (" ") can be encoded as "{plus}" (and therefore "{plus}" has to be
+also percent-encoded).
++
+Reserved characters are: "%" (used for encoding), "{plus}" (can be used to
+encode SPACE), all whitespace characters as defined in Perl, including SP,
+TAB and LF, (used to separate fields in a record).
+
+* Currently recognized fields are:
+<repository path>::
+       path to repository GIT_DIR, relative to `$projectroot`
+<repository owner>::
+       displayed as repository owner, preferably full name, or email,
+       or both
+
+You can generate the projects list index file using the project_index action
+(the 'TXT' link on projects list page) directly from gitweb; see also
+"Generating projects list using gitweb" section below.
+
+Example contents:
+-----------------------------------------------------------------------
+foo.git       Joe+R+Hacker+<joe@example.com>
+foo/bar.git   O+W+Ner+<owner@example.org>
+-----------------------------------------------------------------------
+
+
+By default this file controls only which projects are *visible* on projects
+list page (note that entries that do not point to correctly recognized git
+repositories won't be displayed by gitweb).  Even if a project is not
+visible on projects list page, you can view it nevertheless by hand-crafting
+a gitweb URL.  By setting `$strict_export` configuration variable (see
+linkgit:gitweb.conf[5]) to true value you can allow viewing only of
+repositories also shown on the overview page (i.e. only projects explicitly
+listed in projects list file will be accessible).
+
+
+Generating projects list using gitweb
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We assume that GITWEB_CONFIG has its default Makefile value, namely
+'gitweb_config.perl'. Put the following in 'gitweb_make_index.perl' file:
+----------------------------------------------------------------------------
+read_config_file("gitweb_config.perl");
+$projects_list = $projectroot;
+----------------------------------------------------------------------------
+
+Then create the following script to get list of project in the format
+suitable for GITWEB_LIST build configuration variable (or
+`$projects_list` variable in gitweb config):
+
+----------------------------------------------------------------------------
+#!/bin/sh
+
+export GITWEB_CONFIG="gitweb_make_index.perl"
+export GATEWAY_INTERFACE="CGI/1.1"
+export HTTP_ACCEPT="*/*"
+export REQUEST_METHOD="GET"
+export QUERY_STRING="a=project_index"
+
+perl -- /var/www/cgi-bin/gitweb.cgi
+----------------------------------------------------------------------------
+
+Run this script and save its output to a file.  This file could then be used
+as projects list file, which means that you can set `$projects_list` to its
+filename.
+
+
+Controlling access to git repositories
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+By default all git repositories under `$projectroot` are visible and
+available to gitweb.  You can however configure how gitweb controls access
+to repositories.
+
+* As described in "Projects list file format" section, you can control which
+projects are *visible* by selectively including repositories in projects
+list file, and setting `$projects_list` gitweb configuration variable to
+point to it.  With `$strict_export` set, projects list file can be used to
+control which repositories are *available* as well.
+
+* You can configure gitweb to only list and allow viewing of the explicitly
+exported repositories, via `$export_ok` variable in gitweb config file; see
+linkgit:gitweb.conf[5] manpage.  If it evaluates to true, gitweb shows
+repositories only if this file named by `$export_ok` exists in its object
+database (if directory has the magic file named `$export_ok`).
++
+For example linkgit:git-daemon[1] by default (unless `--export-all` option
+is used) allows pulling only for those repositories that have
+'git-daemon-export-ok' file.  Adding
++
+--------------------------------------------------------------------------
+our $export_ok = "git-daemon-export-ok";
+--------------------------------------------------------------------------
++
+makes gitweb show and allow access only to those repositories that can be
+fetched from via `git://` protocol.
+
+* Finally, it is possible to specify an arbitrary perl subroutine that will
+be called for each repository to determine if it can be exported.  The
+subroutine receives an absolute path to the project (repository) as its only
+parameter (i.e. "$projectroot/$project").
++
+For example, if you use mod_perl to run the script, and have dumb
+HTTP protocol authentication configured for your repositories, you
+can use the following hook to allow access only if the user is
+authorized to read the files:
++
+--------------------------------------------------------------------------
+$export_auth_hook = sub {
+       use Apache2::SubRequest ();
+       use Apache2::Const -compile => qw(HTTP_OK);
+       my $path = "$_[0]/HEAD";
+       my $r    = Apache2::RequestUtil->request;
+       my $sub  = $r->lookup_file($path);
+       return $sub->filename eq $path
+           && $sub->status == Apache2::Const::HTTP_OK;
+};
+--------------------------------------------------------------------------
+
+
+Per-repository gitweb configuration
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+You can configure individual repositories shown in gitweb by creating file
+in the 'GIT_DIR' of git repository, or by setting some repo configuration
+variable (in 'GIT_DIR/config', see linkgit:git-config[1]).
+
+You can use the following files in repository:
+
+README.html::
+       A html file (HTML fragment) which is included on the gitweb project
+       "summary" page inside `<div>` block element. You can use it for longer
+       description of a project, to provide links (for example to project's
+       homepage), etc. This is recognized only if XSS prevention is off
+       (`$prevent_xss` is false, see linkgit:gitweb.conf[5]); a way to include
+       a README safely when XSS prevention is on may be worked out in the
+       future.
+
+description (or `gitweb.description`)::
+       Short (shortened to `$projects_list_description_width` in the projects
+       list page, which is 25 characters by default; see
+       linkgit:gitweb.conf[5]) single line description of a project (of a
+       repository).  Plain text file; HTML will be escaped.  By default set to
++
+-------------------------------------------------------------------------------
+Unnamed repository; edit this file to name it for gitweb.
+-------------------------------------------------------------------------------
++
+from the template during repository creation, usually installed in
+'/usr/share/git-core/templates/'.  You can use the `gitweb.description` repo
+configuration variable, but the file takes precedence.
+
+category (or `gitweb.category`)::
+       Singe line category of a project, used to group projects if
+       `$projects_list_group_categories` is enabled.  By default (file and
+       configuration variable absent), uncategorized projects are put in the
+       `$project_list_default_category` category.  You can use the
+       `gitweb.category` repo configuration variable, but the file takes
+       precedence.
++
+The configuration variables `$projects_list_group_categories` and
+`$project_list_default_category` are described in linkgit:gitweb.conf[5]
+
+cloneurl (or multiple-valued `gitweb.url`)::
+       File with repository URL (used for clone and fetch), one per line.
+       Displayed in the project summary page. You can use multiple-valued
+       `gitweb.url` repository configuration variable for that, but the file
+       takes precedence.
++
+This is per-repository enhancement / version of global prefix-based
+`@git_base_url_list` gitweb configuration variable (see
+linkgit:gitweb.conf[5]).
+
+gitweb.owner::
+       You can use the `gitweb.owner` repository configuration variable to set
+       repository's owner.  It is displayed in the project list and summary
+       page.
++
+If it's not set, filesystem directory's owner is used (via GECOS field,
+i.e. real name field from *getpwuid*(3)) if `$projects_list` is unset
+(gitweb scans `$projectroot` for repositories); if `$projects_list`
+points to file with list of repositories, then project owner defaults to
+value from this file for given repository.
+
+various `gitweb.*` config variables (in config)::
+       Read description of `%feature` hash for detailed list, and descriptions.
+       See also "Configuring gitweb features" section in linkgit:gitweb.conf[5]
+
+
+ACTIONS, AND URLS
+-----------------
+Gitweb can use path_info (component) based URLs, or it can pass all necessary
+information via query parameters.  The typical gitweb URLs are broken down in to
+five components:
+
+-----------------------------------------------------------------------
+.../gitweb.cgi/<repo>/<action>/<revision>:/<path>?<arguments>
+-----------------------------------------------------------------------
+
+repo::
+       The repository the action will be performed on.
++
+All actions except for those that list all available projects,
+in whatever form, require this parameter.
+
+action::
+       The action that will be run.  Defaults to 'projects_list' if repo
+       is not set, and to 'summary' otherwise.
+
+revision::
+       Revision shown.  Defaults to HEAD.
+
+path::
+       The path within the <repository> that the action is performed on,
+       for those actions that require it.
+
+arguments::
+       Any arguments that control the behaviour of the action.
+
+Some actions require or allow to specify two revisions, and sometimes even two
+pathnames.  In most general form such path_info (component) based gitweb URL
+looks like this:
+
+-----------------------------------------------------------------------
+.../gitweb.cgi/<repo>/<action>/<revision_from>:/<path_from>..<revision_to>:/<path_to>?<arguments>
+-----------------------------------------------------------------------
+
+
+Each action is implemented as a subroutine, and must be present in %actions
+hash.  Some actions are disabled by default, and must be turned on via feature
+mechanism.  For example to enable 'blame' view add the following to gitweb
+configuration file:
+
+-----------------------------------------------------------------------
+$feature{'blame'}{'default'} = [1];
+-----------------------------------------------------------------------
+
+
+Actions:
+~~~~~~~~
+The standard actions are:
+
+project_list::
+       Lists the available Git repositories.  This is the default command if no
+       repository is specified in the URL.
+
+summary::
+       Displays summary about given repository.  This is the default command if
+       no action is specified in URL, and only repository is specified.
+
+heads::
+remotes::
+       Lists all local or all remote-tracking branches in given repository.
++
+The latter is not available by default, unless configured.
+
+tags::
+       List all tags (lightweight and annotated) in given repository.
+
+blob::
+tree::
+       Shows the files and directories in a given repository path, at given
+       revision.  This is default command if no action is specified in the URL,
+       and path is given.
+
+blob_plain::
+       Returns the raw data for the file in given repository, at given path and
+       revision.  Links to this action are marked 'raw'.
+
+blobdiff::
+       Shows the difference between two revisions of the same file.
+
+blame::
+blame_incremental::
+       Shows the blame (also called annotation) information for a file. On a
+       per line basis it shows the revision in which that line was last changed
+       and the user that committed the change.  The incremental version (which
+       if configured is used automatically when JavaScript is enabled) uses
+       Ajax to incrementally add blame info to the contents of given file.
++
+This action is disabled by default for performance reasons.
+
+commit::
+commitdiff::
+       Shows information about a specific commit in a repository.  The 'commit'
+       view shows information about commit in more detail, the 'commitdiff'
+       action shows changeset for given commit.
+
+patch::
+       Returns the commit in plain text mail format, suitable for applying with
+       linkgit:git-am[1].
+
+tag::
+       Display specific annotated tag (tag object).
+
+log::
+shortlog::
+       Shows log information (commit message or just commit subject) for a
+       given branch (starting from given revision).
++
+The 'shortlog' view is more compact; it shows one commit per line.
+
+history::
+       Shows history of the file or directory in a given repository path,
+       starting from given revision (defaults to HEAD, i.e. default branch).
++
+This view is similar to 'shortlog' view.
+
+rss::
+atom::
+       Generates an RSS (or Atom) feed of changes to repository.
+
+
+WEBSERVER CONFIGURATION
+-----------------------
+This section explains how to configure some common webservers to run gitweb. In
+all cases, `/path/to/gitweb` in the examples is the directory you ran installed
+gitweb in, and contains `gitweb_config.perl`.
+
+If you've configured a web server that isn't listed here for gitweb, please send
+in the instructions so they can be included in a future release.
+
+Apache as CGI
+~~~~~~~~~~~~~
+Apache must be configured to support CGI scripts in the directory in
+which gitweb is installed.  Let's assume that it is '/var/www/cgi-bin'
+directory.
+
+-----------------------------------------------------------------------
+ScriptAlias /cgi-bin/ "/var/www/cgi-bin/"
+
+<Directory "/var/www/cgi-bin">
+    Options Indexes FollowSymlinks ExecCGI
+    AllowOverride None
+    Order allow,deny
+    Allow from all
+</Directory>
+-----------------------------------------------------------------------
+
+With that configuration the full path to browse repositories would be:
+
+  http://server/cgi-bin/gitweb.cgi
+
+Apache with mod_perl, via ModPerl::Registry
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+You can use mod_perl with gitweb.  You must install Apache::Registry
+(for mod_perl 1.x) or ModPerl::Registry (for mod_perl 2.x) to enable
+this support.
+
+Assuming that gitweb is installed to '/var/www/perl', the following
+Apache configuration (for mod_perl 2.x) is suitable.
+
+-----------------------------------------------------------------------
+Alias /perl "/var/www/perl"
+
+<Directory "/var/www/perl">
+    SetHandler perl-script
+    PerlResponseHandler ModPerl::Registry
+    PerlOptions +ParseHeaders
+    Options Indexes FollowSymlinks +ExecCGI
+    AllowOverride None
+    Order allow,deny
+    Allow from all
+</Directory>
+-----------------------------------------------------------------------
+
+With that configuration the full path to browse repositories would be:
+
+  http://server/perl/gitweb.cgi
+
+Apache with FastCGI
+~~~~~~~~~~~~~~~~~~~
+Gitweb works with Apache and FastCGI.  First you need to rename, copy
+or symlink gitweb.cgi to gitweb.fcgi.  Let's assume that gitweb is
+installed in '/usr/share/gitweb' directory.  The following Apache
+configuration is suitable (UNTESTED!)
+
+-----------------------------------------------------------------------
+FastCgiServer /usr/share/gitweb/gitweb.cgi
+ScriptAlias /gitweb /usr/share/gitweb/gitweb.cgi
+
+Alias /gitweb/static /usr/share/gitweb/static
+<Directory /usr/share/gitweb/static>
+    SetHandler default-handler
+</Directory>
+-----------------------------------------------------------------------
+
+With that configuration the full path to browse repositories would be:
+
+  http://server/gitweb
+
+
+ADVANCED WEB SERVER SETUP
+-------------------------
+All of those examples use request rewriting, and need `mod_rewrite`
+(or equivalent; examples below are written for Apache).
+
+Single URL for gitweb and for fetching
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+If you want to have one URL for both gitweb and your `http://`
+repositories, you can configure Apache like this:
+
+-----------------------------------------------------------------------
+<VirtualHost *:80>
+    ServerName    git.example.org
+    DocumentRoot  /pub/git
+    SetEnv        GITWEB_CONFIG   /etc/gitweb.conf
+
+    # turning on mod rewrite
+    RewriteEngine on
+
+    # make the front page an internal rewrite to the gitweb script
+    RewriteRule ^/$  /cgi-bin/gitweb.cgi
+
+    # make access for "dumb clients" work
+    RewriteRule ^/(.*\.git/(?!/?(HEAD|info|objects|refs)).*)?$ \
+               /cgi-bin/gitweb.cgi%{REQUEST_URI}  [L,PT]
+</VirtualHost>
+-----------------------------------------------------------------------
+
+The above configuration expects your public repositories to live under
+'/pub/git' and will serve them as `http://git.domain.org/dir-under-pub-git`,
+both as cloneable GIT URL and as browseable gitweb interface.  If you then
+start your linkgit:git-daemon[1] with `--base-path=/pub/git --export-all`
+then you can even use the `git://` URL with exactly the same path.
+
+Setting the environment variable `GITWEB_CONFIG` will tell gitweb to use the
+named file (i.e. in this example '/etc/gitweb.conf') as a configuration for
+gitweb.  You don't really need it in above example; it is required only if
+your configuration file is in different place than built-in (during
+compiling gitweb) 'gitweb_config.perl' or '/etc/gitweb.conf'.  See
+linkgit:gitweb.conf[5] for details, especially information about precedence
+rules.
+
+If you use the rewrite rules from the example you *might* also need
+something like the following in your gitweb configuration file
+('/etc/gitweb.conf' following example):
+----------------------------------------------------------------------------
+@stylesheets = ("/some/absolute/path/gitweb.css");
+$my_uri    = "/";
+$home_link = "/";
+$per_request_config = 1;
+----------------------------------------------------------------------------
+Nowadays though gitweb should create HTML base tag when needed (to set base
+URI for relative links), so it should work automatically.
+
+
+Webserver configuration with multiple projects' root
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+If you want to use gitweb with several project roots you can edit your
+Apache virtual host and gitweb configuration files in the following way.
+
+The virtual host configuration (in Apache configuration file) should look
+like this:
+--------------------------------------------------------------------------
+<VirtualHost *:80>
+    ServerName    git.example.org
+    DocumentRoot  /pub/git
+    SetEnv        GITWEB_CONFIG  /etc/gitweb.conf
+
+    # turning on mod rewrite
+    RewriteEngine on
+
+    # make the front page an internal rewrite to the gitweb script
+    RewriteRule ^/$  /cgi-bin/gitweb.cgi  [QSA,L,PT]
+
+    # look for a public_git folder in unix users' home
+    # http://git.example.org/~<user>/
+    RewriteRule ^/\~([^\/]+)(/|/gitweb.cgi)?$  /cgi-bin/gitweb.cgi \
+               [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT]
+
+    # http://git.example.org/+<user>/
+    #RewriteRule ^/\+([^\/]+)(/|/gitweb.cgi)?$ /cgi-bin/gitweb.cgi \
+                [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT]
+
+    # http://git.example.org/user/<user>/
+    #RewriteRule ^/user/([^\/]+)/(gitweb.cgi)?$        /cgi-bin/gitweb.cgi \
+                [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT]
+
+    # defined list of project roots
+    RewriteRule ^/scm(/|/gitweb.cgi)?$ /cgi-bin/gitweb.cgi \
+               [QSA,E=GITWEB_PROJECTROOT:/pub/scm/,L,PT]
+    RewriteRule ^/var(/|/gitweb.cgi)?$ /cgi-bin/gitweb.cgi \
+               [QSA,E=GITWEB_PROJECTROOT:/var/git/,L,PT]
+
+    # make access for "dumb clients" work
+    RewriteRule ^/(.*\.git/(?!/?(HEAD|info|objects|refs)).*)?$ \
+               /cgi-bin/gitweb.cgi%{REQUEST_URI}  [L,PT]
+</VirtualHost>
+--------------------------------------------------------------------------
+
+Here actual project root is passed to gitweb via `GITWEB_PROJECT_ROOT`
+environment variable from a web server, so you need to put the following
+line in gitweb configuration file ('/etc/gitweb.conf' in above example):
+--------------------------------------------------------------------------
+$projectroot = $ENV{'GITWEB_PROJECTROOT'} || "/pub/git";
+--------------------------------------------------------------------------
+*Note* that this requires to be set for each request, so either
+`$per_request_config` must be false, or the above must be put in code
+referenced by `$per_request_config`;
+
+These configurations enable two things. First, each unix user (`<user>`) of
+the server will be able to browse through gitweb git repositories found in
+'~/public_git/' with the following url:
+
+  http://git.example.org/~<user>/
+
+If you do not want this feature on your server just remove the second
+rewrite rule.
+
+If you already use `mod_userdir` in your virtual host or you don't want to
+use the \'~' as first character, just comment or remove the second rewrite
+rule, and uncomment one of the following according to what you want.
+
+Second, repositories found in '/pub/scm/' and '/var/git/' will be accessible
+through `http://git.example.org/scm/` and `http://git.example.org/var/`.
+You can add as many project roots as you want by adding rewrite rules like
+the third and the fourth.
+
+
+PATH_INFO usage
+~~~~~~~~~~~~~~~
+If you enable PATH_INFO usage in gitweb by putting
+----------------------------------------------------------------------------
+$feature{'pathinfo'}{'default'} = [1];
+----------------------------------------------------------------------------
+in your gitweb configuration file, it is possible to set up your server so
+that it consumes and produces URLs in the form
+
+  http://git.example.com/project.git/shortlog/sometag
+
+i.e. without 'gitweb.cgi' part, by using a configuration such as the
+following.  This configuration assumes that '/var/www/gitweb' is the
+DocumentRoot of your webserver, contains the gitweb.cgi script and
+complementary static files (stylesheet, favicon, JavaScript):
+
+----------------------------------------------------------------------------
+<VirtualHost *:80>
+       ServerAlias git.example.com
+
+       DocumentRoot /var/www/gitweb
+
+       <Directory /var/www/gitweb>
+               Options ExecCGI
+               AddHandler cgi-script cgi
+
+               DirectoryIndex gitweb.cgi
+
+               RewriteEngine On
+               RewriteCond %{REQUEST_FILENAME} !-f
+               RewriteCond %{REQUEST_FILENAME} !-d
+               RewriteRule ^.* /gitweb.cgi/$0 [L,PT]
+       </Directory>
+</VirtualHost>
+----------------------------------------------------------------------------
+The rewrite rule guarantees that existing static files will be properly
+served, whereas any other URL will be passed to gitweb as PATH_INFO
+parameter.
+
+*Notice* that in this case you don't need special settings for
+`@stylesheets`, `$my_uri` and `$home_link`, but you lose "dumb client"
+access to your project .git dirs (described in "Single URL for gitweb and
+for fetching" section).  A possible workaround for the latter is the
+following: in your project root dir (e.g. '/pub/git') have the projects
+named *without* a .git extension (e.g. '/pub/git/project' instead of
+'/pub/git/project.git') and configure Apache as follows:
+----------------------------------------------------------------------------
+<VirtualHost *:80>
+       ServerAlias git.example.com
+
+       DocumentRoot /var/www/gitweb
+
+       AliasMatch ^(/.*?)(\.git)(/.*)?$ /pub/git$1$3
+       <Directory /var/www/gitweb>
+               Options ExecCGI
+               AddHandler cgi-script cgi
+
+               DirectoryIndex gitweb.cgi
+
+               RewriteEngine On
+               RewriteCond %{REQUEST_FILENAME} !-f
+               RewriteCond %{REQUEST_FILENAME} !-d
+               RewriteRule ^.* /gitweb.cgi/$0 [L,PT]
+       </Directory>
+</VirtualHost>
+----------------------------------------------------------------------------
+
+The additional AliasMatch makes it so that
+
+  http://git.example.com/project.git
+
+will give raw access to the project's git dir (so that the project can be
+cloned), while
+
+  http://git.example.com/project
+
+will provide human-friendly gitweb access.
+
+This solution is not 100% bulletproof, in the sense that if some project has
+a named ref (branch, tag) starting with 'git/', then paths such as
+
+  http://git.example.com/project/command/abranch..git/abranch
+
+will fail with a 404 error.
+
+
+BUGS
+----
+Please report any bugs or feature requests to git@vger.kernel.org,
+putting "gitweb" in the subject of email.
+
+SEE ALSO
+--------
+linkgit:gitweb.conf[5], linkgit:git-instaweb[1]
+
+'gitweb/README', 'gitweb/INSTALL'
+
+GIT
+---
+Part of the linkgit:git[1] suite
index 35f440876ed182de319b6d3f0b8296b1a1ede29d..327f69bcf5a20c7cf33a712b08bed9d4ccfe9d2e 100755 (executable)
@@ -1,31 +1,39 @@
 #!/bin/sh
-# This requires a branch named in $head
-# (usually 'man' or 'html', provided by the git.git repository)
-set -e
-head="$1"
-mandir="$2"
-SUBDIRECTORY_OK=t
-USAGE='<refname> <target directory>'
-. "$(git --exec-path)"/git-sh-setup
-cd_to_toplevel
+# This requires git-manpages and/or git-htmldocs repositories
 
-test -z "$mandir" && usage
-if ! git rev-parse --verify "$head^0" >/dev/null; then
-       echo >&2 "head: $head does not exist in the current repository"
-       usage
+repository=${1?repository}
+destdir=${2?destination}
+
+head=master GIT_DIR=
+for d in "$repository/.git" "$repository"
+do
+       if GIT_DIR="$d" git rev-parse refs/heads/master >/dev/null 2>&1
+       then
+               GIT_DIR="$d"
+               export GIT_DIR
+               break
+       fi
+done
+
+if test -z "$GIT_DIR"
+then
+       echo >&2 "Neither $repository nor $repository/.git is a repository"
+       exit 1
 fi
 
-GIT_INDEX_FILE=`pwd`/.quick-doc.index
-export GIT_INDEX_FILE
+GIT_WORK_TREE=$(pwd)
+GIT_INDEX_FILE=$(pwd)/.quick-doc.$$
+export GIT_INDEX_FILE GIT_WORK_TREE
 rm -f "$GIT_INDEX_FILE"
 trap 'rm -f "$GIT_INDEX_FILE"' 0
 
 git read-tree $head
-git checkout-index -a -f --prefix="$mandir"/
+git checkout-index -a -f --prefix="$destdir"/
 
-if test -n "$GZ"; then
+if test -n "$GZ"
+then
        git ls-tree -r --name-only $head |
-       xargs printf "$mandir/%s\n" |
+       xargs printf "$destdir/%s\n" |
        xargs gzip -f
 fi
 rm -f "$GIT_INDEX_FILE"
index b613d4ed083d080797f7da90fc92212f98611d07..1a5c12e3171ab82f489bcd53657c8a89741a3dab 100644 (file)
@@ -7,6 +7,11 @@ With --no-commit perform the merge but pretend the merge
 failed and do not autocommit, to give the user a chance to
 inspect and further tweak the merge result before committing.
 
+--edit::
+-e::
+       Invoke editor before committing successful merge to further
+       edit the default merge message.
+
 --ff::
 --no-ff::
        Do not generate a merge commit if the merge resolved as
index 3e6df338bed519eb74cf233f40589ddbfb0da57b..5747f442f25e6271c7464db78496b85ee058d05f 100644 (file)
@@ -1,9 +1,12 @@
---reset::
-       Forget about the current operation in progress.  Can be used
-       to clear the sequencer state after a failed cherry-pick or
-       revert.
-
 --continue::
        Continue the operation in progress using the information in
        '.git/sequencer'.  Can be used to continue after resolving
        conflicts in a failed cherry-pick or revert.
+
+--quit::
+       Forget about the current operation in progress.  Can be used
+       to clear the sequencer state after a failed cherry-pick or
+       revert.
+
+--abort::
+       Cancel the operation and return to the pre-sequence state.
diff --git a/Documentation/technical/api-argv-array.txt b/Documentation/technical/api-argv-array.txt
new file mode 100644 (file)
index 0000000..49b3d52
--- /dev/null
@@ -0,0 +1,46 @@
+argv-array API
+==============
+
+The argv-array API allows one to dynamically build and store
+NULL-terminated lists.  An argv-array maintains the invariant that the
+`argv` member always points to a non-NULL array, and that the array is
+always NULL-terminated at the element pointed to by `argv[argc]`. This
+makes the result suitable for passing to functions expecting to receive
+argv from main(), or the link:api-run-command.html[run-command API].
+
+The link:api-string-list.html[string-list API] is similar, but cannot be
+used for these purposes; instead of storing a straight string pointer,
+it contains an item structure with a `util` field that is not compatible
+with the traditional argv interface.
+
+Each `argv_array` manages its own memory. Any strings pushed into the
+array are duplicated, and all memory is freed by argv_array_clear().
+
+Data Structures
+---------------
+
+`struct argv_array`::
+
+       A single array. This should be initialized by assignment from
+       `ARGV_ARRAY_INIT`, or by calling `argv_array_init`. The `argv`
+       member contains the actual array; the `argc` member contains the
+       number of elements in the array, not including the terminating
+       NULL.
+
+Functions
+---------
+
+`argv_array_init`::
+       Initialize an array. This is no different than assigning from
+       `ARGV_ARRAY_INIT`.
+
+`argv_array_push`::
+       Push a copy of a string onto the end of the array.
+
+`argv_array_pushf`::
+       Format a string and push it onto the end of the array. This is a
+       convenience wrapper combining `strbuf_addf` and `argv_array_push`.
+
+`argv_array_clear`::
+       Free all memory associated with the array and return it to the
+       initial, empty state.
index f6a4a361bd56e1cc0f4645e09898e6852bfb8a8b..4b92514f60d65232e955cd5dddbeb5318add7274 100644 (file)
@@ -135,9 +135,14 @@ There are some macros to easily define options:
        describes the group or an empty string.
        Start the description with an upper-case letter.
 
-`OPT_BOOLEAN(short, long, &int_var, description)`::
-       Introduce a boolean option.
-       `int_var` is incremented on each use.
+`OPT_BOOL(short, long, &int_var, description)`::
+       Introduce a boolean option. `int_var` is set to one with
+       `--option` and set to zero with `--no-option`.
+
+`OPT_COUNTUP(short, long, &int_var, description)`::
+       Introduce a count-up option.
+       `int_var` is incremented on each use of `--option`, and
+       reset to zero with `--no-option`.
 
 `OPT_BIT(short, long, &int_var, description, mask)`::
        Introduce a boolean option.
@@ -148,8 +153,9 @@ There are some macros to easily define options:
        If used, `int_var` is bitwise-anded with the inverted `mask`.
 
 `OPT_SET_INT(short, long, &int_var, description, integer)`::
-       Introduce a boolean option.
-       If used, set `int_var` to `integer`.
+       Introduce an integer option.
+       `int_var` is set to `integer` with `--option`, and
+       reset to zero with `--no-option`.
 
 `OPT_SET_PTR(short, long, &ptr_var, description, ptr)`::
        Introduce a boolean option.
@@ -198,6 +204,11 @@ There are some macros to easily define options:
        "auto", set `int_var` to 1 if stdout is a tty or a pager,
        0 otherwise.
 
+`OPT_NOOP_NOARG(short, long)`::
+       Introduce an option that has no effect and takes no arguments.
+       Use it to hide deprecated options that are still to be recognized
+       and ignored silently.
+
 
 The last element of the array must be `OPT_END()`.
 
diff --git a/Documentation/technical/api-sha1-array.txt b/Documentation/technical/api-sha1-array.txt
new file mode 100644 (file)
index 0000000..4a4bae8
--- /dev/null
@@ -0,0 +1,79 @@
+sha1-array API
+==============
+
+The sha1-array API provides storage and manipulation of sets of SHA1
+identifiers. The emphasis is on storage and processing efficiency,
+making them suitable for large lists. Note that the ordering of items is
+not preserved over some operations.
+
+Data Structures
+---------------
+
+`struct sha1_array`::
+
+       A single array of SHA1 hashes. This should be initialized by
+       assignment from `SHA1_ARRAY_INIT`.  The `sha1` member contains
+       the actual data. The `nr` member contains the number of items in
+       the set.  The `alloc` and `sorted` members are used internally,
+       and should not be needed by API callers.
+
+Functions
+---------
+
+`sha1_array_append`::
+       Add an item to the set. The sha1 will be placed at the end of
+       the array (but note that some operations below may lose this
+       ordering).
+
+`sha1_array_sort`::
+       Sort the elements in the array.
+
+`sha1_array_lookup`::
+       Perform a binary search of the array for a specific sha1.
+       If found, returns the offset (in number of elements) of the
+       sha1. If not found, returns a negative integer. If the array is
+       not sorted, this function has the side effect of sorting it.
+
+`sha1_array_clear`::
+       Free all memory associated with the array and return it to the
+       initial, empty state.
+
+`sha1_array_for_each_unique`::
+       Efficiently iterate over each unique element of the list,
+       executing the callback function for each one. If the array is
+       not sorted, this function has the side effect of sorting it.
+
+Examples
+--------
+
+-----------------------------------------
+void print_callback(const unsigned char sha1[20],
+                   void *data)
+{
+       printf("%s\n", sha1_to_hex(sha1));
+}
+
+void some_func(void)
+{
+       struct sha1_array hashes = SHA1_ARRAY_INIT;
+       unsigned char sha1[20];
+
+       /* Read objects into our set */
+       while (read_object_from_stdin(sha1))
+               sha1_array_append(&hashes, sha1);
+
+       /* Check if some objects are in our set */
+       while (read_object_from_stdin(sha1)) {
+               if (sha1_array_lookup(&hashes, sha1) >= 0)
+                       printf("it's in there!\n");
+
+       /*
+        * Print the unique set of objects. We could also have
+        * avoided adding duplicate objects in the first place,
+        * but we would end up re-sorting the array repeatedly.
+        * Instead, this will sort once and then skip duplicates
+        * in linear time.
+        */
+       sha1_array_for_each_unique(&hashes, print_callback, NULL);
+}
+-----------------------------------------
index a7004c63e7ff5a4fe237267121a5b7c76a4c5496..546980c0a41ce9ba6d09ad5038b4412b7ef42cc7 100644 (file)
@@ -60,6 +60,13 @@ process on the server side over the Git protocol is this:
      "0039git-upload-pack /schacon/gitbook.git\0host=example.com\0" |
      nc -v example.com 9418
 
+If the server refuses the request for some reasons, it could abort
+gracefully with an error message.
+
+----
+  error-line     =  PKT-LINE("ERR" SP explanation-text)
+----
+
 
 SSH Transport
 -------------
index f1dc5faeaddfa814bc49775c7186d8142b9988be..a04c8f32b767d757066526fc4f4685df812f3825 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.7.7
+DEF_VER=v1.7.8
 
 LF='
 '
diff --git a/INSTALL b/INSTALL
index bbb9d4dc9ab37a242d8accb86fa8a0d35d677ab8..bf0d97ef769adda0066578c7823a124385785e94 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -139,34 +139,11 @@ Issues of note:
    uses some compatibility wrappers to work on AsciiDoc 8. If you have
    AsciiDoc 7, try "make ASCIIDOC7=YesPlease".
 
-   Alternatively, pre-formatted documentation is available in
-   "html" and "man" branches of the git repository itself.  For
-   example, you could:
-
-       $ mkdir manual && cd manual
-       $ git init
-       $ git fetch-pack git://git.kernel.org/pub/scm/git/git.git man html |
-         while read a b
-         do
-           echo $a >.git/$b
-         done
-       $ cp .git/refs/heads/man .git/refs/heads/master
-       $ git checkout
-
-   to checkout the pre-built man pages.  Also in this repository:
-
-       $ git checkout html
-
-   would instead give you a copy of what you see at:
-
-       http://www.kernel.org/pub/software/scm/git/docs/
-
    There are also "make quick-install-doc", "make quick-install-man"
    and "make quick-install-html" which install preformatted man pages
-   and html documentation.
-   This does not require asciidoc/xmlto, but it only works from within
-   a cloned checkout of git.git with these two extra branches, and will
-   not work for the maintainer for obvious chicken-and-egg reasons.
+   and html documentation. To use these build targets, you need to
+   clone two separate git-htmldocs and git-manpages repositories next
+   to the clone of git itself.
 
    It has been reported that docbook-xsl version 1.72 and 1.73 are
    buggy; 1.72 misformats manual pages for callouts, and 1.73 needs
index 741aac424f11877e1fea39647ef39ba73de46639..b21d2f14176d08011cea79790eb2246d50787ec9 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -57,8 +57,8 @@ all::
 #
 # Define NO_STRLCPY if you don't have strlcpy.
 #
-# Define NO_STRTOUMAX if you don't have strtoumax in the C library.
-# If your compiler also does not support long long or does not have
+# Define NO_STRTOUMAX if you don't have both strtoimax and strtoumax in the
+# C library. If your compiler also does not support long long or does not have
 # strtoull, define NO_STRTOULL.
 #
 # Define NO_SETENV if you don't have setenv in the C library.
@@ -250,6 +250,12 @@ all::
 #   DEFAULT_EDITOR='$GIT_FALLBACK_EDITOR',
 #   DEFAULT_EDITOR='"C:\Program Files\Vim\gvim.exe" --nofork'
 #
+# Define COMPUTE_HEADER_DEPENDENCIES to "yes" if you want dependencies on
+# header files to be automatically computed, to avoid rebuilding objects when
+# an unrelated header file changes.  Define it to "no" to use the hard-coded
+# dependency rules.  The default is "auto", which means to use computed header
+# dependencies if your compiler is detected to support it.
+#
 # Define CHECK_HEADER_DEPENDENCIES to check for problems in the hard-coded
 # dependency rules.
 #
@@ -501,6 +507,7 @@ VCSSVN_LIB=vcs-svn/lib.a
 
 LIB_H += advice.h
 LIB_H += archive.h
+LIB_H += argv-array.h
 LIB_H += attr.h
 LIB_H += blob.h
 LIB_H += builtin.h
@@ -514,9 +521,10 @@ LIB_H += compat/mingw.h
 LIB_H += compat/obstack.h
 LIB_H += compat/win32/pthread.h
 LIB_H += compat/win32/syslog.h
-LIB_H += compat/win32/sys/poll.h
+LIB_H += compat/win32/poll.h
 LIB_H += compat/win32/dirent.h
 LIB_H += connected.h
+LIB_H += convert.h
 LIB_H += csum-file.h
 LIB_H += decorate.h
 LIB_H += delta.h
@@ -584,6 +592,7 @@ LIB_OBJS += alloc.o
 LIB_OBJS += archive.o
 LIB_OBJS += archive-tar.o
 LIB_OBJS += archive-zip.o
+LIB_OBJS += argv-array.o
 LIB_OBJS += attr.o
 LIB_OBJS += base85.o
 LIB_OBJS += bisect.o
@@ -820,6 +829,7 @@ ifeq ($(uname_S),GNU/kFreeBSD)
        NO_STRLCPY = YesPlease
        NO_MKSTEMPS = YesPlease
        HAVE_PATHS_H = YesPlease
+       DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease
 endif
 ifeq ($(uname_S),UnixWare)
        CC = cc
@@ -1085,6 +1095,7 @@ ifeq ($(uname_S),Windows)
        NO_PREAD = YesPlease
        NEEDS_CRYPTO_WITH_SSL = YesPlease
        NO_LIBGEN_H = YesPlease
+       NO_SYS_POLL_H = YesPlease
        NO_SYMLINK_HEAD = YesPlease
        NO_IPV6 = YesPlease
        NO_SETENV = YesPlease
@@ -1123,7 +1134,7 @@ ifeq ($(uname_S),Windows)
        BASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE
        COMPAT_OBJS = compat/msvc.o compat/winansi.o \
                compat/win32/pthread.o compat/win32/syslog.o \
-               compat/win32/sys/poll.o compat/win32/dirent.o
+               compat/win32/poll.o compat/win32/dirent.o
        COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
        BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE -NODEFAULTLIB:MSVCRT.lib
        EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib
@@ -1178,6 +1189,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))
        NO_PREAD = YesPlease
        NEEDS_CRYPTO_WITH_SSL = YesPlease
        NO_LIBGEN_H = YesPlease
+       NO_SYS_POLL_H = YesPlease
        NO_SYMLINK_HEAD = YesPlease
        NO_SETENV = YesPlease
        NO_UNSETENV = YesPlease
@@ -1211,7 +1223,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))
        COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
        COMPAT_OBJS += compat/mingw.o compat/winansi.o \
                compat/win32/pthread.o compat/win32/syslog.o \
-               compat/win32/sys/poll.o compat/win32/dirent.o
+               compat/win32/poll.o compat/win32/dirent.o
        EXTLIBS += -lws2_32
        PTHREAD_LIBS =
        X = .exe
@@ -1240,21 +1252,32 @@ endif
 endif
 
 ifdef CHECK_HEADER_DEPENDENCIES
-COMPUTE_HEADER_DEPENDENCIES =
+COMPUTE_HEADER_DEPENDENCIES = no
 USE_COMPUTED_HEADER_DEPENDENCIES =
-else
+endif
+
 ifndef COMPUTE_HEADER_DEPENDENCIES
+COMPUTE_HEADER_DEPENDENCIES = auto
+endif
+
+ifeq ($(COMPUTE_HEADER_DEPENDENCIES),auto)
 dep_check = $(shell $(CC) $(ALL_CFLAGS) \
        -c -MF /dev/null -MMD -MP -x c /dev/null -o /dev/null 2>&1; \
        echo $$?)
 ifeq ($(dep_check),0)
-COMPUTE_HEADER_DEPENDENCIES=YesPlease
-endif
+override COMPUTE_HEADER_DEPENDENCIES = yes
+else
+override COMPUTE_HEADER_DEPENDENCIES = no
 endif
 endif
 
-ifdef COMPUTE_HEADER_DEPENDENCIES
+ifeq ($(COMPUTE_HEADER_DEPENDENCIES),yes)
 USE_COMPUTED_HEADER_DEPENDENCIES = YesPlease
+else
+ifneq ($(COMPUTE_HEADER_DEPENDENCIES),no)
+$(error please set COMPUTE_HEADER_DEPENDENCIES to yes, no, or auto \
+(not "$(COMPUTE_HEADER_DEPENDENCIES)"))
+endif
 endif
 
 ifdef SANE_TOOL_PATH
@@ -1455,7 +1478,7 @@ ifdef NO_STRLCPY
 endif
 ifdef NO_STRTOUMAX
        COMPAT_CFLAGS += -DNO_STRTOUMAX
-       COMPAT_OBJS += compat/strtoumax.o
+       COMPAT_OBJS += compat/strtoumax.o compat/strtoimax.o
 endif
 ifdef NO_STRTOULL
        COMPAT_CFLAGS += -DNO_STRTOULL
@@ -1901,9 +1924,9 @@ OBJECTS := $(GIT_OBJS) $(XDIFF_OBJS) $(VCSSVN_OBJS)
 dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d)
 dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS))))
 
-ifdef COMPUTE_HEADER_DEPENDENCIES
+ifeq ($(COMPUTE_HEADER_DEPENDENCIES),yes)
 $(dep_dirs):
-       mkdir -p $@
+       @mkdir -p $@
 
 missing_dep_dirs := $(filter-out $(wildcard $(dep_dirs)),$(dep_dirs))
 dep_file = $(dir $@).depend/$(notdir $@).d
@@ -1914,7 +1937,7 @@ Please unset CHECK_HEADER_DEPENDENCIES and try again)
 endif
 endif
 
-ifndef COMPUTE_HEADER_DEPENDENCIES
+ifneq ($(COMPUTE_HEADER_DEPENDENCIES),yes)
 ifndef CHECK_HEADER_DEPENDENCIES
 dep_dirs =
 missing_dep_dirs =
@@ -2004,13 +2027,13 @@ builtin/branch.o builtin/checkout.o builtin/clone.o builtin/reset.o branch.o tra
 builtin/bundle.o bundle.o transport.o: bundle.h
 builtin/bisect--helper.o builtin/rev-list.o bisect.o: bisect.h
 builtin/clone.o builtin/fetch-pack.o transport.o: fetch-pack.h
-builtin/grep.o builtin/pack-objects.o transport-helper.o: thread-utils.h
+builtin/grep.o builtin/pack-objects.o transport-helper.o thread-utils.o: thread-utils.h
 builtin/send-pack.o transport.o: send-pack.h
 builtin/log.o builtin/shortlog.o: shortlog.h
 builtin/prune.o builtin/reflog.o reachable.o: reachable.h
 builtin/commit.o builtin/revert.o wt-status.o: wt-status.h
 builtin/tar-tree.o archive-tar.o: tar.h
-connect.o transport.o http-backend.o: url.h
+connect.o transport.o url.o http-backend.o: url.h
 http-fetch.o http-walker.o remote-curl.o transport.o walker.o: walker.h
 http.o http-walker.o http-push.o http-fetch.o remote-curl.o: http.h url.h
 
@@ -2124,17 +2147,21 @@ po/git.pot: $(LOCALIZED_C)
 
 pot: po/git.pot
 
+FIND_SOURCE_FILES = ( git ls-files '*.[hcS]' 2>/dev/null || \
+                       $(FIND) . \( -name .git -type d -prune \) \
+                               -o \( -name '*.[hcS]' -type f -print \) )
+
 $(ETAGS_TARGET): FORCE
        $(RM) $(ETAGS_TARGET)
-       $(FIND) . -name '*.[hcS]' -print | xargs etags -a -o $(ETAGS_TARGET)
+       $(FIND_SOURCE_FILES) | xargs etags -a -o $(ETAGS_TARGET)
 
 tags: FORCE
        $(RM) tags
-       $(FIND) . -name '*.[hcS]' -print | xargs ctags -a
+       $(FIND_SOURCE_FILES) | xargs ctags -a
 
 cscope:
        $(RM) cscope*
-       $(FIND) . -name '*.[hcS]' -print | xargs cscope -b
+       $(FIND_SOURCE_FILES) | xargs cscope -b
 
 ### Detect prefix changes
 TRACK_CFLAGS = $(CC):$(subst ','\'',$(ALL_CFLAGS)):\
@@ -2289,8 +2316,7 @@ install: all
        $(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)'
        $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
        $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(mergetools_instdir_SQ)'
-       (cd mergetools && $(TAR) cf - .) | \
-       (cd '$(DESTDIR_SQ)$(mergetools_instdir_SQ)' && umask 022 && $(TAR) xof -)
+       $(INSTALL) -m 644 mergetools/* '$(DESTDIR_SQ)$(mergetools_instdir_SQ)'
 ifndef NO_PERL
        $(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
        $(MAKE) -C gitweb install
index 6372427426d80b01b03437fd1a55fd412fb8f75d..7d9276973af3a8f455778d2ce3ced66167f46801 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes/1.7.7.txt
\ No newline at end of file
+Documentation/RelNotes/1.7.8.txt
\ No newline at end of file
index 3fd7f475f1d8ff5efe602a10f02ea3e1c3aa9edd..2ae740a71e6d43ee81afdeddcb53f983f10a8fff 100644 (file)
--- a/archive.c
+++ b/archive.c
@@ -318,7 +318,7 @@ static int parse_archive_args(int argc, const char **argv,
                        "prepend prefix to each pathname in the archive"),
                OPT_STRING('o', "output", &output, "file",
                        "write the archive to this file"),
-               OPT_BOOLEAN(0, "worktree-attributes", &worktree_attributes,
+               OPT_BOOL(0, "worktree-attributes", &worktree_attributes,
                        "read .gitattributes in working directory"),
                OPT__VERBOSE(&verbose, "report archived files on stderr"),
                OPT__COMPR('0', &compression_level, "store only", 0),
@@ -332,7 +332,7 @@ static int parse_archive_args(int argc, const char **argv,
                OPT__COMPR_HIDDEN('8', &compression_level, 8),
                OPT__COMPR('9', &compression_level, "compress better", 9),
                OPT_GROUP(""),
-               OPT_BOOLEAN('l', "list", &list,
+               OPT_BOOL('l', "list", &list,
                        "list supported archive formats"),
                OPT_GROUP(""),
                OPT_STRING(0, "remote", &remote, "repo",
diff --git a/argv-array.c b/argv-array.c
new file mode 100644 (file)
index 0000000..a4e0420
--- /dev/null
@@ -0,0 +1,51 @@
+#include "cache.h"
+#include "argv-array.h"
+#include "strbuf.h"
+
+static const char *empty_argv_storage = NULL;
+const char **empty_argv = &empty_argv_storage;
+
+void argv_array_init(struct argv_array *array)
+{
+       array->argv = empty_argv;
+       array->argc = 0;
+       array->alloc = 0;
+}
+
+static void argv_array_push_nodup(struct argv_array *array, const char *value)
+{
+       if (array->argv == empty_argv)
+               array->argv = NULL;
+
+       ALLOC_GROW(array->argv, array->argc + 2, array->alloc);
+       array->argv[array->argc++] = value;
+       array->argv[array->argc] = NULL;
+}
+
+void argv_array_push(struct argv_array *array, const char *value)
+{
+       argv_array_push_nodup(array, xstrdup(value));
+}
+
+void argv_array_pushf(struct argv_array *array, const char *fmt, ...)
+{
+       va_list ap;
+       struct strbuf v = STRBUF_INIT;
+
+       va_start(ap, fmt);
+       strbuf_vaddf(&v, fmt, ap);
+       va_end(ap);
+
+       argv_array_push_nodup(array, strbuf_detach(&v, NULL));
+}
+
+void argv_array_clear(struct argv_array *array)
+{
+       if (array->argv != empty_argv) {
+               int i;
+               for (i = 0; i < array->argc; i++)
+                       free((char **)array->argv[i]);
+               free(array->argv);
+       }
+       argv_array_init(array);
+}
diff --git a/argv-array.h b/argv-array.h
new file mode 100644 (file)
index 0000000..74dd2b1
--- /dev/null
@@ -0,0 +1,20 @@
+#ifndef ARGV_ARRAY_H
+#define ARGV_ARRAY_H
+
+extern const char **empty_argv;
+
+struct argv_array {
+       const char **argv;
+       int argc;
+       int alloc;
+};
+
+#define ARGV_ARRAY_INIT { empty_argv, 0, 0 }
+
+void argv_array_init(struct argv_array *);
+void argv_array_push(struct argv_array *, const char *);
+__attribute__((format (printf,2,3)))
+void argv_array_pushf(struct argv_array *, const char *fmt, ...);
+void argv_array_clear(struct argv_array *);
+
+#endif /* ARGV_ARRAY_H */
diff --git a/attr.c b/attr.c
index 33cb4e4d113cbb3816ba824cb06bf494a4bd9bc3..76b079f0f530e1372b2866f40cce21ec5266394c 100644 (file)
--- a/attr.c
+++ b/attr.c
@@ -11,6 +11,7 @@
 #include "cache.h"
 #include "exec_cmd.h"
 #include "attr.h"
+#include "dir.h"
 
 const char git_attr__true[] = "(builtin)true";
 const char git_attr__false[] = "\0(builtin)false";
@@ -20,8 +21,6 @@ static const char git_attr__unknown[] = "(builtin)unknown";
 #define ATTR__UNSET NULL
 #define ATTR__UNKNOWN git_attr__unknown
 
-static const char *attributes_file;
-
 /* This is a randomly chosen prime. */
 #define HASHSIZE 257
 
@@ -494,14 +493,6 @@ static int git_attr_system(void)
        return !git_env_bool("GIT_ATTR_NOSYSTEM", 0);
 }
 
-static int git_attr_config(const char *var, const char *value, void *dummy)
-{
-       if (!strcmp(var, "core.attributesfile"))
-               return git_config_pathname(&attributes_file, var, value);
-
-       return 0;
-}
-
 static void bootstrap_attr_stack(void)
 {
        if (!attr_stack) {
@@ -521,9 +512,8 @@ static void bootstrap_attr_stack(void)
                        }
                }
 
-               git_config(git_attr_config, NULL);
-               if (attributes_file) {
-                       elem = read_attr_from_file(attributes_file, 1);
+               if (git_attributes_file) {
+                       elem = read_attr_from_file(git_attributes_file, 1);
                        if (elem) {
                                elem->origin = NULL;
                                elem->prev = attr_stack;
@@ -533,7 +523,7 @@ static void bootstrap_attr_stack(void)
 
                if (!is_bare_repository() || direction == GIT_ATTR_INDEX) {
                        elem = read_attr(GITATTRIBUTES_FILE, 1);
-                       elem->origin = strdup("");
+                       elem->origin = xstrdup("");
                        elem->prev = attr_stack;
                        attr_stack = elem;
                        debug_push(elem);
@@ -552,7 +542,6 @@ static void prepare_attr_stack(const char *path)
 {
        struct attr_stack *elem, *info;
        int dirlen, len;
-       struct strbuf pathbuf;
        const char *cp;
 
        cp = strrchr(path, '/');
@@ -561,8 +550,6 @@ static void prepare_attr_stack(const char *path)
        else
                dirlen = cp - path;
 
-       strbuf_init(&pathbuf, dirlen+2+strlen(GITATTRIBUTES_FILE));
-
        /*
         * At the bottom of the attribute stack is the built-in
         * set of attribute definitions, followed by the contents
@@ -607,27 +594,28 @@ static void prepare_attr_stack(const char *path)
         * Read from parent directories and push them down
         */
        if (!is_bare_repository() || direction == GIT_ATTR_INDEX) {
-               while (1) {
-                       char *cp;
+               struct strbuf pathbuf = STRBUF_INIT;
 
+               while (1) {
                        len = strlen(attr_stack->origin);
                        if (dirlen <= len)
                                break;
-                       strbuf_reset(&pathbuf);
-                       strbuf_add(&pathbuf, path, dirlen);
+                       cp = memchr(path + len + 1, '/', dirlen - len - 1);
+                       if (!cp)
+                               cp = path + dirlen;
+                       strbuf_add(&pathbuf, path, cp - path);
                        strbuf_addch(&pathbuf, '/');
-                       cp = strchr(pathbuf.buf + len + 1, '/');
-                       strcpy(cp + 1, GITATTRIBUTES_FILE);
+                       strbuf_addstr(&pathbuf, GITATTRIBUTES_FILE);
                        elem = read_attr(pathbuf.buf, 0);
-                       *cp = '\0';
-                       elem->origin = strdup(pathbuf.buf);
+                       strbuf_setlen(&pathbuf, cp - path);
+                       elem->origin = strbuf_detach(&pathbuf, NULL);
                        elem->prev = attr_stack;
                        attr_stack = elem;
                        debug_push(elem);
                }
-       }
 
-       strbuf_release(&pathbuf);
+               strbuf_release(&pathbuf);
+       }
 
        /*
         * Finally push the "info" one at the top of the stack.
@@ -644,7 +632,7 @@ static int path_matches(const char *pathname, int pathlen,
                /* match basename */
                const char *basename = strrchr(pathname, '/');
                basename = basename ? basename + 1 : pathname;
-               return (fnmatch(pattern, basename, 0) == 0);
+               return (fnmatch_icase(pattern, basename, 0) == 0);
        }
        /*
         * match with FNM_PATHNAME; the pattern has base implicitly
@@ -658,7 +646,7 @@ static int path_matches(const char *pathname, int pathlen,
                return 0;
        if (baselen != 0)
                baselen++;
-       return fnmatch(pattern, pathname + baselen, FNM_PATHNAME) == 0;
+       return fnmatch_icase(pattern, pathname + baselen, FNM_PATHNAME) == 0;
 }
 
 static int macroexpand_one(int attr_nr, int rem);
index c7b7d7913dc444926b0260211e8bc3aa75eeffdb..6e186e29cc4a6a74944798e8c4248219e9c5997f 100644 (file)
--- a/bisect.c
+++ b/bisect.c
 #include "log-tree.h"
 #include "bisect.h"
 #include "sha1-array.h"
+#include "argv-array.h"
 
 static struct sha1_array good_revs;
 static struct sha1_array skipped_revs;
 
 static const unsigned char *current_bad_sha1;
 
-struct argv_array {
-       const char **argv;
-       int argv_nr;
-       int argv_alloc;
-};
-
 static const char *argv_checkout[] = {"checkout", "-q", NULL, "--", NULL};
 static const char *argv_show_branch[] = {"show-branch", NULL, NULL};
 static const char *argv_update_ref[] = {"update-ref", "--no-deref", "BISECT_HEAD", NULL, NULL};
@@ -405,21 +400,6 @@ struct commit_list *find_bisection(struct commit_list *list,
        return best;
 }
 
-static void argv_array_push(struct argv_array *array, const char *string)
-{
-       ALLOC_GROW(array->argv, array->argv_nr + 1, array->argv_alloc);
-       array->argv[array->argv_nr++] = string;
-}
-
-static void argv_array_push_sha1(struct argv_array *array,
-                                const unsigned char *sha1,
-                                const char *format)
-{
-       struct strbuf buf = STRBUF_INIT;
-       strbuf_addf(&buf, format, sha1_to_hex(sha1));
-       argv_array_push(array, strbuf_detach(&buf, NULL));
-}
-
 static int register_ref(const char *refname, const unsigned char *sha1,
                        int flags, void *cb_data)
 {
@@ -449,16 +429,10 @@ static void read_bisect_paths(struct argv_array *array)
                die_errno("Could not open file '%s'", filename);
 
        while (strbuf_getline(&str, fp, '\n') != EOF) {
-               char *quoted;
-               int res;
-
                strbuf_trim(&str);
-               quoted = strbuf_detach(&str, NULL);
-               res = sq_dequote_to_argv(quoted, &array->argv,
-                                        &array->argv_nr, &array->argv_alloc);
-               if (res)
+               if (sq_dequote_to_argv_array(str.buf, array))
                        die("Badly quoted content in file '%s': %s",
-                           filename, quoted);
+                           filename, str.buf);
        }
 
        strbuf_release(&str);
@@ -623,7 +597,7 @@ static void bisect_rev_setup(struct rev_info *revs, const char *prefix,
                             const char *bad_format, const char *good_format,
                             int read_paths)
 {
-       struct argv_array rev_argv = { NULL, 0, 0 };
+       struct argv_array rev_argv = ARGV_ARRAY_INIT;
        int i;
 
        init_revisions(revs, prefix);
@@ -631,17 +605,17 @@ static void bisect_rev_setup(struct rev_info *revs, const char *prefix,
        revs->commit_format = CMIT_FMT_UNSPECIFIED;
 
        /* rev_argv.argv[0] will be ignored by setup_revisions */
-       argv_array_push(&rev_argv, xstrdup("bisect_rev_setup"));
-       argv_array_push_sha1(&rev_argv, current_bad_sha1, bad_format);
+       argv_array_push(&rev_argv, "bisect_rev_setup");
+       argv_array_pushf(&rev_argv, bad_format, sha1_to_hex(current_bad_sha1));
        for (i = 0; i < good_revs.nr; i++)
-               argv_array_push_sha1(&rev_argv, good_revs.sha1[i],
-                                    good_format);
-       argv_array_push(&rev_argv, xstrdup("--"));
+               argv_array_pushf(&rev_argv, good_format,
+                                sha1_to_hex(good_revs.sha1[i]));
+       argv_array_push(&rev_argv, "--");
        if (read_paths)
                read_bisect_paths(&rev_argv);
-       argv_array_push(&rev_argv, NULL);
 
-       setup_revisions(rev_argv.argv_nr, rev_argv.argv, revs, NULL);
+       setup_revisions(rev_argv.argc, rev_argv.argv, revs, NULL);
+       /* XXX leak rev_argv, as "revs" may still be pointing to it */
 }
 
 static void bisect_common(struct rev_info *revs)
@@ -826,25 +800,25 @@ static int check_ancestors(const char *prefix)
 {
        struct rev_info revs;
        struct object_array pending_copy;
-       int i, res;
+       int res;
 
        bisect_rev_setup(&revs, prefix, "^%s", "%s", 0);
 
        /* Save pending objects, so they can be cleaned up later. */
-       memset(&pending_copy, 0, sizeof(pending_copy));
-       for (i = 0; i < revs.pending.nr; i++)
-               add_object_array(revs.pending.objects[i].item,
-                                revs.pending.objects[i].name,
-                                &pending_copy);
+       pending_copy = revs.pending;
+       revs.leak_pending = 1;
 
+       /*
+        * bisect_common calls prepare_revision_walk right away, which
+        * (together with .leak_pending = 1) makes us the sole owner of
+        * the list of pending objects.
+        */
        bisect_common(&revs);
        res = (revs.commits != NULL);
 
        /* Clean up objects used, as they will be reused. */
-       for (i = 0; i < pending_copy.nr; i++) {
-               struct object *o = pending_copy.objects[i].item;
-               clear_commit_marks((struct commit *)o, ALL_REV_FLAGS);
-       }
+       clear_commit_marks_for_object_array(&pending_copy, ALL_REV_FLAGS);
+       free(pending_copy.objects);
 
        return res;
 }
index d8098762f62a9dfb991f64702d34047182cfa951..025a97be0281b9fd3ccff2b36c03495b005ac4fe 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -241,6 +241,7 @@ void create_branch(const char *head,
 void remove_branch_state(void)
 {
        unlink(git_path("CHERRY_PICK_HEAD"));
+       unlink(git_path("REVERT_HEAD"));
        unlink(git_path("MERGE_HEAD"));
        unlink(git_path("MERGE_RR"));
        unlink(git_path("MERGE_MSG"));
index f2edc52818e817e83717f992a6bbd66dad4ef24d..b3b59db534ff0763c822ae9da571d5643a8b709a 100644 (file)
@@ -250,9 +250,6 @@ static int fuzzy_matchlines(const char *s1, size_t n1,
        const char *last2 = s2 + n2 - 1;
        int result = 0;
 
-       if (n1 < 0 || n2 < 0)
-               return 0;
-
        /* ignore line endings */
        while ((*last1 == '\r') || (*last1 == '\n'))
                last1--;
@@ -1407,6 +1404,9 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc
                                            "%d leading pathname components (line %d)" , p_value, linenr);
                                patch->old_name = patch->new_name = patch->def_name;
                        }
+                       if (!patch->is_delete && !patch->new_name)
+                               die("git diff header lacks filename information "
+                                   "(line %d)", linenr);
                        patch->is_toplevel_relative = 1;
                        *hdrsize = git_hdr_len;
                        return offset;
@@ -2447,6 +2447,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,
        char *old, *oldlines;
        struct strbuf newlines;
        int new_blank_lines_at_end = 0;
+       int found_new_blank_lines_at_end = 0;
+       int hunk_linenr = frag->linenr;
        unsigned long leading, trailing;
        int pos, applied_pos;
        struct image preimage;
@@ -2540,14 +2542,18 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,
                                error("invalid start of line: '%c'", first);
                        return -1;
                }
-               if (added_blank_line)
+               if (added_blank_line) {
+                       if (!new_blank_lines_at_end)
+                               found_new_blank_lines_at_end = hunk_linenr;
                        new_blank_lines_at_end++;
+               }
                else if (is_blank_context)
                        ;
                else
                        new_blank_lines_at_end = 0;
                patch += len;
                size -= len;
+               hunk_linenr++;
        }
        if (inaccurate_eof &&
            old > oldlines && old[-1] == '\n' &&
@@ -2629,7 +2635,8 @@ static int apply_one_fragment(struct image *img, struct fragment *frag,
                    preimage.nr + applied_pos >= img->nr &&
                    (ws_rule & WS_BLANK_AT_EOF) &&
                    ws_error_action != nowarn_ws_error) {
-                       record_ws_error(WS_BLANK_AT_EOF, "+", 1, frag->linenr);
+                       record_ws_error(WS_BLANK_AT_EOF, "+", 1,
+                                       found_new_blank_lines_at_end);
                        if (ws_error_action == correct_ws_error) {
                                while (new_blank_lines_at_end--)
                                        remove_last_line(&postimage);
@@ -3831,7 +3838,6 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
        int i;
        int errs = 0;
        int is_not_gitdir = !startup_info->have_repository;
-       int binary;
        int force_apply = 0;
 
        const char *whitespace_option = NULL;
@@ -3850,12 +3856,8 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
                        "ignore additions made by the patch"),
                OPT_BOOLEAN(0, "stat", &diffstat,
                        "instead of applying the patch, output diffstat for the input"),
-               { OPTION_BOOLEAN, 0, "allow-binary-replacement", &binary,
-                 NULL, "old option, now no-op",
-                 PARSE_OPT_HIDDEN | PARSE_OPT_NOARG },
-               { OPTION_BOOLEAN, 0, "binary", &binary,
-                 NULL, "old option, now no-op",
-                 PARSE_OPT_HIDDEN | PARSE_OPT_NOARG },
+               OPT_NOOP_NOARG(0, "allow-binary-replacement"),
+               OPT_NOOP_NOARG(0, "binary"),
                OPT_BOOLEAN(0, "numstat", &numstat,
                        "shows number of added and deleted lines in decimal notation"),
                OPT_BOOLEAN(0, "summary", &summary,
index 883c0092ad43d602136e68d2afde8e319a904676..931956def986bbdf5b77e163f1b18c961e7be09c 100644 (file)
@@ -61,6 +61,8 @@ static int run_remote_archiver(int argc, const char **argv,
        if (strcmp(buf, "ACK")) {
                if (len > 5 && !prefixcmp(buf, "NACK "))
                        die(_("git archive: NACK %s"), buf + 5);
+               if (len > 4 && !prefixcmp(buf, "ERR "))
+                       die(_("remote error: %s"), buf + 4);
                die(_("git archive: protocol error"));
        }
 
index 26a5d424b8ceb0fd403a492e46e3637fd35068ba..80febbe420db1c75bbcf7eda6a733e7e66549790 100644 (file)
@@ -2096,6 +2096,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
        if (!contents_from || strcmp("-", contents_from)) {
                struct stat st;
                const char *read_from;
+               char *buf_ptr;
                unsigned long buf_len;
 
                if (contents_from) {
@@ -2113,8 +2114,8 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt,
                switch (st.st_mode & S_IFMT) {
                case S_IFREG:
                        if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV) &&
-                           textconv_object(read_from, mode, null_sha1, &buf.buf, &buf_len))
-                               buf.len = buf_len;
+                           textconv_object(read_from, mode, null_sha1, &buf_ptr, &buf_len))
+                               strbuf_attach(&buf, buf_ptr, buf_len, buf_len + 1);
                        else if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size)
                                die_errno("cannot open or read '%s'", read_from);
                        break;
index f49596f826228e22d354194ddbb73ec100d8728e..55cad766c7e3d284b1361b1beca8c5d51de96083 100644 (file)
@@ -260,9 +260,22 @@ static char *resolve_symref(const char *src, const char *prefix)
 
 struct append_ref_cb {
        struct ref_list *ref_list;
+       const char **pattern;
        int ret;
 };
 
+static int match_patterns(const char **pattern, const char *refname)
+{
+       if (!*pattern)
+               return 1; /* no pattern always matches */
+       while (*pattern) {
+               if (!fnmatch(*pattern, refname, 0))
+                       return 1;
+               pattern++;
+       }
+       return 0;
+}
+
 static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
 {
        struct append_ref_cb *cb = (struct append_ref_cb *)(cb_data);
@@ -297,6 +310,9 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
        if ((kind & ref_list->kinds) == 0)
                return 0;
 
+       if (!match_patterns(cb->pattern, refname))
+               return 0;
+
        commit = NULL;
        if (ref_list->verbose || ref_list->with_commit || merge_filter != NO_FILTER) {
                commit = lookup_commit_reference_gently(sha1, 1);
@@ -492,7 +508,7 @@ static void show_detached(struct ref_list *ref_list)
        }
 }
 
-static int print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit)
+static int print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit, const char **pattern)
 {
        int i;
        struct append_ref_cb cb;
@@ -506,6 +522,7 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru
        if (merge_filter != NO_FILTER)
                init_revisions(&ref_list.revs, NULL);
        cb.ref_list = &ref_list;
+       cb.pattern = pattern;
        cb.ret = 0;
        for_each_rawref(append_ref, &cb);
        if (merge_filter != NO_FILTER) {
@@ -523,7 +540,7 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru
        qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp);
 
        detached = (detached && (kinds & REF_LOCAL_BRANCH));
-       if (detached)
+       if (detached && match_patterns(pattern, "HEAD"))
                show_detached(&ref_list);
 
        for (i = 0; i < ref_list.index; i++) {
@@ -608,7 +625,7 @@ static int opt_parse_merge_filter(const struct option *opt, const char *arg, int
 
 int cmd_branch(int argc, const char **argv, const char *prefix)
 {
-       int delete = 0, rename = 0, force_create = 0;
+       int delete = 0, rename = 0, force_create = 0, list = 0;
        int verbose = 0, abbrev = -1, detached = 0;
        int reflog = 0;
        enum branch_track track;
@@ -624,7 +641,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                OPT_SET_INT( 0, "set-upstream",  &track, "change upstream info",
                        BRANCH_TRACK_OVERRIDE),
                OPT__COLOR(&branch_use_color, "use colored output"),
-               OPT_SET_INT('r', NULL,     &kinds, "act on remote-tracking branches",
+               OPT_SET_INT('r', "remotes",     &kinds, "act on remote-tracking branches",
                        REF_REMOTE_BRANCH),
                {
                        OPTION_CALLBACK, 0, "contains", &with_commit, "commit",
@@ -641,13 +658,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                OPT__ABBREV(&abbrev),
 
                OPT_GROUP("Specific git-branch actions:"),
-               OPT_SET_INT('a', NULL, &kinds, "list both remote-tracking and local branches",
+               OPT_SET_INT('a', "all", &kinds, "list both remote-tracking and local branches",
                        REF_REMOTE_BRANCH | REF_LOCAL_BRANCH),
-               OPT_BIT('d', NULL, &delete, "delete fully merged branch", 1),
+               OPT_BIT('d', "delete", &delete, "delete fully merged branch", 1),
                OPT_BIT('D', NULL, &delete, "delete branch (even if not merged)", 2),
-               OPT_BIT('m', NULL, &rename, "move/rename a branch and its reflog", 1),
+               OPT_BIT('m', "move", &rename, "move/rename a branch and its reflog", 1),
                OPT_BIT('M', NULL, &rename, "move/rename a branch, even if target exists", 2),
-               OPT_BOOLEAN('l', NULL, &reflog, "create the branch's reflog"),
+               OPT_BOOLEAN(0, "list", &list, "list branch names"),
+               OPT_BOOLEAN('l', "create-reflog", &reflog, "create the branch's reflog"),
                OPT__FORCE(&force_create, "force creation (when already exists)"),
                {
                        OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref,
@@ -686,7 +704,11 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 
        argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
                             0);
-       if (!!delete + !!rename + !!force_create > 1)
+
+       if (!delete && !rename && argc == 0)
+               list = 1;
+
+       if (!!delete + !!rename + !!force_create + !!list > 1)
                usage_with_options(builtin_branch_usage, options);
 
        if (abbrev == -1)
@@ -694,13 +716,17 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 
        if (delete)
                return delete_branches(argc, argv, delete > 1, kinds);
-       else if (argc == 0)
-               return print_ref_list(kinds, detached, verbose, abbrev, with_commit);
-       else if (rename && (argc == 1))
-               rename_branch(head, argv[0], rename > 1);
-       else if (rename && (argc == 2))
-               rename_branch(argv[0], argv[1], rename > 1);
-       else if (argc <= 2) {
+       else if (list)
+               return print_ref_list(kinds, detached, verbose, abbrev,
+                                     with_commit, argv);
+       else if (rename) {
+               if (argc == 1)
+                       rename_branch(head, argv[0], rename > 1);
+               else if (argc == 2)
+                       rename_branch(argv[0], argv[1], rename > 1);
+               else
+                       usage_with_options(builtin_branch_usage, options);
+       } else if (argc > 0 && argc <= 2) {
                if (kinds != REF_LOCAL_BRANCH)
                        die(_("-a and -r options to 'git branch' do not make sense with a branch name"));
                create_branch(head, argv[0], (argc == 2) ? argv[1] : head,
index 708988a0e1a318fb737624f489d01e0686cd0611..44c421eb0fe9cd8947d6666d15d790bc241ee7f3 100644 (file)
@@ -5,6 +5,7 @@
 #include "parse-options.h"
 
 static int all_attrs;
+static int cached_attrs;
 static int stdin_paths;
 static const char * const check_attr_usage[] = {
 "git check-attr [-a | --all | attr...] [--] pathname...",
@@ -16,6 +17,7 @@ static int null_term_line;
 
 static const struct option check_attr_options[] = {
        OPT_BOOLEAN('a', "all", &all_attrs, "report all attributes set on file"),
+       OPT_BOOLEAN(0,  "cached", &cached_attrs, "use .gitattributes only from the index"),
        OPT_BOOLEAN(0 , "stdin", &stdin_paths, "read file names from stdin"),
        OPT_BOOLEAN('z', NULL, &null_term_line,
                "input paths are terminated by a null character"),
@@ -92,6 +94,8 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix)
        struct git_attr_check *check;
        int cnt, i, doubledash, filei;
 
+       git_config(git_default_config, NULL);
+
        argc = parse_options(argc, argv, prefix, check_attr_options,
                             check_attr_usage, PARSE_OPT_KEEP_DASHDASH);
 
@@ -99,6 +103,9 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix)
                die("invalid cache");
        }
 
+       if (cached_attrs)
+               git_attr_set_direction(GIT_ATTR_INDEX, NULL);
+
        doubledash = -1;
        for (i = 0; doubledash < 0 && i < argc; i++) {
                if (!strcmp(argv[i], "--"))
index 0723cf245e52e6c0ab99cbe92c74010f9f7d7167..28a7320271a9555356170bfdb06ffb4b07bc9b92 100644 (file)
@@ -8,29 +8,32 @@
 #include "strbuf.h"
 
 static const char builtin_check_ref_format_usage[] =
-"git check-ref-format [--print] <refname>\n"
+"git check-ref-format [--normalize] [options] <refname>\n"
 "   or: git check-ref-format --branch <branchname-shorthand>";
 
 /*
- * Remove leading slashes and replace each run of adjacent slashes in
- * src with a single slash, and write the result to dst.
+ * Return a copy of refname but with leading slashes removed and runs
+ * of adjacent slashes replaced with single slashes.
  *
  * This function is similar to normalize_path_copy(), but stripped down
  * to meet check_ref_format's simpler needs.
  */
-static void collapse_slashes(char *dst, const char *src)
+static char *collapse_slashes(const char *refname)
 {
+       char *ret = xmalloc(strlen(refname) + 1);
        char ch;
        char prev = '/';
+       char *cp = ret;
 
-       while ((ch = *src++) != '\0') {
+       while ((ch = *refname++) != '\0') {
                if (prev == '/' && ch == prev)
                        continue;
 
-               *dst++ = ch;
+               *cp++ = ch;
                prev = ch;
        }
-       *dst = '\0';
+       *cp = '\0';
+       return ret;
 }
 
 static int check_ref_format_branch(const char *arg)
@@ -45,27 +48,41 @@ static int check_ref_format_branch(const char *arg)
        return 0;
 }
 
-static int check_ref_format_print(const char *arg)
-{
-       char *refname = xmalloc(strlen(arg) + 1);
-
-       if (check_ref_format(arg))
-               return 1;
-       collapse_slashes(refname, arg);
-       printf("%s\n", refname);
-       return 0;
-}
-
 int cmd_check_ref_format(int argc, const char **argv, const char *prefix)
 {
+       int i;
+       int normalize = 0;
+       int flags = 0;
+       const char *refname;
+
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage(builtin_check_ref_format_usage);
 
        if (argc == 3 && !strcmp(argv[1], "--branch"))
                return check_ref_format_branch(argv[2]);
-       if (argc == 3 && !strcmp(argv[1], "--print"))
-               return check_ref_format_print(argv[2]);
-       if (argc != 2)
+
+       for (i = 1; i < argc && argv[i][0] == '-'; i++) {
+               if (!strcmp(argv[i], "--normalize") || !strcmp(argv[i], "--print"))
+                       normalize = 1;
+               else if (!strcmp(argv[i], "--allow-onelevel"))
+                       flags |= REFNAME_ALLOW_ONELEVEL;
+               else if (!strcmp(argv[i], "--no-allow-onelevel"))
+                       flags &= ~REFNAME_ALLOW_ONELEVEL;
+               else if (!strcmp(argv[i], "--refspec-pattern"))
+                       flags |= REFNAME_REFSPEC_PATTERN;
+               else
+                       usage(builtin_check_ref_format_usage);
+       }
+       if (! (i == argc - 1))
                usage(builtin_check_ref_format_usage);
-       return !!check_ref_format(argv[1]);
+
+       refname = argv[i];
+       if (normalize)
+               refname = collapse_slashes(refname);
+       if (check_refname_format(refname, flags))
+               return 1;
+       if (normalize)
+               printf("%s\n", refname);
+
+       return 0;
 }
index 5e356a6c6178e08b90d992b0eae36048b5d81513..2a8077242500d54ac50d5829a86b14803fc69126 100644 (file)
@@ -19,6 +19,7 @@
 #include "ll-merge.h"
 #include "resolve-undo.h"
 #include "submodule.h"
+#include "argv-array.h"
 
 static const char * const checkout_usage[] = {
        "git checkout [options] <branch>",
@@ -71,7 +72,7 @@ static int update_some(const unsigned char *sha1, const char *base, int baselen,
        hashcpy(ce->sha1, sha1);
        memcpy(ce->name, base, baselen);
        memcpy(ce->name + baselen, pathname, len - baselen);
-       ce->ce_flags = create_ce_flags(len, 0);
+       ce->ce_flags = create_ce_flags(len, 0) | CE_UPDATE;
        ce->ce_mode = create_ce_mode(mode);
        add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
        return 0;
@@ -228,6 +229,8 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
 
        for (pos = 0; pos < active_nr; pos++) {
                struct cache_entry *ce = active_cache[pos];
+               if (source_tree && !(ce->ce_flags & CE_UPDATE))
+                       continue;
                match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, ps_matched);
        }
 
@@ -266,6 +269,8 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
        state.refresh_cache = 1;
        for (pos = 0; pos < active_nr; pos++) {
                struct cache_entry *ce = active_cache[pos];
+               if (source_tree && !(ce->ce_flags & CE_UPDATE))
+                       continue;
                if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) {
                        if (!ce_stage(ce)) {
                                errs |= checkout_entry(ce, &state, NULL);
@@ -588,35 +593,11 @@ static void update_refs_for_switch(struct checkout_opts *opts,
                report_tracking(new);
 }
 
-struct rev_list_args {
-       int argc;
-       int alloc;
-       const char **argv;
-};
-
-static void add_one_rev_list_arg(struct rev_list_args *args, const char *s)
-{
-       ALLOC_GROW(args->argv, args->argc + 1, args->alloc);
-       args->argv[args->argc++] = s;
-}
-
-static int add_one_ref_to_rev_list_arg(const char *refname,
-                                      const unsigned char *sha1,
-                                      int flags,
-                                      void *cb_data)
-{
-       add_one_rev_list_arg(cb_data, refname);
-       return 0;
-}
-
-static int clear_commit_marks_from_one_ref(const char *refname,
-                                     const unsigned char *sha1,
-                                     int flags,
-                                     void *cb_data)
+static int add_pending_uninteresting_ref(const char *refname,
+                                        const unsigned char *sha1,
+                                        int flags, void *cb_data)
 {
-       struct commit *commit = lookup_commit_reference_gently(sha1, 1);
-       if (commit)
-               clear_commit_marks(commit, -1);
+       add_pending_sha1(cb_data, refname, sha1, flags | UNINTERESTING);
        return 0;
 }
 
@@ -685,19 +666,21 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs)
  */
 static void orphaned_commit_warning(struct commit *commit)
 {
-       struct rev_list_args args = { 0, 0, NULL };
        struct rev_info revs;
-
-       add_one_rev_list_arg(&args, "(internal)");
-       add_one_rev_list_arg(&args, sha1_to_hex(commit->object.sha1));
-       add_one_rev_list_arg(&args, "--not");
-       for_each_ref(add_one_ref_to_rev_list_arg, &args);
-       add_one_rev_list_arg(&args, "--");
-       add_one_rev_list_arg(&args, NULL);
+       struct object *object = &commit->object;
+       struct object_array refs;
 
        init_revisions(&revs, NULL);
-       if (setup_revisions(args.argc - 1, args.argv, &revs, NULL) != 1)
-               die(_("internal error: only -- alone should have been left"));
+       setup_revisions(0, NULL, &revs, NULL);
+
+       object->flags &= ~UNINTERESTING;
+       add_pending_object(&revs, object, sha1_to_hex(object->sha1));
+
+       for_each_ref(add_pending_uninteresting_ref, &revs);
+
+       refs = revs.pending;
+       revs.leak_pending = 1;
+
        if (prepare_revision_walk(&revs))
                die(_("internal error in revision walk"));
        if (!(commit->object.flags & UNINTERESTING))
@@ -705,8 +688,8 @@ static void orphaned_commit_warning(struct commit *commit)
        else
                describe_detached_head(_("Previous HEAD position was"), commit);
 
-       clear_commit_marks(commit, -1);
-       for_each_ref(clear_commit_marks_from_one_ref, NULL);
+       clear_commit_marks_for_object_array(&refs, ALL_REV_FLAGS);
+       free(refs.objects);
 }
 
 static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
@@ -882,7 +865,7 @@ static int parse_branchname_arg(int argc, const char **argv,
        new->name = arg;
        setup_branch_path(new);
 
-       if (check_ref_format(new->path) == CHECK_REF_FORMAT_OK &&
+       if (!check_refname_format(new->path, 0) &&
            resolve_ref(new->path, branch_rev, 1, NULL))
                hashcpy(rev, branch_rev);
        else
index 488f48e9a571fa9b958cd95b92808b44f4541982..efe8b6cce5a9f2ae40c6f69755debecfb6b501ca 100644 (file)
@@ -577,9 +577,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 
        if (0 <= option_verbosity) {
                if (option_bare)
-                       printf(_("Cloning into bare repository %s...\n"), dir);
+                       printf(_("Cloning into bare repository '%s'...\n"), dir);
                else
-                       printf(_("Cloning into %s...\n"), dir);
+                       printf(_("Cloning into '%s'...\n"), dir);
        }
        init_db(option_template, INIT_DB_QUIET);
        write_config(&option_config);
index cbc9613ec661bc2cef8274cd66efb06b9cab55b6..8f2bebecf313a0e4b742f9327ec178c7e3547dd2 100644 (file)
@@ -255,8 +255,9 @@ static int list_paths(struct string_list *list, const char *with_tree,
        m = xcalloc(1, i);
 
        if (with_tree) {
-               const char *max_prefix = pathspec_prefix(prefix, pattern);
-               overlay_tree_on_cache(with_tree, max_prefix);
+               char *max_prefix = common_prefix(pattern);
+               overlay_tree_on_cache(with_tree, max_prefix ? max_prefix : prefix);
+               free(max_prefix);
        }
 
        for (i = 0; i < active_nr; i++) {
@@ -1392,7 +1393,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
        if (get_sha1("HEAD", sha1))
                current_head = NULL;
        else {
-               current_head = lookup_commit(sha1);
+               current_head = lookup_commit_or_die(sha1, "HEAD");
                if (!current_head || parse_commit(current_head))
                        die(_("could not parse HEAD commit"));
        }
@@ -1424,6 +1425,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                        pptr = &commit_list_insert(c->item, pptr)->next;
        } else if (whence == FROM_MERGE) {
                struct strbuf m = STRBUF_INIT;
+               struct commit *commit;
                FILE *fp;
 
                if (!reflog_msg)
@@ -1437,7 +1439,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                        unsigned char sha1[20];
                        if (get_sha1_hex(m.buf, sha1) < 0)
                                die(_("Corrupt MERGE_HEAD file (%s)"), m.buf);
-                       pptr = &commit_list_insert(lookup_commit(sha1), pptr)->next;
+                       commit = lookup_commit_or_die(sha1, "MERGE_HEAD");
+                       pptr = &commit_list_insert(commit, pptr)->next;
                }
                fclose(fp);
                strbuf_release(&m);
@@ -1511,6 +1514,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
        }
 
        unlink(git_path("CHERRY_PICK_HEAD"));
+       unlink(git_path("REVERT_HEAD"));
        unlink(git_path("MERGE_HEAD"));
        unlink(git_path("MERGE_MSG"));
        unlink(git_path("MERGE_MODE"));
index 0b4ecac855dce9b70878de4d15f4d317254d9ef3..0315ad76f881aab9a64084f8d2fdd2019907e4cb 100644 (file)
@@ -99,6 +99,7 @@ static int show_config(const char *key_, const char *value_, void *cb)
        const char *vptr = value;
        int must_free_vptr = 0;
        int dup_error = 0;
+       int must_print_delim = 0;
 
        if (!use_key_regexp && strcmp(key_, key))
                return 0;
@@ -109,10 +110,8 @@ static int show_config(const char *key_, const char *value_, void *cb)
                return 0;
 
        if (show_keys) {
-               if (value_)
-                       printf("%s%c", key_, key_delim);
-               else
-                       printf("%s", key_);
+               printf("%s", key_);
+               must_print_delim = 1;
        }
        if (seen && !do_all)
                dup_error = 1;
@@ -130,16 +129,23 @@ static int show_config(const char *key_, const char *value_, void *cb)
        } else if (types == TYPE_PATH) {
                git_config_pathname(&vptr, key_, value_);
                must_free_vptr = 1;
+       } else if (value_) {
+               vptr = value_;
+       } else {
+               /* Just show the key name */
+               vptr = "";
+               must_print_delim = 0;
        }
-       else
-               vptr = value_?value_:"";
        seen++;
        if (dup_error) {
                error("More than one value for the key %s: %s",
                                key_, vptr);
        }
-       else
+       else {
+               if (must_print_delim)
+                       printf("%c", key_delim);
                printf("%s%c", vptr, term);
+       }
        if (must_free_vptr)
                /* If vptr must be freed, it's a pointer to a
                 * dynamically allocated buffer, it's safe to cast to
index 1118689fb246b864ce758039543327c4304cdaa4..0fe638fc45c780e53901b8be22d3b4d715be3c30 100644 (file)
@@ -182,7 +182,7 @@ static int builtin_diff_combined(struct rev_info *revs,
                hashcpy((unsigned char *)(parent + i), ent[i].item->sha1);
        diff_tree_combined(parent[0], parent + 1, ents - 1,
                           revs->dense_combined_merges, revs);
-       free(parent);
+       free((void *)parent);
        return 0;
 }
 
index c8bf9b85b07690dce8af5e093e02994ae5c510be..c6bc8eb0aa6f5a6bc35c69e7893118a17813db7d 100644 (file)
@@ -546,7 +546,7 @@ static void filter_refs(struct ref **refs, int nr_match, char **match)
        for (ref = *refs; ref; ref = next) {
                next = ref->next;
                if (!memcmp(ref->name, "refs/", 5) &&
-                   check_ref_format(ref->name + 5))
+                   check_refname_format(ref->name + 5, 0))
                        ; /* trash */
                else if (args.fetch_all &&
                         (!args.depth || prefixcmp(ref->name, "refs/tags/") )) {
index 7a4e41cca75b87d3e5a7c4690658d9879777e965..91731b909aeb22bf8d4e366b8b92281ac0f9ac0c 100644 (file)
@@ -379,8 +379,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
                url = xstrdup("foreign");
 
        rm = ref_map;
-       if (check_everything_connected(iterate_ref_map, 0, &rm))
-               return error(_("%s did not send all necessary objects\n"), url);
+       if (check_everything_connected(iterate_ref_map, 0, &rm)) {
+               rc = error(_("%s did not send all necessary objects\n"), url);
+               goto abort;
+       }
 
        for (rm = ref_map; rm; rm = rm->next) {
                struct ref *ref = NULL;
@@ -462,12 +464,15 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
                                fprintf(stderr, " %s\n", note);
                }
        }
-       free(url);
-       fclose(fp);
+
        if (rc & STORE_REF_ERROR_DF_CONFLICT)
                error(_("some local refs could not be updated; try running\n"
                      " 'git remote prune %s' to remove any old, conflicting "
                      "branches"), remote_name);
+
+ abort:
+       free(url);
+       fclose(fp);
        return rc;
 }
 
@@ -505,10 +510,10 @@ static int fetch_refs(struct transport *transport, struct ref *ref_map)
        return ret;
 }
 
-static int prune_refs(struct transport *transport, struct ref *ref_map)
+static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map)
 {
        int result = 0;
-       struct ref *ref, *stale_refs = get_stale_heads(transport->remote, ref_map);
+       struct ref *ref, *stale_refs = get_stale_heads(refs, ref_count, ref_map);
        const char *dangling_msg = dry_run
                ? _("   (%s will become dangling)\n")
                : _("   (%s has become dangling)\n");
@@ -699,8 +704,31 @@ static int do_fetch(struct transport *transport,
                free_refs(ref_map);
                return 1;
        }
-       if (prune)
-               prune_refs(transport, ref_map);
+       if (prune) {
+               /* If --tags was specified, pretend the user gave us the canonical tags refspec */
+               if (tags == TAGS_SET) {
+                       const char *tags_str = "refs/tags/*:refs/tags/*";
+                       struct refspec *tags_refspec, *refspec;
+
+                       /* Copy the refspec and add the tags to it */
+                       refspec = xcalloc(ref_count + 1, sizeof(struct refspec));
+                       tags_refspec = parse_fetch_refspec(1, &tags_str);
+                       memcpy(refspec, refs, ref_count * sizeof(struct refspec));
+                       memcpy(&refspec[ref_count], tags_refspec, sizeof(struct refspec));
+                       ref_count++;
+
+                       prune_refs(refspec, ref_count, ref_map);
+
+                       ref_count--;
+                       /* The rest of the strings belong to fetch_one */
+                       free_refspec(1, tags_refspec);
+                       free(refspec);
+               } else if (ref_count) {
+                       prune_refs(refs, ref_count, ref_map);
+               } else {
+                       prune_refs(transport->remote->fetch, transport->remote->fetch_refspec_nr, ref_map);
+               }
+       }
        free_refs(ref_map);
 
        /* if neither --no-tags nor --tags was specified, do automated tag
@@ -883,7 +911,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv)
        atexit(unlock_pack);
        refspec = parse_fetch_refspec(ref_nr, refs);
        exit_code = do_fetch(transport, refspec, ref_nr);
-       free(refspec);
+       free_refspec(ref_nr, refspec);
        transport_disconnect(transport);
        transport = NULL;
        return exit_code;
index 75816329d6153c35e0763b35f70cfaf165f2a4b8..7e2f22589dcb14d5ba95ce1331ef816a458533d0 100644 (file)
@@ -293,7 +293,7 @@ static int do_fmt_merge_msg(int merge_title, struct strbuf *in,
                struct commit *head;
                struct rev_info rev;
 
-               head = lookup_commit(head_sha1);
+               head = lookup_commit_or_die(head_sha1, "HEAD");
                init_revisions(&rev, NULL);
                rev.commit_format = CMIT_FMT_ONELINE;
                rev.ignore_merges = 1;
index 89e75c6894e6fdb2abbe464d825f5c7186a14141..d90e5d2b29f9ac72a104d6308c04497991a03c17 100644 (file)
@@ -69,6 +69,9 @@ static struct {
        { "subject" },
        { "body" },
        { "contents" },
+       { "contents:subject" },
+       { "contents:body" },
+       { "contents:signature" },
        { "upstream" },
        { "symref" },
        { "flag" },
@@ -361,6 +364,18 @@ static const char *copy_email(const char *buf)
        return xmemdupz(email, eoemail + 1 - email);
 }
 
+static char *copy_subject(const char *buf, unsigned long len)
+{
+       char *r = xmemdupz(buf, len);
+       int i;
+
+       for (i = 0; i < len; i++)
+               if (r[i] == '\n')
+                       r[i] = ' ';
+
+       return r;
+}
+
 static void grab_date(const char *buf, struct atom_value *v, const char *atomname)
 {
        const char *eoemail = strstr(buf, "> ");
@@ -458,38 +473,56 @@ static void grab_person(const char *who, struct atom_value *val, int deref, stru
        }
 }
 
-static void find_subpos(const char *buf, unsigned long sz, const char **sub, const char **body)
+static void find_subpos(const char *buf, unsigned long sz,
+                       const char **sub, unsigned long *sublen,
+                       const char **body, unsigned long *bodylen,
+                       unsigned long *nonsiglen,
+                       const char **sig, unsigned long *siglen)
 {
-       while (*buf) {
-               const char *eol = strchr(buf, '\n');
-               if (!eol)
-                       return;
-               if (eol[1] == '\n') {
-                       buf = eol + 1;
-                       break; /* found end of header */
-               }
-               buf = eol + 1;
+       const char *eol;
+       /* skip past header until we hit empty line */
+       while (*buf && *buf != '\n') {
+               eol = strchrnul(buf, '\n');
+               if (*eol)
+                       eol++;
+               buf = eol;
        }
+       /* skip any empty lines */
        while (*buf == '\n')
                buf++;
-       if (!*buf)
-               return;
-       *sub = buf; /* first non-empty line */
-       buf = strchr(buf, '\n');
-       if (!buf) {
-               *body = "";
-               return; /* no body */
+
+       /* parse signature first; we might not even have a subject line */
+       *sig = buf + parse_signature(buf, strlen(buf));
+       *siglen = strlen(*sig);
+
+       /* subject is first non-empty line */
+       *sub = buf;
+       /* subject goes to first empty line */
+       while (buf < *sig && *buf && *buf != '\n') {
+               eol = strchrnul(buf, '\n');
+               if (*eol)
+                       eol++;
+               buf = eol;
        }
+       *sublen = buf - *sub;
+       /* drop trailing newline, if present */
+       if (*sublen && (*sub)[*sublen - 1] == '\n')
+               *sublen -= 1;
+
+       /* skip any empty lines */
        while (*buf == '\n')
-               buf++; /* skip blank between subject and body */
+               buf++;
        *body = buf;
+       *bodylen = strlen(buf);
+       *nonsiglen = *sig - buf;
 }
 
 /* See grab_values */
 static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
 {
        int i;
-       const char *subpos = NULL, *bodypos = NULL;
+       const char *subpos = NULL, *bodypos = NULL, *sigpos = NULL;
+       unsigned long sublen = 0, bodylen = 0, nonsiglen = 0, siglen = 0;
 
        for (i = 0; i < used_atom_cnt; i++) {
                const char *name = used_atom[i];
@@ -500,17 +533,27 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj
                        name++;
                if (strcmp(name, "subject") &&
                    strcmp(name, "body") &&
-                   strcmp(name, "contents"))
+                   strcmp(name, "contents") &&
+                   strcmp(name, "contents:subject") &&
+                   strcmp(name, "contents:body") &&
+                   strcmp(name, "contents:signature"))
                        continue;
                if (!subpos)
-                       find_subpos(buf, sz, &subpos, &bodypos);
-               if (!subpos)
-                       return;
+                       find_subpos(buf, sz,
+                                   &subpos, &sublen,
+                                   &bodypos, &bodylen, &nonsiglen,
+                                   &sigpos, &siglen);
 
                if (!strcmp(name, "subject"))
-                       v->s = copy_line(subpos);
+                       v->s = copy_subject(subpos, sublen);
+               else if (!strcmp(name, "contents:subject"))
+                       v->s = copy_subject(subpos, sublen);
                else if (!strcmp(name, "body"))
-                       v->s = xstrdup(bodypos);
+                       v->s = xmemdupz(bodypos, bodylen);
+               else if (!strcmp(name, "contents:body"))
+                       v->s = xmemdupz(bodypos, nonsiglen);
+               else if (!strcmp(name, "contents:signature"))
+                       v->s = xmemdupz(sigpos, siglen);
                else if (!strcmp(name, "contents"))
                        v->s = xstrdup(subpos);
        }
index df1a88b51ae7773a15276d144113c0002fddb1cd..30d0dc82f053a5cbdfabcf474be816484ef3ca2b 100644 (file)
@@ -11,6 +11,7 @@
 #include "fsck.h"
 #include "parse-options.h"
 #include "dir.h"
+#include "progress.h"
 
 #define REACHABLE 0x0001
 #define SEEN      0x0002
@@ -27,8 +28,10 @@ static const char *head_points_at;
 static int errors_found;
 static int write_lost_and_found;
 static int verbose;
+static int show_progress = -1;
 #define ERROR_OBJECT 01
 #define ERROR_REACHABLE 02
+#define ERROR_PACK 04
 
 #ifdef NO_D_INO_IN_DIRENT
 #define SORT_DIRENT 0
@@ -137,7 +140,11 @@ static int traverse_one_object(struct object *obj)
 
 static int traverse_reachable(void)
 {
+       struct progress *progress = NULL;
+       unsigned int nr = 0;
        int result = 0;
+       if (show_progress)
+               progress = start_progress_delay("Checking connectivity", 0, 0, 2);
        while (pending.nr) {
                struct object_array_entry *entry;
                struct object *obj;
@@ -145,7 +152,9 @@ static int traverse_reachable(void)
                entry = pending.objects + --pending.nr;
                obj = entry->item;
                result |= traverse_one_object(obj);
+               display_progress(progress, ++nr);
        }
+       stop_progress(&progress);
        return !!result;
 }
 
@@ -281,14 +290,8 @@ static void check_connectivity(void)
        }
 }
 
-static int fsck_sha1(const unsigned char *sha1)
+static int fsck_obj(struct object *obj)
 {
-       struct object *obj = parse_object(sha1);
-       if (!obj) {
-               errors_found |= ERROR_OBJECT;
-               return error("%s: object corrupt or missing",
-                            sha1_to_hex(sha1));
-       }
        if (obj->flags & SEEN)
                return 0;
        obj->flags |= SEEN;
@@ -331,6 +334,29 @@ static int fsck_sha1(const unsigned char *sha1)
        return 0;
 }
 
+static int fsck_sha1(const unsigned char *sha1)
+{
+       struct object *obj = parse_object(sha1);
+       if (!obj) {
+               errors_found |= ERROR_OBJECT;
+               return error("%s: object corrupt or missing",
+                            sha1_to_hex(sha1));
+       }
+       return fsck_obj(obj);
+}
+
+static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type,
+                          unsigned long size, void *buffer, int *eaten)
+{
+       struct object *obj;
+       obj = parse_object_buffer(sha1, type, size, buffer, eaten);
+       if (!obj) {
+               errors_found |= ERROR_OBJECT;
+               return error("%s: object corrupt or missing", sha1_to_hex(sha1));
+       }
+       return fsck_obj(obj);
+}
+
 /*
  * This is the sorting chunk size: make it reasonably
  * big so that we can sort well..
@@ -512,15 +538,20 @@ static void get_default_heads(void)
 static void fsck_object_dir(const char *path)
 {
        int i;
+       struct progress *progress = NULL;
 
        if (verbose)
                fprintf(stderr, "Checking object directory\n");
 
+       if (show_progress)
+               progress = start_progress("Checking object directories", 256);
        for (i = 0; i < 256; i++) {
                static char dir[4096];
                sprintf(dir, "%s/%02x", path, i);
                fsck_dir(i, dir);
+               display_progress(progress, i+1);
        }
+       stop_progress(&progress);
        fsck_sha1_list();
 }
 
@@ -591,6 +622,7 @@ static struct option fsck_opts[] = {
        OPT_BOOLEAN(0, "strict", &check_strict, "enable more strict checking"),
        OPT_BOOLEAN(0, "lost-found", &write_lost_and_found,
                                "write dangling objects in .git/lost-found"),
+       OPT_BOOL(0, "progress", &show_progress, "show progress"),
        OPT_END(),
 };
 
@@ -603,6 +635,12 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
        read_replace_refs = 0;
 
        argc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0);
+
+       if (show_progress == -1)
+               show_progress = isatty(2);
+       if (verbose)
+               show_progress = 0;
+
        if (write_lost_and_found) {
                check_full = 1;
                include_reflogs = 0;
@@ -622,20 +660,28 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
 
        if (check_full) {
                struct packed_git *p;
+               uint32_t total = 0, count = 0;
+               struct progress *progress = NULL;
 
                prepare_packed_git();
-               for (p = packed_git; p; p = p->next)
-                       /* verify gives error messages itself */
-                       verify_pack(p);
 
+               if (show_progress) {
+                       for (p = packed_git; p; p = p->next) {
+                               if (open_pack_index(p))
+                                       continue;
+                               total += p->num_objects;
+                       }
+
+                       progress = start_progress("Checking objects", total);
+               }
                for (p = packed_git; p; p = p->next) {
-                       uint32_t j, num;
-                       if (open_pack_index(p))
-                               continue;
-                       num = p->num_objects;
-                       for (j = 0; j < num; j++)
-                               fsck_sha1(nth_packed_object_sha1(p, j));
+                       /* verify gives error messages itself */
+                       if (verify_pack(p, fsck_obj_buffer,
+                                       progress, count))
+                               errors_found |= ERROR_PACK;
+                       count += p->num_objects;
                }
+               stop_progress(&progress);
        }
 
        heads = 0;
index 0498094711d1addd40f526f0c76dd8ddb76ef550..271376d82b4391318fda9d5cfd5a0764d3768117 100644 (file)
@@ -32,7 +32,7 @@ static const char *prune_expire = "2.weeks.ago";
 static const char *argv_pack_refs[] = {"pack-refs", "--all", "--prune", NULL};
 static const char *argv_reflog[] = {"reflog", "expire", "--all", NULL};
 static const char *argv_repack[MAX_ADD] = {"repack", "-d", "-l", NULL};
-static const char *argv_prune[] = {"prune", "--expire", NULL, NULL};
+static const char *argv_prune[] = {"prune", "--expire", NULL, NULL, NULL};
 static const char *argv_rerere[] = {"rerere", "gc", NULL};
 
 static int gc_config(const char *var, const char *value, void *cb)
@@ -243,6 +243,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
 
        if (prune_expire) {
                argv_prune[2] = prune_expire;
+               if (quiet)
+                       argv_prune[3] = "--no-progress";
                if (run_command_v_opt(argv_prune, RUN_GIT_CMD))
                        return error(FAILED_RUN, argv_prune[0]);
        }
index a286692e467710d92346ab6900e98f1126cb967d..988ea1d3324d6e32fcfd3b97da0fad5ae41b592c 100644 (file)
@@ -74,13 +74,32 @@ static int all_work_added;
 /* This lock protects all the variables above. */
 static pthread_mutex_t grep_mutex;
 
+static inline void grep_lock(void)
+{
+       if (use_threads)
+               pthread_mutex_lock(&grep_mutex);
+}
+
+static inline void grep_unlock(void)
+{
+       if (use_threads)
+               pthread_mutex_unlock(&grep_mutex);
+}
+
 /* Used to serialize calls to read_sha1_file. */
 static pthread_mutex_t read_sha1_mutex;
 
-#define grep_lock() pthread_mutex_lock(&grep_mutex)
-#define grep_unlock() pthread_mutex_unlock(&grep_mutex)
-#define read_sha1_lock() pthread_mutex_lock(&read_sha1_mutex)
-#define read_sha1_unlock() pthread_mutex_unlock(&read_sha1_mutex)
+static inline void read_sha1_lock(void)
+{
+       if (use_threads)
+               pthread_mutex_lock(&read_sha1_mutex);
+}
+
+static inline void read_sha1_unlock(void)
+{
+       if (use_threads)
+               pthread_mutex_unlock(&read_sha1_mutex);
+}
 
 /* Signalled when a new work_item is added to todo. */
 static pthread_cond_t cond_add;
@@ -354,13 +373,9 @@ static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type
 {
        void *data;
 
-       if (use_threads) {
-               read_sha1_lock();
-               data = read_sha1_file(sha1, type, size);
-               read_sha1_unlock();
-       } else {
-               data = read_sha1_file(sha1, type, size);
-       }
+       read_sha1_lock();
+       data = read_sha1_file(sha1, type, size);
+       read_sha1_unlock();
        return data;
 }
 
@@ -542,18 +557,19 @@ static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int
 static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
                     struct tree_desc *tree, struct strbuf *base, int tn_len)
 {
-       int hit = 0, match = 0;
+       int hit = 0;
+       enum interesting match = entry_not_interesting;
        struct name_entry entry;
        int old_baselen = base->len;
 
        while (tree_entry(tree, &entry)) {
-               int te_len = tree_entry_len(entry.path, entry.sha1);
+               int te_len = tree_entry_len(&entry);
 
-               if (match != 2) {
+               if (match != all_entries_interesting) {
                        match = tree_entry_interesting(&entry, base, tn_len, pathspec);
-                       if (match < 0)
+                       if (match == all_entries_not_interesting)
                                break;
-                       if (match == 0)
+                       if (match == entry_not_interesting)
                                continue;
                }
 
@@ -640,13 +656,15 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec,
        return hit;
 }
 
-static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec)
+static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec,
+                         int exc_std)
 {
        struct dir_struct dir;
        int i, hit = 0;
 
        memset(&dir, 0, sizeof(dir));
-       setup_standard_excludes(&dir);
+       if (exc_std)
+               setup_standard_excludes(&dir);
 
        fill_directory(&dir, pathspec->raw);
        for (i = 0; i < dir.nr; i++) {
@@ -753,7 +771,7 @@ static int help_callback(const struct option *opt, const char *arg, int unset)
 int cmd_grep(int argc, const char **argv, const char *prefix)
 {
        int hit = 0;
-       int cached = 0;
+       int cached = 0, untracked = 0, opt_exclude = -1;
        int seen_dashdash = 0;
        int external_grep_allowed__ignored;
        const char *show_in_pager = NULL, *default_pager = "dummy";
@@ -777,8 +795,13 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
        struct option options[] = {
                OPT_BOOLEAN(0, "cached", &cached,
                        "search in index instead of in the work tree"),
-               OPT_BOOLEAN(0, "index", &use_index,
-                       "--no-index finds in contents not managed by git"),
+               { OPTION_BOOLEAN, 0, "index", &use_index, NULL,
+                       "finds in contents not managed by git",
+                       PARSE_OPT_NOARG | PARSE_OPT_NEGHELP },
+               OPT_BOOLEAN(0, "untracked", &untracked,
+                       "search in both tracked and untracked files"),
+               OPT_SET_INT(0, "exclude-standard", &opt_exclude,
+                           "search also in ignored files", 1),
                OPT_GROUP(""),
                OPT_BOOLEAN('v', "invert-match", &opt.invert,
                        "show non-matching lines"),
@@ -1048,13 +1071,16 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
        if (!show_in_pager)
                setup_pager();
 
+       if (!use_index && (untracked || cached))
+               die(_("--cached or --untracked cannot be used with --no-index."));
 
-       if (!use_index) {
-               if (cached)
-                       die(_("--cached cannot be used with --no-index."));
+       if (!use_index || untracked) {
+               int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude;
                if (list.nr)
-                       die(_("--no-index cannot be used with revs."));
-               hit = grep_directory(&opt, &pathspec);
+                       die(_("--no-index or --untracked cannot be used with revs."));
+               hit = grep_directory(&opt, &pathspec, use_exclude);
+       } else if (0 <= opt_exclude) {
+               die(_("--[no-]exclude-standard cannot be used for tracked contents."));
        } else if (!list.nr) {
                if (!cached)
                        setup_work_tree();
index e8a800d3ac42bfce328bea786d8e1efa4ff695c7..7cff175745d680d9cde24280e569b4f513d28673 100644 (file)
@@ -545,7 +545,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
                strip_trailing_slash_from_submodules();
 
        /* Find common prefix for all pathspec's */
-       max_prefix = pathspec_prefix(prefix, pathspec);
+       max_prefix = common_prefix(pathspec);
        max_prefix_len = max_prefix ? strlen(max_prefix) : 0;
 
        /* Treat unmatching pathspec elements as errors */
index 10223092a9ebfed4092db680529a42ca8886a9f2..41c88a98a2de1ce817351243f9aa9e2811604097 100644 (file)
@@ -43,6 +43,9 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
        struct transport *transport;
        const struct ref *ref;
 
+       if (argc == 2 && !strcmp("-h", argv[1]))
+               usage(ls_remote_usage);
+
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
 
index ee56974371fa9224ecb4b8203fb1356b6cf6e156..2870a6af6fbc868455f2961483cfbfdb065a1a14 100644 (file)
@@ -46,11 +46,10 @@ static const char * const builtin_merge_usage[] = {
 
 static int show_diffstat = 1, shortlog_len, squash;
 static int option_commit = 1, allow_fast_forward = 1;
-static int fast_forward_only;
+static int fast_forward_only, option_edit;
 static int allow_trivial = 1, have_message;
 static struct strbuf merge_msg;
 static struct commit_list *remoteheads;
-static unsigned char head[20], stash[20];
 static struct strategy **use_strategies;
 static size_t use_strategies_nr, use_strategies_alloc;
 static const char **xopts;
@@ -190,6 +189,8 @@ static struct option builtin_merge_options[] = {
                "create a single commit instead of doing a merge"),
        OPT_BOOLEAN(0, "commit", &option_commit,
                "perform a commit if the merge succeeds (default)"),
+       OPT_BOOLEAN('e', "edit", &option_edit,
+               "edit message before committing"),
        OPT_BOOLEAN(0, "ff", &allow_fast_forward,
                "allow fast-forward (default)"),
        OPT_BOOLEAN(0, "ff-only", &fast_forward_only,
@@ -217,7 +218,7 @@ static void drop_save(void)
        unlink(git_path("MERGE_MODE"));
 }
 
-static void save_state(void)
+static int save_state(unsigned char *stash)
 {
        int len;
        struct child_process cp;
@@ -236,11 +237,12 @@ static void save_state(void)
 
        if (finish_command(&cp) || len < 0)
                die(_("stash failed"));
-       else if (!len)
-               return;
+       else if (!len)          /* no changes */
+               return -1;
        strbuf_setlen(&buffer, buffer.len-1);
        if (get_sha1(buffer.buf, stash))
                die(_("not a valid object: %s"), buffer.buf);
+       return 0;
 }
 
 static void read_empty(unsigned const char *sha1, int verbose)
@@ -278,7 +280,8 @@ static void reset_hard(unsigned const char *sha1, int verbose)
                die(_("read-tree failed"));
 }
 
-static void restore_state(void)
+static void restore_state(const unsigned char *head,
+                         const unsigned char *stash)
 {
        struct strbuf sb = STRBUF_INIT;
        const char *args[] = { "stash", "apply", NULL, NULL };
@@ -308,25 +311,25 @@ static void finish_up_to_date(const char *msg)
        drop_save();
 }
 
-static void squash_message(void)
+static void squash_message(struct commit *commit)
 {
        struct rev_info rev;
-       struct commit *commit;
        struct strbuf out = STRBUF_INIT;
        struct commit_list *j;
+       const char *filename;
        int fd;
        struct pretty_print_context ctx = {0};
 
        printf(_("Squash commit -- not updating HEAD\n"));
-       fd = open(git_path("SQUASH_MSG"), O_WRONLY | O_CREAT, 0666);
+       filename = git_path("SQUASH_MSG");
+       fd = open(filename, O_WRONLY | O_CREAT, 0666);
        if (fd < 0)
-               die_errno(_("Could not write to '%s'"), git_path("SQUASH_MSG"));
+               die_errno(_("Could not write to '%s'"), filename);
 
        init_revisions(&rev, NULL);
        rev.ignore_merges = 1;
        rev.commit_format = CMIT_FMT_MEDIUM;
 
-       commit = lookup_commit(head);
        commit->object.flags |= UNINTERESTING;
        add_pending_object(&rev, &commit->object, NULL);
 
@@ -355,9 +358,11 @@ static void squash_message(void)
        strbuf_release(&out);
 }
 
-static void finish(const unsigned char *new_head, const char *msg)
+static void finish(struct commit *head_commit,
+                  const unsigned char *new_head, const char *msg)
 {
        struct strbuf reflog_message = STRBUF_INIT;
+       const unsigned char *head = head_commit->object.sha1;
 
        if (!msg)
                strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
@@ -368,7 +373,7 @@ static void finish(const unsigned char *new_head, const char *msg)
                        getenv("GIT_REFLOG_ACTION"), msg);
        }
        if (squash) {
-               squash_message();
+               squash_message(head_commit);
        } else {
                if (verbosity >= 0 && !merge_msg.len)
                        printf(_("No merge message -- not updating HEAD\n"));
@@ -489,14 +494,16 @@ static void merge_name(const char *remote, struct strbuf *msg)
 
        if (!strcmp(remote, "FETCH_HEAD") &&
                        !access(git_path("FETCH_HEAD"), R_OK)) {
+               const char *filename;
                FILE *fp;
                struct strbuf line = STRBUF_INIT;
                char *ptr;
 
-               fp = fopen(git_path("FETCH_HEAD"), "r");
+               filename = git_path("FETCH_HEAD");
+               fp = fopen(filename, "r");
                if (!fp)
                        die_errno(_("could not open '%s' for reading"),
-                                 git_path("FETCH_HEAD"));
+                                 filename);
                strbuf_getline(&line, fp, '\n');
                fclose(fp);
                ptr = strstr(line.buf, "\tnot-for-merge\t");
@@ -671,7 +678,7 @@ int try_merge_command(const char *strategy, size_t xopts_nr,
 }
 
 static int try_merge_strategy(const char *strategy, struct commit_list *common,
-                             const char *head_arg)
+                             struct commit *head, const char *head_arg)
 {
        int index_fd;
        struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
@@ -717,7 +724,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,
                        commit_list_insert(j->item, &reversed);
 
                index_fd = hold_locked_index(lock, 1);
-               clean = merge_recursive(&o, lookup_commit(head),
+               clean = merge_recursive(&o, head,
                                remoteheads->item, reversed, &result);
                if (active_cache_changed &&
                                (write_cache(index_fd, active_cache, active_nr) ||
@@ -842,51 +849,78 @@ static void add_strategies(const char *string, unsigned attr)
 
 }
 
-static void write_merge_msg(void)
+static void write_merge_msg(struct strbuf *msg)
 {
-       int fd = open(git_path("MERGE_MSG"), O_WRONLY | O_CREAT, 0666);
+       const char *filename = git_path("MERGE_MSG");
+       int fd = open(filename, O_WRONLY | O_CREAT, 0666);
        if (fd < 0)
                die_errno(_("Could not open '%s' for writing"),
-                         git_path("MERGE_MSG"));
-       if (write_in_full(fd, merge_msg.buf, merge_msg.len) != merge_msg.len)
-               die_errno(_("Could not write to '%s'"), git_path("MERGE_MSG"));
+                         filename);
+       if (write_in_full(fd, msg->buf, msg->len) != msg->len)
+               die_errno(_("Could not write to '%s'"), filename);
        close(fd);
 }
 
-static void read_merge_msg(void)
+static void read_merge_msg(struct strbuf *msg)
+{
+       const char *filename = git_path("MERGE_MSG");
+       strbuf_reset(msg);
+       if (strbuf_read_file(msg, filename, 0) < 0)
+               die_errno(_("Could not read from '%s'"), filename);
+}
+
+static void write_merge_state(void);
+static void abort_commit(const char *err_msg)
 {
-       strbuf_reset(&merge_msg);
-       if (strbuf_read_file(&merge_msg, git_path("MERGE_MSG"), 0) < 0)
-               die_errno(_("Could not read from '%s'"), git_path("MERGE_MSG"));
+       if (err_msg)
+               error("%s", err_msg);
+       fprintf(stderr,
+               _("Not committing merge; use 'git commit' to complete the merge.\n"));
+       write_merge_state();
+       exit(1);
 }
 
-static void run_prepare_commit_msg(void)
+static void prepare_to_commit(void)
 {
-       write_merge_msg();
+       struct strbuf msg = STRBUF_INIT;
+       strbuf_addbuf(&msg, &merge_msg);
+       strbuf_addch(&msg, '\n');
+       write_merge_msg(&msg);
        run_hook(get_index_file(), "prepare-commit-msg",
                 git_path("MERGE_MSG"), "merge", NULL, NULL);
-       read_merge_msg();
+       if (option_edit) {
+               if (launch_editor(git_path("MERGE_MSG"), NULL, NULL))
+                       abort_commit(NULL);
+       }
+       read_merge_msg(&msg);
+       stripspace(&msg, option_edit);
+       if (!msg.len)
+               abort_commit(_("Empty commit message."));
+       strbuf_release(&merge_msg);
+       strbuf_addbuf(&merge_msg, &msg);
+       strbuf_release(&msg);
 }
 
-static int merge_trivial(void)
+static int merge_trivial(struct commit *head)
 {
        unsigned char result_tree[20], result_commit[20];
        struct commit_list *parent = xmalloc(sizeof(*parent));
 
        write_tree_trivial(result_tree);
        printf(_("Wonderful.\n"));
-       parent->item = lookup_commit(head);
+       parent->item = head;
        parent->next = xmalloc(sizeof(*parent->next));
        parent->next->item = remoteheads->item;
        parent->next->next = NULL;
-       run_prepare_commit_msg();
+       prepare_to_commit();
        commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL);
-       finish(result_commit, "In-index merge");
+       finish(head, result_commit, "In-index merge");
        drop_save();
        return 0;
 }
 
-static int finish_automerge(struct commit_list *common,
+static int finish_automerge(struct commit *head,
+                           struct commit_list *common,
                            unsigned char *result_tree,
                            const char *wt_strategy)
 {
@@ -897,22 +931,22 @@ static int finish_automerge(struct commit_list *common,
        free_commit_list(common);
        if (allow_fast_forward) {
                parents = remoteheads;
-               commit_list_insert(lookup_commit(head), &parents);
+               commit_list_insert(head, &parents);
                parents = reduce_heads(parents);
        } else {
                struct commit_list **pptr = &parents;
 
-               pptr = &commit_list_insert(lookup_commit(head),
+               pptr = &commit_list_insert(head,
                                pptr)->next;
                for (j = remoteheads; j; j = j->next)
                        pptr = &commit_list_insert(j->item, pptr)->next;
        }
-       free_commit_list(remoteheads);
        strbuf_addch(&merge_msg, '\n');
-       run_prepare_commit_msg();
+       prepare_to_commit();
+       free_commit_list(remoteheads);
        commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL);
        strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
-       finish(result_commit, buf.buf);
+       finish(head, result_commit, buf.buf);
        strbuf_release(&buf);
        drop_save();
        return 0;
@@ -920,13 +954,14 @@ static int finish_automerge(struct commit_list *common,
 
 static int suggest_conflicts(int renormalizing)
 {
+       const char *filename;
        FILE *fp;
        int pos;
 
-       fp = fopen(git_path("MERGE_MSG"), "a");
+       filename = git_path("MERGE_MSG");
+       fp = fopen(filename, "a");
        if (!fp)
-               die_errno(_("Could not open '%s' for writing"),
-                         git_path("MERGE_MSG"));
+               die_errno(_("Could not open '%s' for writing"), filename);
        fprintf(fp, "\nConflicts:\n");
        for (pos = 0; pos < active_nr; pos++) {
                struct cache_entry *ce = active_cache[pos];
@@ -946,7 +981,8 @@ static int suggest_conflicts(int renormalizing)
        return 1;
 }
 
-static struct commit *is_old_style_invocation(int argc, const char **argv)
+static struct commit *is_old_style_invocation(int argc, const char **argv,
+                                             const unsigned char *head)
 {
        struct commit *second_token = NULL;
        if (argc > 2) {
@@ -1015,12 +1051,47 @@ static int setup_with_upstream(const char ***argv)
        return i;
 }
 
+static void write_merge_state(void)
+{
+       const char *filename;
+       int fd;
+       struct commit_list *j;
+       struct strbuf buf = STRBUF_INIT;
+
+       for (j = remoteheads; j; j = j->next)
+               strbuf_addf(&buf, "%s\n",
+                       sha1_to_hex(j->item->object.sha1));
+       filename = git_path("MERGE_HEAD");
+       fd = open(filename, O_WRONLY | O_CREAT, 0666);
+       if (fd < 0)
+               die_errno(_("Could not open '%s' for writing"), filename);
+       if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+               die_errno(_("Could not write to '%s'"), filename);
+       close(fd);
+       strbuf_addch(&merge_msg, '\n');
+       write_merge_msg(&merge_msg);
+
+       filename = git_path("MERGE_MODE");
+       fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
+       if (fd < 0)
+               die_errno(_("Could not open '%s' for writing"), filename);
+       strbuf_reset(&buf);
+       if (!allow_fast_forward)
+               strbuf_addf(&buf, "no-ff");
+       if (write_in_full(fd, buf.buf, buf.len) != buf.len)
+               die_errno(_("Could not write to '%s'"), filename);
+       close(fd);
+}
+
 int cmd_merge(int argc, const char **argv, const char *prefix)
 {
        unsigned char result_tree[20];
+       unsigned char stash[20];
+       unsigned char head_sha1[20];
+       struct commit *head_commit;
        struct strbuf buf = STRBUF_INIT;
        const char *head_arg;
-       int flag, head_invalid = 0, i;
+       int flag, i;
        int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
        struct commit_list *common = NULL;
        const char *best_strategy = NULL, *wt_strategy = NULL;
@@ -1033,11 +1104,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
         * Check if we are _not_ on a detached HEAD, i.e. if there is a
         * current branch.
         */
-       branch = resolve_ref("HEAD", head, 0, &flag);
+       branch = resolve_ref("HEAD", head_sha1, 0, &flag);
        if (branch && !prefixcmp(branch, "refs/heads/"))
                branch += 11;
-       if (is_null_sha1(head))
-               head_invalid = 1;
+       if (!branch || is_null_sha1(head_sha1))
+               head_commit = NULL;
+       else
+               head_commit = lookup_commit_or_die(head_sha1, "HEAD");
 
        git_config(git_merge_config, NULL);
 
@@ -1114,12 +1187,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
         * additional safety measure to check for it.
         */
 
-       if (!have_message && is_old_style_invocation(argc, argv)) {
+       if (!have_message && head_commit &&
+           is_old_style_invocation(argc, argv, head_commit->object.sha1)) {
                strbuf_addstr(&merge_msg, argv[0]);
                head_arg = argv[1];
                argv += 2;
                argc -= 2;
-       } else if (head_invalid) {
+       } else if (!head_commit) {
                struct object *remote_head;
                /*
                 * If the merged head is a valid one there is no reason
@@ -1166,7 +1240,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                }
        }
 
-       if (head_invalid || !argc)
+       if (!head_commit || !argc)
                usage_with_options(builtin_merge_usage,
                        builtin_merge_options);
 
@@ -1207,17 +1281,16 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
        }
 
        if (!remoteheads->next)
-               common = get_merge_bases(lookup_commit(head),
-                               remoteheads->item, 1);
+               common = get_merge_bases(head_commit, remoteheads->item, 1);
        else {
                struct commit_list *list = remoteheads;
-               commit_list_insert(lookup_commit(head), &list);
+               commit_list_insert(head_commit, &list);
                common = get_octopus_merge_bases(list);
                free(list);
        }
 
-       update_ref("updating ORIG_HEAD", "ORIG_HEAD", head, NULL, 0,
-               DIE_ON_ERR);
+       update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.sha1,
+                  NULL, 0, DIE_ON_ERR);
 
        if (!common)
                ; /* No common ancestors found. We need a real merge. */
@@ -1231,13 +1304,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                return 0;
        } else if (allow_fast_forward && !remoteheads->next &&
                        !common->next &&
-                       !hashcmp(common->item->object.sha1, head)) {
+                       !hashcmp(common->item->object.sha1, head_commit->object.sha1)) {
                /* Again the most common case of merging one remote. */
                struct strbuf msg = STRBUF_INIT;
                struct object *o;
                char hex[41];
 
-               strcpy(hex, find_unique_abbrev(head, DEFAULT_ABBREV));
+               strcpy(hex, find_unique_abbrev(head_commit->object.sha1, DEFAULT_ABBREV));
 
                if (verbosity >= 0)
                        printf(_("Updating %s..%s\n"),
@@ -1252,10 +1325,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                if (!o)
                        return 1;
 
-               if (checkout_fast_forward(head, remoteheads->item->object.sha1))
+               if (checkout_fast_forward(head_commit->object.sha1, remoteheads->item->object.sha1))
                        return 1;
 
-               finish(o->sha1, msg.buf);
+               finish(head_commit, o->sha1, msg.buf);
                drop_save();
                return 0;
        } else if (!remoteheads->next && common->next)
@@ -1275,8 +1348,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                        git_committer_info(IDENT_ERROR_ON_NO_NAME);
                        printf(_("Trying really trivial in-index merge...\n"));
                        if (!read_tree_trivial(common->item->object.sha1,
-                                       head, remoteheads->item->object.sha1))
-                               return merge_trivial();
+                                       head_commit->object.sha1, remoteheads->item->object.sha1))
+                               return merge_trivial(head_commit);
                        printf(_("Nope.\n"));
                }
        } else {
@@ -1295,8 +1368,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                         * merge_bases again, otherwise "git merge HEAD^
                         * HEAD^^" would be missed.
                         */
-                       common_one = get_merge_bases(lookup_commit(head),
-                               j->item, 1);
+                       common_one = get_merge_bases(head_commit, j->item, 1);
                        if (hashcmp(common_one->item->object.sha1,
                                j->item->object.sha1)) {
                                up_to_date = 0;
@@ -1323,21 +1395,18 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
         * sync with the head commit.  The strategies are responsible
         * to ensure this.
         */
-       if (use_strategies_nr != 1) {
-               /*
-                * Stash away the local changes so that we can try more
-                * than one.
-                */
-               save_state();
-       } else {
-               memcpy(stash, null_sha1, 20);
-       }
+       if (use_strategies_nr == 1 ||
+           /*
+            * Stash away the local changes so that we can try more than one.
+            */
+           save_state(stash))
+               hashcpy(stash, null_sha1);
 
        for (i = 0; i < use_strategies_nr; i++) {
                int ret;
                if (i) {
                        printf(_("Rewinding the tree to pristine...\n"));
-                       restore_state();
+                       restore_state(head_commit->object.sha1, stash);
                }
                if (use_strategies_nr != 1)
                        printf(_("Trying merge strategy %s...\n"),
@@ -1349,7 +1418,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                wt_strategy = use_strategies[i]->name;
 
                ret = try_merge_strategy(use_strategies[i]->name,
-                       common, head_arg);
+                                        common, head_commit, head_arg);
                if (!option_commit && !ret) {
                        merge_was_ok = 1;
                        /*
@@ -1391,14 +1460,15 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
         * auto resolved the merge cleanly.
         */
        if (automerge_was_ok)
-               return finish_automerge(common, result_tree, wt_strategy);
+               return finish_automerge(head_commit, common, result_tree,
+                                       wt_strategy);
 
        /*
         * Pick the result from the best strategy and have the user fix
         * it up.
         */
        if (!best_strategy) {
-               restore_state();
+               restore_state(head_commit->object.sha1, stash);
                if (use_strategies_nr > 1)
                        fprintf(stderr,
                                _("No merge strategy handled the merge.\n"));
@@ -1410,41 +1480,16 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                ; /* We already have its result in the working tree. */
        else {
                printf(_("Rewinding the tree to pristine...\n"));
-               restore_state();
+               restore_state(head_commit->object.sha1, stash);
                printf(_("Using the %s to prepare resolving by hand.\n"),
                        best_strategy);
-               try_merge_strategy(best_strategy, common, head_arg);
+               try_merge_strategy(best_strategy, common, head_commit, head_arg);
        }
 
        if (squash)
-               finish(NULL, NULL);
-       else {
-               int fd;
-               struct commit_list *j;
-
-               for (j = remoteheads; j; j = j->next)
-                       strbuf_addf(&buf, "%s\n",
-                               sha1_to_hex(j->item->object.sha1));
-               fd = open(git_path("MERGE_HEAD"), O_WRONLY | O_CREAT, 0666);
-               if (fd < 0)
-                       die_errno(_("Could not open '%s' for writing"),
-                                 git_path("MERGE_HEAD"));
-               if (write_in_full(fd, buf.buf, buf.len) != buf.len)
-                       die_errno(_("Could not write to '%s'"), git_path("MERGE_HEAD"));
-               close(fd);
-               strbuf_addch(&merge_msg, '\n');
-               write_merge_msg();
-               fd = open(git_path("MERGE_MODE"), O_WRONLY | O_CREAT | O_TRUNC, 0666);
-               if (fd < 0)
-                       die_errno(_("Could not open '%s' for writing"),
-                                 git_path("MERGE_MODE"));
-               strbuf_reset(&buf);
-               if (!allow_fast_forward)
-                       strbuf_addf(&buf, "no-ff");
-               if (write_in_full(fd, buf.buf, buf.len) != buf.len)
-                       die_errno(_("Could not write to '%s'"), git_path("MERGE_MODE"));
-               close(fd);
-       }
+               finish(head_commit, NULL, NULL);
+       else
+               write_merge_state();
 
        if (merge_was_ok) {
                fprintf(stderr, _("Automatic merge went well; "
index 098395fda1932674e29f8d2cb332c722a6cb275f..4ae1c412d47bd581f22ec8b821a1111224dddd28 100644 (file)
@@ -60,6 +60,7 @@ static void write_tree(unsigned char *sha1)
        }
 
        write_sha1_file(buf.buf, buf.len, tree_type, sha1);
+       strbuf_release(&buf);
 }
 
 static const char *mktree_usage[] = {
index 40f33ca4d0e6d0bb1733e0544029c6f588d09b59..5efe6c5760c43d0059a940ab9d4610b97092c8bd 100644 (file)
@@ -29,7 +29,11 @@ static const char **copy_pathspec(const char *prefix, const char **pathspec,
                        to_copy--;
                if (to_copy != length || base_name) {
                        char *it = xmemdupz(result[i], to_copy);
-                       result[i] = base_name ? strdup(basename(it)) : it;
+                       if (base_name) {
+                               result[i] = xstrdup(basename(it));
+                               free(it);
+                       } else
+                               result[i] = it;
                }
        }
        return get_pathspec(prefix, result);
index 31f5c1c971381a9490b717e0413ee9a40260ef39..1b374583c2751801dfae1d0d1861d28f81c7ad7b 100644 (file)
@@ -172,7 +172,9 @@ static void show_name(const struct object *obj,
 }
 
 static char const * const name_rev_usage[] = {
-       "git name-rev [options] ( --all | --stdin | <commit>... )",
+       "git name-rev [options] <commit>...",
+       "git name-rev [options] --all",
+       "git name-rev [options] --stdin",
        NULL
 };
 
@@ -289,7 +291,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
                max = get_max_object_index();
                for (i = 0; i < max; i++) {
                        struct object *obj = get_indexed_object(i);
-                       if (!obj)
+                       if (!obj || obj->type != OBJ_COMMIT)
                                continue;
                        show_name(obj, NULL,
                                  always, allow_undefined, data.name_only);
index 2b18de5dc37bf849dbdbc892e4e9f3a34893dd9d..558cd34bccf66b1ef4cf9d66bfefe43fae76ceb3 100644 (file)
@@ -454,8 +454,8 @@ static int mark_tagged(const char *path, const unsigned char *sha1, int flag,
        return 0;
 }
 
-static void add_to_write_order(struct object_entry **wo,
-                              int *endp,
+static inline void add_to_write_order(struct object_entry **wo,
+                              unsigned int *endp,
                               struct object_entry *e)
 {
        if (e->filled)
@@ -465,32 +465,62 @@ static void add_to_write_order(struct object_entry **wo,
 }
 
 static void add_descendants_to_write_order(struct object_entry **wo,
-                                          int *endp,
+                                          unsigned int *endp,
                                           struct object_entry *e)
 {
-       struct object_entry *child;
-
-       for (child = e->delta_child; child; child = child->delta_sibling)
-               add_to_write_order(wo, endp, child);
-       for (child = e->delta_child; child; child = child->delta_sibling)
-               add_descendants_to_write_order(wo, endp, child);
+       int add_to_order = 1;
+       while (e) {
+               if (add_to_order) {
+                       struct object_entry *s;
+                       /* add this node... */
+                       add_to_write_order(wo, endp, e);
+                       /* all its siblings... */
+                       for (s = e->delta_sibling; s; s = s->delta_sibling) {
+                               add_to_write_order(wo, endp, s);
+                       }
+               }
+               /* drop down a level to add left subtree nodes if possible */
+               if (e->delta_child) {
+                       add_to_order = 1;
+                       e = e->delta_child;
+               } else {
+                       add_to_order = 0;
+                       /* our sibling might have some children, it is next */
+                       if (e->delta_sibling) {
+                               e = e->delta_sibling;
+                               continue;
+                       }
+                       /* go back to our parent node */
+                       e = e->delta;
+                       while (e && !e->delta_sibling) {
+                               /* we're on the right side of a subtree, keep
+                                * going up until we can go right again */
+                               e = e->delta;
+                       }
+                       if (!e) {
+                               /* done- we hit our original root node */
+                               return;
+                       }
+                       /* pass it off to sibling at this level */
+                       e = e->delta_sibling;
+               }
+       };
 }
 
 static void add_family_to_write_order(struct object_entry **wo,
-                                     int *endp,
+                                     unsigned int *endp,
                                      struct object_entry *e)
 {
        struct object_entry *root;
 
        for (root = e; root->delta; root = root->delta)
                ; /* nothing */
-       add_to_write_order(wo, endp, root);
        add_descendants_to_write_order(wo, endp, root);
 }
 
 static struct object_entry **compute_write_order(void)
 {
-       int i, wo_end;
+       unsigned int i, wo_end, last_untagged;
 
        struct object_entry **wo = xmalloc(nr_objects * sizeof(*wo));
 
@@ -506,8 +536,8 @@ static struct object_entry **compute_write_order(void)
         * Make sure delta_sibling is sorted in the original
         * recency order.
         */
-       for (i = nr_objects - 1; 0 <= i; i--) {
-               struct object_entry *e = &objects[i];
+       for (i = nr_objects; i > 0;) {
+               struct object_entry *e = &objects[--i];
                if (!e->delta)
                        continue;
                /* Mark me as the first child */
@@ -521,7 +551,7 @@ static struct object_entry **compute_write_order(void)
        for_each_tag_ref(mark_tagged, NULL);
 
        /*
-        * Give the commits in the original recency order until
+        * Give the objects in the original recency order until
         * we see a tagged tip.
         */
        for (i = wo_end = 0; i < nr_objects; i++) {
@@ -529,6 +559,7 @@ static struct object_entry **compute_write_order(void)
                        break;
                add_to_write_order(wo, &wo_end, &objects[i]);
        }
+       last_untagged = i;
 
        /*
         * Then fill all the tagged tips.
@@ -541,7 +572,7 @@ static struct object_entry **compute_write_order(void)
        /*
         * And then all remaining commits and tags.
         */
-       for (i = 0; i < nr_objects; i++) {
+       for (i = last_untagged; i < nr_objects; i++) {
                if (objects[i].type != OBJ_COMMIT &&
                    objects[i].type != OBJ_TAG)
                        continue;
@@ -551,7 +582,7 @@ static struct object_entry **compute_write_order(void)
        /*
         * And then all the trees.
         */
-       for (i = 0; i < nr_objects; i++) {
+       for (i = last_untagged; i < nr_objects; i++) {
                if (objects[i].type != OBJ_TREE)
                        continue;
                add_to_write_order(wo, &wo_end, &objects[i]);
@@ -560,8 +591,13 @@ static struct object_entry **compute_write_order(void)
        /*
         * Finally all the rest in really tight order
         */
-       for (i = 0; i < nr_objects; i++)
-               add_family_to_write_order(wo, &wo_end, &objects[i]);
+       for (i = last_untagged; i < nr_objects; i++) {
+               if (!objects[i].filled)
+                       add_family_to_write_order(wo, &wo_end, &objects[i]);
+       }
+
+       if (wo_end != nr_objects)
+               die("ordered %u objects, expected %"PRIu32, wo_end, nr_objects);
 
        return wo;
 }
@@ -804,6 +840,10 @@ static int add_object_entry(const unsigned char *sha1, enum object_type type,
                off_t offset = find_pack_entry_one(sha1, p);
                if (offset) {
                        if (!found_pack) {
+                               if (!is_pack_valid(p)) {
+                                       warning("packfile %s cannot be accessed", p->pack_name);
+                                       continue;
+                               }
                                found_offset = offset;
                                found_pack = p;
                        }
@@ -975,7 +1015,7 @@ static void add_pbase_object(struct tree_desc *tree,
        while (tree_entry(tree,&entry)) {
                if (S_ISGITLINK(entry.mode))
                        continue;
-               cmp = tree_entry_len(entry.path, entry.sha1) != cmplen ? 1 :
+               cmp = tree_entry_len(&entry) != cmplen ? 1 :
                      memcmp(name, entry.path, cmplen);
                if (cmp > 0)
                        continue;
index e65690ba370511072dfa1e64838eef5e5686aac9..58d7cb83240ecef7ab3ff82f3aa92959ec7a62fe 100644 (file)
@@ -5,6 +5,7 @@
 #include "builtin.h"
 #include "reachable.h"
 #include "parse-options.h"
+#include "progress.h"
 #include "dir.h"
 
 static const char * const prune_usage[] = {
@@ -14,6 +15,7 @@ static const char * const prune_usage[] = {
 static int show_only;
 static int verbose;
 static unsigned long expire;
+static int show_progress = -1;
 
 static int prune_tmp_object(const char *path, const char *filename)
 {
@@ -124,9 +126,11 @@ static void remove_temporary_files(const char *path)
 int cmd_prune(int argc, const char **argv, const char *prefix)
 {
        struct rev_info revs;
+       struct progress *progress = NULL;
        const struct option options[] = {
                OPT__DRY_RUN(&show_only, "do not remove, show only"),
                OPT__VERBOSE(&verbose, "report pruned objects"),
+               OPT_BOOL(0, "progress", &show_progress, "show progress"),
                OPT_DATE(0, "expire", &expire,
                         "expire objects older than <time>"),
                OPT_END()
@@ -152,7 +156,14 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
                else
                        die("unrecognized argument: %s", name);
        }
-       mark_reachable_objects(&revs, 1);
+
+       if (show_progress == -1)
+               show_progress = isatty(2);
+       if (show_progress)
+               progress = start_progress_delay("Checking connectivity", 0, 0, 2);
+
+       mark_reachable_objects(&revs, 1, progress);
+       stop_progress(&progress);
        prune_object_dir(get_object_directory());
 
        prune_packed_objects(show_only);
index 9b56be3cc690d4a5e4eb65ccd8e364f3245cde39..7ec68a1e8089f74ce7c70fecd86d5c18264ce4bc 100644 (file)
@@ -154,7 +154,8 @@ static void write_head_info(void)
 struct command {
        struct command *next;
        const char *error_string;
-       unsigned int skip_update;
+       unsigned int skip_update:1,
+                    did_not_exist:1;
        unsigned char old_sha1[20];
        unsigned char new_sha1[20];
        char ref_name[FLEX_ARRAY]; /* more */
@@ -212,21 +213,15 @@ static int copy_to_sideband(int in, int out, void *arg)
        return 0;
 }
 
-static int run_receive_hook(struct command *commands, const char *hook_name)
+typedef int (*feed_fn)(void *, const char **, size_t *);
+static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_state)
 {
-       static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4];
-       struct command *cmd;
        struct child_process proc;
        struct async muxer;
        const char *argv[2];
-       int have_input = 0, code;
-
-       for (cmd = commands; !have_input && cmd; cmd = cmd->next) {
-               if (!cmd->error_string)
-                       have_input = 1;
-       }
+       int code;
 
-       if (!have_input || access(hook_name, X_OK) < 0)
+       if (access(hook_name, X_OK) < 0)
                return 0;
 
        argv[0] = hook_name;
@@ -254,15 +249,13 @@ static int run_receive_hook(struct command *commands, const char *hook_name)
                return code;
        }
 
-       for (cmd = commands; cmd; cmd = cmd->next) {
-               if (!cmd->error_string) {
-                       size_t n = snprintf(buf, sizeof(buf), "%s %s %s\n",
-                               sha1_to_hex(cmd->old_sha1),
-                               sha1_to_hex(cmd->new_sha1),
-                               cmd->ref_name);
-                       if (write_in_full(proc.in, buf, n) != n)
-                               break;
-               }
+       while (1) {
+               const char *buf;
+               size_t n;
+               if (feed(feed_state, &buf, &n))
+                       break;
+               if (write_in_full(proc.in, buf, n) != n)
+                       break;
        }
        close(proc.in);
        if (use_sideband)
@@ -270,6 +263,51 @@ static int run_receive_hook(struct command *commands, const char *hook_name)
        return finish_command(&proc);
 }
 
+struct receive_hook_feed_state {
+       struct command *cmd;
+       int skip_broken;
+       struct strbuf buf;
+};
+
+static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)
+{
+       struct receive_hook_feed_state *state = state_;
+       struct command *cmd = state->cmd;
+
+       while (cmd &&
+              state->skip_broken && (cmd->error_string || cmd->did_not_exist))
+               cmd = cmd->next;
+       if (!cmd)
+               return -1; /* EOF */
+       strbuf_reset(&state->buf);
+       strbuf_addf(&state->buf, "%s %s %s\n",
+                   sha1_to_hex(cmd->old_sha1), sha1_to_hex(cmd->new_sha1),
+                   cmd->ref_name);
+       state->cmd = cmd->next;
+       if (bufp) {
+               *bufp = state->buf.buf;
+               *sizep = state->buf.len;
+       }
+       return 0;
+}
+
+static int run_receive_hook(struct command *commands, const char *hook_name,
+                           int skip_broken)
+{
+       struct receive_hook_feed_state state;
+       int status;
+
+       strbuf_init(&state.buf, 0);
+       state.cmd = commands;
+       state.skip_broken = skip_broken;
+       if (feed_receive_hook(&state, NULL, NULL))
+               return 0;
+       state.cmd = commands;
+       status = run_and_feed_hook(hook_name, feed_receive_hook, &state);
+       strbuf_release(&state.buf);
+       return status;
+}
+
 static int run_update_hook(struct command *cmd)
 {
        static const char update_hook[] = "hooks/update";
@@ -363,7 +401,7 @@ static const char *update(struct command *cmd)
        struct ref_lock *lock;
 
        /* only refs/... are allowed */
-       if (prefixcmp(name, "refs/") || check_ref_format(name + 5)) {
+       if (prefixcmp(name, "refs/") || check_refname_format(name + 5, 0)) {
                rp_error("refusing to create funny ref '%s' remotely", name);
                return "funny refname";
        }
@@ -452,8 +490,13 @@ static const char *update(struct command *cmd)
 
        if (is_null_sha1(new_sha1)) {
                if (!parse_object(old_sha1)) {
-                       rp_warning("Allowing deletion of corrupt ref.");
                        old_sha1 = NULL;
+                       if (ref_exists(name)) {
+                               rp_warning("Allowing deletion of corrupt ref.");
+                       } else {
+                               rp_warning("Deleting a non-existent ref.");
+                               cmd->did_not_exist = 1;
+                       }
                }
                if (delete_ref(namespaced_name, old_sha1, 0)) {
                        rp_error("failed to delete %s", name);
@@ -484,7 +527,7 @@ static void run_update_post_hook(struct command *commands)
        struct child_process proc;
 
        for (argc = 0, cmd = commands; cmd; cmd = cmd->next) {
-               if (cmd->error_string)
+               if (cmd->error_string || cmd->did_not_exist)
                        continue;
                argc++;
        }
@@ -495,7 +538,7 @@ static void run_update_post_hook(struct command *commands)
 
        for (argc = 1, cmd = commands; cmd; cmd = cmd->next) {
                char *p;
-               if (cmd->error_string)
+               if (cmd->error_string || cmd->did_not_exist)
                        continue;
                p = xmalloc(strlen(cmd->ref_name) + 1);
                strcpy(p, cmd->ref_name);
@@ -591,7 +634,7 @@ static int command_singleton_iterator(void *cb_data, unsigned char sha1[20])
        struct command **cmd_list = cb_data;
        struct command *cmd = *cmd_list;
 
-       if (!cmd)
+       if (!cmd || is_null_sha1(cmd->new_sha1))
                return -1; /* end of list */
        *cmd_list = NULL; /* this returns only one */
        hashcpy(sha1, cmd->new_sha1);
@@ -616,11 +659,16 @@ static int iterate_receive_command_list(void *cb_data, unsigned char sha1[20])
        struct command **cmd_list = cb_data;
        struct command *cmd = *cmd_list;
 
-       if (!cmd)
-               return -1; /* end of list */
-       *cmd_list = cmd->next;
-       hashcpy(sha1, cmd->new_sha1);
-       return 0;
+       while (cmd) {
+               if (!is_null_sha1(cmd->new_sha1)) {
+                       hashcpy(sha1, cmd->new_sha1);
+                       *cmd_list = cmd->next;
+                       return 0;
+               }
+               cmd = cmd->next;
+       }
+       *cmd_list = NULL;
+       return -1; /* end of list */
 }
 
 static void execute_commands(struct command *commands, const char *unpacker_error)
@@ -639,7 +687,7 @@ static void execute_commands(struct command *commands, const char *unpacker_erro
                                       0, &cmd))
                set_connectivity_errors(commands);
 
-       if (run_receive_hook(commands, pre_receive_hook)) {
+       if (run_receive_hook(commands, pre_receive_hook, 0)) {
                for (cmd = commands; cmd; cmd = cmd->next)
                        cmd->error_string = "pre-receive hook declined";
                return;
@@ -907,7 +955,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
                        unlink_or_warn(pack_lockfile);
                if (report_status)
                        report(commands, unpack_status);
-               run_receive_hook(commands, post_receive_hook);
+               run_receive_hook(commands, post_receive_hook, 1);
                run_update_post_hook(commands);
                if (auto_gc) {
                        const char *argv_gc_auto[] = {
index 3a9c80f3dbfe26d5623c118fc0fcaa257e01b973..062d7dad1b5af720e70adcaa05b60bf68977b05c 100644 (file)
@@ -647,7 +647,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
                init_revisions(&cb.revs, prefix);
                if (cb.verbose)
                        printf("Marking reachable objects...");
-               mark_reachable_objects(&cb.revs, 0);
+               mark_reachable_objects(&cb.revs, 0, NULL);
                if (cb.verbose)
                        putchar('\n');
        }
index f2a9c26dc3494c0881ca3f91b44785c4b52caae7..c810643815e38f4ada805e2af49973e4e0051801 100644 (file)
@@ -349,7 +349,8 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat
                else
                        string_list_append(&states->tracked, abbrev_branch(ref->name));
        }
-       stale_refs = get_stale_heads(states->remote, fetch_map);
+       stale_refs = get_stale_heads(states->remote->fetch,
+                                    states->remote->fetch_refspec_nr, fetch_map);
        for (ref = stale_refs; ref; ref = ref->next) {
                struct string_list_item *item =
                        string_list_append(&states->stale, abbrev_branch(ref->name));
@@ -389,8 +390,8 @@ static int get_push_ref_states(const struct ref *remote_refs,
        local_refs = get_local_heads();
        push_map = copy_ref_list(remote_refs);
 
-       match_refs(local_refs, &push_map, remote->push_refspec_nr,
-                  remote->push_refspec, MATCH_REFS_NONE);
+       match_push_refs(local_refs, &push_map, remote->push_refspec_nr,
+                       remote->push_refspec, MATCH_REFS_NONE);
 
        states->push.strdup_strings = 1;
        for (ref = push_map; ref; ref = ref->next) {
@@ -570,7 +571,7 @@ static int read_remote_branches(const char *refname,
        unsigned char orig_sha1[20];
        const char *symref;
 
-       strbuf_addf(&buf, "refs/remotes/%s", rename->old);
+       strbuf_addf(&buf, "refs/remotes/%s/", rename->old);
        if (!prefixcmp(refname, buf.buf)) {
                item = string_list_append(rename->remote_branches, xstrdup(refname));
                symref = resolve_ref(refname, orig_sha1, 1, &flag);
@@ -621,10 +622,11 @@ static int mv(int argc, const char **argv)
                OPT_END()
        };
        struct remote *oldremote, *newremote;
-       struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT, buf3 = STRBUF_INIT;
+       struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT, buf3 = STRBUF_INIT,
+               old_remote_context = STRBUF_INIT;
        struct string_list remote_branches = STRING_LIST_INIT_NODUP;
        struct rename_info rename;
-       int i;
+       int i, refspec_updated = 0;
 
        if (argc != 3)
                usage_with_options(builtin_remote_rename_usage, options);
@@ -659,15 +661,25 @@ static int mv(int argc, const char **argv)
        strbuf_addf(&buf, "remote.%s.fetch", rename.new);
        if (git_config_set_multivar(buf.buf, NULL, NULL, 1))
                return error("Could not remove config section '%s'", buf.buf);
+       strbuf_addf(&old_remote_context, ":refs/remotes/%s/", rename.old);
        for (i = 0; i < oldremote->fetch_refspec_nr; i++) {
                char *ptr;
 
                strbuf_reset(&buf2);
                strbuf_addstr(&buf2, oldremote->fetch_refspec[i]);
-               ptr = strstr(buf2.buf, rename.old);
-               if (ptr)
-                       strbuf_splice(&buf2, ptr-buf2.buf, strlen(rename.old),
-                                       rename.new, strlen(rename.new));
+               ptr = strstr(buf2.buf, old_remote_context.buf);
+               if (ptr) {
+                       refspec_updated = 1;
+                       strbuf_splice(&buf2,
+                                     ptr-buf2.buf + strlen(":refs/remotes/"),
+                                     strlen(rename.old), rename.new,
+                                     strlen(rename.new));
+               } else
+                       warning("Not updating non-default fetch respec\n"
+                               "\t%s\n"
+                               "\tPlease update the configuration manually if necessary.",
+                               buf2.buf);
+
                if (git_config_set_multivar(buf.buf, buf2.buf, "^$", 0))
                        return error("Could not append '%s'", buf.buf);
        }
@@ -685,6 +697,9 @@ static int mv(int argc, const char **argv)
                }
        }
 
+       if (!refspec_updated)
+               return 0;
+
        /*
         * First remove symrefs, then rename the rest, finally create
         * the new symrefs.
@@ -1384,7 +1399,7 @@ static int set_branches(int argc, const char **argv)
                             builtin_remote_setbranches_usage, 0);
        if (argc == 0) {
                error("no remote specified");
-               usage_with_options(builtin_remote_seturl_usage, options);
+               usage_with_options(builtin_remote_setbranches_usage, options);
        }
        argv[argc] = NULL;
 
@@ -1412,7 +1427,7 @@ static int set_url(int argc, const char **argv)
                            "delete URLs"),
                OPT_END()
        };
-       argc = parse_options(argc, argv, NULL, options, builtin_remote_update_usage,
+       argc = parse_options(argc, argv, NULL, options, builtin_remote_seturl_usage,
                             PARSE_OPT_KEEP_ARGV0);
 
        if (add_mode && delete_mode)
index fe3a647a36c9a064d32c8bf9d9868da041bd82a2..517fa1031a86f50c0d41ba567237aa701e9c2c05 100644 (file)
@@ -94,7 +94,7 @@ static int replace_object(const char *object_ref, const char *replace_ref,
                     "refs/replace/%s",
                     sha1_to_hex(object)) > sizeof(ref) - 1)
                die("replace ref name too long: %.*s...", 50, ref);
-       if (check_ref_format(ref))
+       if (check_refname_format(ref, 0))
                die("'%s' is not a valid ref name.", ref);
 
        if (!resolve_ref(ref, prev, 1, NULL))
index 4c19f844a977b6413afcd5df6b4f8bc0875775a8..98d1cbeccacc4b22dee88dab3d0154a3f70afe81 100644 (file)
@@ -468,6 +468,14 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                return 0;
        }
 
+       if (argc > 2 && !strcmp(argv[1], "--resolve-git-dir")) {
+               const char *gitdir = resolve_gitdir(argv[2]);
+               if (!gitdir)
+                       die("not a gitdir '%s'", argv[2]);
+               puts(gitdir);
+               return 0;
+       }
+
        if (argc > 1 && !strcmp("-h", argv[1]))
                usage(builtin_rev_parse_usage);
 
index ba27cf15ee5478981a0a9a53493f41b67fcf9440..1ea525c10e4c00b66006bf46465ebd490823f25f 100644 (file)
@@ -40,7 +40,12 @@ static const char * const cherry_pick_usage[] = {
 };
 
 enum replay_action { REVERT, CHERRY_PICK };
-enum replay_subcommand { REPLAY_NONE, REPLAY_RESET, REPLAY_CONTINUE };
+enum replay_subcommand {
+       REPLAY_NONE,
+       REPLAY_REMOVE_STATE,
+       REPLAY_CONTINUE,
+       REPLAY_ROLLBACK
+};
 
 struct replay_opts {
        enum replay_action action;
@@ -110,7 +115,7 @@ static void verify_opt_compatible(const char *me, const char *base_opt, ...)
 
 static void verify_opt_mutually_compatible(const char *me, ...)
 {
-       const char *opt1, *opt2;
+       const char *opt1, *opt2 = NULL;
        va_list ap;
 
        va_start(ap, me);
@@ -133,16 +138,16 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
 {
        const char * const * usage_str = revert_or_cherry_pick_usage(opts);
        const char *me = action_name(opts);
-       int noop;
-       int reset = 0;
+       int remove_state = 0;
        int contin = 0;
+       int rollback = 0;
        struct option options[] = {
-               OPT_BOOLEAN(0, "reset", &reset, "forget the current operation"),
-               OPT_BOOLEAN(0, "continue", &contin, "continue the current operation"),
+               OPT_BOOLEAN(0, "quit", &remove_state, "end revert or cherry-pick sequence"),
+               OPT_BOOLEAN(0, "continue", &contin, "resume revert or cherry-pick sequence"),
+               OPT_BOOLEAN(0, "abort", &rollback, "cancel revert or cherry-pick sequence"),
                OPT_BOOLEAN('n', "no-commit", &opts->no_commit, "don't automatically commit"),
                OPT_BOOLEAN('e', "edit", &opts->edit, "edit the commit message"),
-               { OPTION_BOOLEAN, 'r', NULL, &noop, NULL, "no-op (backward compatibility)",
-                 PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 0 },
+               OPT_NOOP_NOARG('r', NULL),
                OPT_BOOLEAN('s', "signoff", &opts->signoff, "add Signed-off-by:"),
                OPT_INTEGER('m', "mainline", &opts->mainline, "parent number"),
                OPT_RERERE_AUTOUPDATE(&opts->allow_rerere_auto),
@@ -170,25 +175,32 @@ static void parse_args(int argc, const char **argv, struct replay_opts *opts)
 
        /* Check for incompatible subcommands */
        verify_opt_mutually_compatible(me,
-                               "--reset", reset,
+                               "--quit", remove_state,
                                "--continue", contin,
+                               "--abort", rollback,
                                NULL);
 
        /* Set the subcommand */
-       if (reset)
-               opts->subcommand = REPLAY_RESET;
+       if (remove_state)
+               opts->subcommand = REPLAY_REMOVE_STATE;
        else if (contin)
                opts->subcommand = REPLAY_CONTINUE;
+       else if (rollback)
+               opts->subcommand = REPLAY_ROLLBACK;
        else
                opts->subcommand = REPLAY_NONE;
 
        /* Check for incompatible command line arguments */
        if (opts->subcommand != REPLAY_NONE) {
                char *this_operation;
-               if (opts->subcommand == REPLAY_RESET)
-                       this_operation = "--reset";
-               else
+               if (opts->subcommand == REPLAY_REMOVE_STATE)
+                       this_operation = "--quit";
+               else if (opts->subcommand == REPLAY_CONTINUE)
                        this_operation = "--continue";
+               else {
+                       assert(opts->subcommand == REPLAY_ROLLBACK);
+                       this_operation = "--abort";
+               }
 
                verify_opt_compatible(me, this_operation,
                                "--no-commit", opts->no_commit,
@@ -288,23 +300,24 @@ static char *get_encoding(const char *message)
        return NULL;
 }
 
-static void write_cherry_pick_head(struct commit *commit)
+static void write_cherry_pick_head(struct commit *commit, const char *pseudoref)
 {
+       const char *filename;
        int fd;
        struct strbuf buf = STRBUF_INIT;
 
        strbuf_addf(&buf, "%s\n", sha1_to_hex(commit->object.sha1));
 
-       fd = open(git_path("CHERRY_PICK_HEAD"), O_WRONLY | O_CREAT, 0666);
+       filename = git_path("%s", pseudoref);
+       fd = open(filename, O_WRONLY | O_CREAT, 0666);
        if (fd < 0)
-               die_errno(_("Could not open '%s' for writing"),
-                         git_path("CHERRY_PICK_HEAD"));
+               die_errno(_("Could not open '%s' for writing"), filename);
        if (write_in_full(fd, buf.buf, buf.len) != buf.len || close(fd))
-               die_errno(_("Could not write to '%s'"), git_path("CHERRY_PICK_HEAD"));
+               die_errno(_("Could not write to '%s'"), filename);
        strbuf_release(&buf);
 }
 
-static void print_advice(void)
+static void print_advice(int show_hint)
 {
        char *msg = getenv("GIT_CHERRY_PICK_HELP");
 
@@ -319,9 +332,11 @@ static void print_advice(void)
                return;
        }
 
-       advise("after resolving the conflicts, mark the corrected paths");
-       advise("with 'git add <paths>' or 'git rm <paths>'");
-       advise("and commit the result with 'git commit'");
+       if (show_hint) {
+               advise("after resolving the conflicts, mark the corrected paths");
+               advise("with 'git add <paths>' or 'git rm <paths>'");
+               advise("and commit the result with 'git commit'");
+       }
 }
 
 static void write_message(struct strbuf *msgbuf, const char *filename)
@@ -331,7 +346,7 @@ static void write_message(struct strbuf *msgbuf, const char *filename)
        int msg_fd = hold_lock_file_for_update(&msg_file, filename,
                                               LOCK_DIE_ON_ERROR);
        if (write_in_full(msg_fd, msgbuf->buf, msgbuf->len) < 0)
-               die_errno(_("Could not write to %s."), filename);
+               die_errno(_("Could not write to %s"), filename);
        strbuf_release(msgbuf);
        if (commit_lock_file(&msg_file) < 0)
                die(_("Error wrapping up %s"), filename);
@@ -566,8 +581,6 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
                        strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
                        strbuf_addstr(&msgbuf, ")\n");
                }
-               if (!opts->no_commit)
-                       write_cherry_pick_head(commit);
        }
 
        if (!opts->strategy || !strcmp(opts->strategy, "recursive") || opts->action == REVERT) {
@@ -588,13 +601,24 @@ static int do_pick_commit(struct commit *commit, struct replay_opts *opts)
                free_commit_list(remotes);
        }
 
+       /*
+        * If the merge was clean or if it failed due to conflict, we write
+        * CHERRY_PICK_HEAD for the subsequent invocation of commit to use.
+        * However, if the merge did not even start, then we don't want to
+        * write it at all.
+        */
+       if (opts->action == CHERRY_PICK && !opts->no_commit && (res == 0 || res == 1))
+               write_cherry_pick_head(commit, "CHERRY_PICK_HEAD");
+       if (opts->action == REVERT && ((opts->no_commit && res == 0) || res == 1))
+               write_cherry_pick_head(commit, "REVERT_HEAD");
+
        if (res) {
                error(opts->action == REVERT
                      ? _("could not revert %s... %s")
                      : _("could not apply %s... %s"),
                      find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV),
                      msg.subject);
-               print_advice();
+               print_advice(res == 1);
                rerere(opts->allow_rerere_auto);
        } else {
                if (!opts->no_commit)
@@ -760,7 +784,7 @@ static void read_populate_todo(struct commit_list **todo_list,
 
        fd = open(todo_file, O_RDONLY);
        if (fd < 0)
-               die_errno(_("Could not open %s."), todo_file);
+               die_errno(_("Could not open %s"), todo_file);
        if (strbuf_read(&buf, fd, 0) < 0) {
                close(fd);
                strbuf_release(&buf);
@@ -835,10 +859,13 @@ static int create_seq_dir(void)
 {
        const char *seq_dir = git_path(SEQ_DIR);
 
-       if (file_exists(seq_dir))
-               return error(_("%s already exists."), seq_dir);
+       if (file_exists(seq_dir)) {
+               error(_("a cherry-pick or revert is already in progress"));
+               advise(_("try \"git cherry-pick (--continue | --quit | --abort)\""));
+               return -1;
+       }
        else if (mkdir(seq_dir, 0777) < 0)
-               die_errno(_("Could not create sequencer directory '%s'."), seq_dir);
+               die_errno(_("Could not create sequencer directory %s"), seq_dir);
        return 0;
 }
 
@@ -852,11 +879,77 @@ static void save_head(const char *head)
        fd = hold_lock_file_for_update(&head_lock, head_file, LOCK_DIE_ON_ERROR);
        strbuf_addf(&buf, "%s\n", head);
        if (write_in_full(fd, buf.buf, buf.len) < 0)
-               die_errno(_("Could not write to %s."), head_file);
+               die_errno(_("Could not write to %s"), head_file);
        if (commit_lock_file(&head_lock) < 0)
                die(_("Error wrapping up %s."), head_file);
 }
 
+static int reset_for_rollback(const unsigned char *sha1)
+{
+       const char *argv[4];    /* reset --merge <arg> + NULL */
+       argv[0] = "reset";
+       argv[1] = "--merge";
+       argv[2] = sha1_to_hex(sha1);
+       argv[3] = NULL;
+       return run_command_v_opt(argv, RUN_GIT_CMD);
+}
+
+static int rollback_single_pick(void)
+{
+       unsigned char head_sha1[20];
+
+       if (!file_exists(git_path("CHERRY_PICK_HEAD")) &&
+           !file_exists(git_path("REVERT_HEAD")))
+               return error(_("no cherry-pick or revert in progress"));
+       if (!resolve_ref("HEAD", head_sha1, 0, NULL))
+               return error(_("cannot resolve HEAD"));
+       if (is_null_sha1(head_sha1))
+               return error(_("cannot abort from a branch yet to be born"));
+       return reset_for_rollback(head_sha1);
+}
+
+static int sequencer_rollback(struct replay_opts *opts)
+{
+       const char *filename;
+       FILE *f;
+       unsigned char sha1[20];
+       struct strbuf buf = STRBUF_INIT;
+
+       filename = git_path(SEQ_HEAD_FILE);
+       f = fopen(filename, "r");
+       if (!f && errno == ENOENT) {
+               /*
+                * There is no multiple-cherry-pick in progress.
+                * If CHERRY_PICK_HEAD or REVERT_HEAD indicates
+                * a single-cherry-pick in progress, abort that.
+                */
+               return rollback_single_pick();
+       }
+       if (!f)
+               return error(_("cannot open %s: %s"), filename,
+                                               strerror(errno));
+       if (strbuf_getline(&buf, f, '\n')) {
+               error(_("cannot read %s: %s"), filename, ferror(f) ?
+                       strerror(errno) : _("unexpected end of file"));
+               fclose(f);
+               goto fail;
+       }
+       fclose(f);
+       if (get_sha1_hex(buf.buf, sha1) || buf.buf[40] != '\0') {
+               error(_("stored pre-cherry-pick HEAD file '%s' is corrupt"),
+                       filename);
+               goto fail;
+       }
+       if (reset_for_rollback(sha1))
+               goto fail;
+       remove_sequencer_state(1);
+       strbuf_release(&buf);
+       return 0;
+fail:
+       strbuf_release(&buf);
+       return -1;
+}
+
 static void save_todo(struct commit_list *todo_list, struct replay_opts *opts)
 {
        const char *todo_file = git_path(SEQ_TODO_FILE);
@@ -869,7 +962,7 @@ static void save_todo(struct commit_list *todo_list, struct replay_opts *opts)
                die(_("Could not format %s."), todo_file);
        if (write_in_full(fd, buf.buf, buf.len) < 0) {
                strbuf_release(&buf);
-               die_errno(_("Could not write to %s."), todo_file);
+               die_errno(_("Could not write to %s"), todo_file);
        }
        if (commit_lock_file(&todo_lock) < 0) {
                strbuf_release(&buf);
@@ -957,43 +1050,41 @@ static int pick_revisions(struct replay_opts *opts)
         * cherry-pick should be handled differently from an existing
         * one that is being continued
         */
-       if (opts->subcommand == REPLAY_RESET) {
+       if (opts->subcommand == REPLAY_REMOVE_STATE) {
                remove_sequencer_state(1);
                return 0;
-       } else if (opts->subcommand == REPLAY_CONTINUE) {
+       }
+       if (opts->subcommand == REPLAY_ROLLBACK)
+               return sequencer_rollback(opts);
+       if (opts->subcommand == REPLAY_CONTINUE) {
                if (!file_exists(git_path(SEQ_TODO_FILE)))
-                       goto error;
+                       return error(_("No %s in progress"), action_name(opts));
                read_populate_opts(&opts);
                read_populate_todo(&todo_list, opts);
 
                /* Verify that the conflict has been resolved */
                if (!index_differs_from("HEAD", 0))
                        todo_list = todo_list->next;
-       } else {
-               /*
-                * Start a new cherry-pick/ revert sequence; but
-                * first, make sure that an existing one isn't in
-                * progress
-                */
+               return pick_commits(todo_list, opts);
+       }
 
-               walk_revs_populate_todo(&todo_list, opts);
-               if (create_seq_dir() < 0) {
-                       error(_("A cherry-pick or revert is in progress."));
-                       advise(_("Use --continue to continue the operation"));
-                       advise(_("or --reset to forget about it"));
-                       return -1;
-               }
-               if (get_sha1("HEAD", sha1)) {
-                       if (opts->action == REVERT)
-                               return error(_("Can't revert as initial commit"));
-                       return error(_("Can't cherry-pick into empty head"));
-               }
-               save_head(sha1_to_hex(sha1));
-               save_opts(opts);
+       /*
+        * Start a new cherry-pick/ revert sequence; but
+        * first, make sure that an existing one isn't in
+        * progress
+        */
+
+       walk_revs_populate_todo(&todo_list, opts);
+       if (create_seq_dir() < 0)
+               return -1;
+       if (get_sha1("HEAD", sha1)) {
+               if (opts->action == REVERT)
+                       return error(_("Can't revert as initial commit"));
+               return error(_("Can't cherry-pick into empty head"));
        }
+       save_head(sha1_to_hex(sha1));
+       save_opts(opts);
        return pick_commits(todo_list, opts);
-error:
-       return error(_("No %s in progress"), action_name(opts));
 }
 
 int cmd_revert(int argc, const char **argv, const char *prefix)
index c1f6ddd927d61fbc2558ee3624224af405d918c3..e0b8030f2b31ee337bc2d2e0925788ec6cd92e0f 100644 (file)
@@ -334,7 +334,7 @@ int send_pack(struct send_pack_args *args,
                demux.data = fd;
                demux.out = -1;
                if (start_async(&demux))
-                       die("receive-pack: unable to fork off sideband demultiplexer");
+                       die("send-pack: unable to fork off sideband demultiplexer");
                in = demux.out;
        }
 
@@ -509,7 +509,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
                flags |= MATCH_REFS_MIRROR;
 
        /* match them up */
-       if (match_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags))
+       if (match_push_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags))
                return -1;
 
        set_ref_status_for_push(remote_refs, args.send_mirror,
index 45f0340c3ea004822477ed70fdd14e05dfcd9bb5..fafb6dd50081b3af22bdbcde898d170ef47f679d 100644 (file)
@@ -145,7 +145,7 @@ static int exclude_existing(const char *match)
                        if (strncmp(ref, match, matchlen))
                                continue;
                }
-               if (check_ref_format(ref)) {
+               if (check_refname_format(ref, 0)) {
                        warning("ref '%s' ignored", ref);
                        continue;
                }
index 4d3b93fedb5f2eca68edca15ca1e10cb60176d36..1288ffcc52530f8ef9561acd2eb2ec5322c9e230 100644 (file)
@@ -22,8 +22,6 @@ static size_t cleanup(char *line, size_t len)
  * Remove empty lines from the beginning and end
  * and also trailing spaces from every line.
  *
- * Note that the buffer will not be NUL-terminated.
- *
  * Turn multiple consecutive empty lines between paragraphs
  * into just one empty line.
  *
index 667515e5278d22548fb83507347ab652533a67b2..9b6fd95494fd80240f88d49224199b7e621717e4 100644 (file)
@@ -407,12 +407,12 @@ static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
 static int strbuf_check_tag_ref(struct strbuf *sb, const char *name)
 {
        if (name[0] == '-')
-               return CHECK_REF_FORMAT_ERROR;
+               return -1;
 
        strbuf_reset(sb);
        strbuf_addf(sb, "refs/tags/%s", name);
 
-       return check_ref_format(sb->buf);
+       return check_refname_format(sb->buf, 0);
 }
 
 int cmd_tag(int argc, const char **argv, const char *prefix)
@@ -429,21 +429,21 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
        struct msg_arg msg = { 0, STRBUF_INIT };
        struct commit_list *with_commit = NULL;
        struct option options[] = {
-               OPT_BOOLEAN('l', NULL, &list, "list tag names"),
+               OPT_BOOLEAN('l', "list", &list, "list tag names"),
                { OPTION_INTEGER, 'n', NULL, &lines, "n",
                                "print <n> lines of each tag message",
                                PARSE_OPT_OPTARG, NULL, 1 },
-               OPT_BOOLEAN('d', NULL, &delete, "delete tags"),
-               OPT_BOOLEAN('v', NULL, &verify, "verify tags"),
+               OPT_BOOLEAN('d', "delete", &delete, "delete tags"),
+               OPT_BOOLEAN('v', "verify", &verify, "verify tags"),
 
                OPT_GROUP("Tag creation options"),
-               OPT_BOOLEAN('a', NULL, &annotate,
+               OPT_BOOLEAN('a', "annotate", &annotate,
                                        "annotated tag, needs a message"),
-               OPT_CALLBACK('m', NULL, &msg, "message",
+               OPT_CALLBACK('m', "message", &msg, "message",
                             "tag message", parse_msg_arg),
-               OPT_FILENAME('F', NULL, &msgfile, "read message from file"),
-               OPT_BOOLEAN('s', NULL, &sign, "annotated and GPG-signed tag"),
-               OPT_STRING('u', NULL, &keyid, "key-id",
+               OPT_FILENAME('F', "file", &msgfile, "read message from file"),
+               OPT_BOOLEAN('s', "sign", &sign, "annotated and GPG-signed tag"),
+               OPT_STRING('u', "local-user", &keyid, "key-id",
                                        "use another key to sign the tag"),
                OPT__FORCE(&force, "replace the tag if exists"),
 
index 6bf849740c6de45fe8ca11eca2a06a52c7ebc0bb..08020bc3a258e055a8f5f6974217cf482edc3ee3 100644 (file)
--- a/bundle.c
+++ b/bundle.c
@@ -23,50 +23,102 @@ static void add_to_ref_list(const unsigned char *sha1, const char *name,
        list->nr++;
 }
 
-/* returns an fd */
-int read_bundle_header(const char *path, struct bundle_header *header)
+/* Eventually this should go to strbuf.[ch] */
+static int strbuf_readline_fd(struct strbuf *sb, int fd)
 {
-       char buffer[1024];
-       int fd;
-       long fpos;
-       FILE *ffd = fopen(path, "rb");
+       strbuf_reset(sb);
+
+       while (1) {
+               char ch;
+               ssize_t len = xread(fd, &ch, 1);
+               if (len < 0)
+                       return -1;
+               strbuf_addch(sb, ch);
+               if (ch == '\n')
+                       break;
+       }
+       return 0;
+}
 
-       if (!ffd)
-               return error("could not open '%s'", path);
-       if (!fgets(buffer, sizeof(buffer), ffd) ||
-                       strcmp(buffer, bundle_signature)) {
-               fclose(ffd);
-               return error("'%s' does not look like a v2 bundle file", path);
+static int parse_bundle_header(int fd, struct bundle_header *header,
+                              const char *report_path)
+{
+       struct strbuf buf = STRBUF_INIT;
+       int status = 0;
+
+       /* The bundle header begins with the signature */
+       if (strbuf_readline_fd(&buf, fd) ||
+           strcmp(buf.buf, bundle_signature)) {
+               if (report_path)
+                       error("'%s' does not look like a v2 bundle file",
+                             report_path);
+               status = -1;
+               goto abort;
        }
-       while (fgets(buffer, sizeof(buffer), ffd)
-                       && buffer[0] != '\n') {
-               int is_prereq = buffer[0] == '-';
-               int offset = is_prereq ? 1 : 0;
-               int len = strlen(buffer);
+
+       /* The bundle header ends with an empty line */
+       while (!strbuf_readline_fd(&buf, fd) &&
+              buf.len && buf.buf[0] != '\n') {
                unsigned char sha1[20];
-               struct ref_list *list = is_prereq ? &header->prerequisites
-                       : &header->references;
-               char delim;
-
-               if (len && buffer[len - 1] == '\n')
-                       buffer[len - 1] = '\0';
-               if (get_sha1_hex(buffer + offset, sha1)) {
-                       warning("unrecognized header: %s", buffer);
-                       continue;
+               int is_prereq = 0;
+
+               if (*buf.buf == '-') {
+                       is_prereq = 1;
+                       strbuf_remove(&buf, 0, 1);
+               }
+               strbuf_rtrim(&buf);
+
+               /*
+                * Tip lines have object name, SP, and refname.
+                * Prerequisites have object name that is optionally
+                * followed by SP and subject line.
+                */
+               if (get_sha1_hex(buf.buf, sha1) ||
+                   (40 <= buf.len && !isspace(buf.buf[40])) ||
+                   (!is_prereq && buf.len <= 40)) {
+                       if (report_path)
+                               error("unrecognized header: %s%s (%d)",
+                                     (is_prereq ? "-" : ""), buf.buf, (int)buf.len);
+                       status = -1;
+                       break;
+               } else {
+                       if (is_prereq)
+                               add_to_ref_list(sha1, "", &header->prerequisites);
+                       else
+                               add_to_ref_list(sha1, buf.buf + 41, &header->references);
                }
-               delim = buffer[40 + offset];
-               if (!isspace(delim) && (delim != '\0' || !is_prereq))
-                       die ("invalid header: %s", buffer);
-               add_to_ref_list(sha1, isspace(delim) ?
-                               buffer + 41 + offset : "", list);
        }
-       fpos = ftell(ffd);
-       fclose(ffd);
-       fd = open(path, O_RDONLY);
+
+ abort:
+       if (status) {
+               close(fd);
+               fd = -1;
+       }
+       strbuf_release(&buf);
+       return fd;
+}
+
+int read_bundle_header(const char *path, struct bundle_header *header)
+{
+       int fd = open(path, O_RDONLY);
+
        if (fd < 0)
                return error("could not open '%s'", path);
-       lseek(fd, fpos, SEEK_SET);
-       return fd;
+       return parse_bundle_header(fd, header, path);
+}
+
+int is_bundle(const char *path, int quiet)
+{
+       struct bundle_header header;
+       int fd = open(path, O_RDONLY);
+
+       if (fd < 0)
+               return 0;
+       memset(&header, 0, sizeof(header));
+       fd = parse_bundle_header(fd, &header, quiet ? NULL : path);
+       if (fd >= 0)
+               close(fd);
+       return (fd >= 0);
 }
 
 static int list_refs(struct ref_list *r, int argc, const char **argv)
@@ -122,11 +174,8 @@ int verify_bundle(struct bundle_header *header, int verbose)
        req_nr = revs.pending.nr;
        setup_revisions(2, argv, &revs, NULL);
 
-       memset(&refs, 0, sizeof(struct object_array));
-       for (i = 0; i < revs.pending.nr; i++) {
-               struct object_array_entry *e = revs.pending.objects + i;
-               add_object_array(e->item, e->name, &refs);
-       }
+       refs = revs.pending;
+       revs.leak_pending = 1;
 
        if (prepare_revision_walk(&revs))
                die("revision walk setup failed");
@@ -144,8 +193,8 @@ int verify_bundle(struct bundle_header *header, int verbose)
                                refs.objects[i].name);
                }
 
-       for (i = 0; i < refs.nr; i++)
-               clear_commit_marks((struct commit *)refs.objects[i].item, -1);
+       clear_commit_marks_for_object_array(&refs, ALL_REV_FLAGS);
+       free(refs.objects);
 
        if (verbose) {
                struct ref_list *r;
index c5a22c8d10c7bf5e27536a3b84033b77f3542445..1584e4d821440d525f10515d6a07c6506b732fec 100644 (file)
--- a/bundle.h
+++ b/bundle.h
@@ -14,6 +14,7 @@ struct bundle_header {
        struct ref_list references;
 };
 
+int is_bundle(const char *path, int quiet);
 int read_bundle_header(const char *path, struct bundle_header *header);
 int create_bundle(struct bundle_header *header, const char *path,
                int argc, const char **argv);
diff --git a/cache.h b/cache.h
index 82e12c862c0ed4fb0da7fb86fc3cef26558810dc..2e6ad3604e4cc6efd60a69407159967a1940e680 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -168,6 +168,7 @@ struct cache_entry {
        unsigned int ce_flags;
        unsigned char sha1[20];
        struct cache_entry *next;
+       struct cache_entry *dir_next;
        char name[FLEX_ARRAY]; /* more */
 };
 
@@ -439,12 +440,12 @@ extern const char *get_git_namespace(void);
 extern const char *strip_namespace(const char *namespaced_ref);
 extern const char *get_git_work_tree(void);
 extern const char *read_gitfile(const char *path);
+extern const char *resolve_gitdir(const char *suspect);
 extern void set_git_work_tree(const char *tree);
 
 #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
 
 extern const char **get_pathspec(const char *prefix, const char **pathspec);
-extern const char *pathspec_prefix(const char *prefix, const char **pathspec);
 extern void setup_work_tree(void);
 extern const char *setup_git_directory_gently(int *);
 extern const char *setup_git_directory(void);
@@ -589,6 +590,7 @@ extern int warn_ambiguous_refs;
 extern int shared_repository;
 extern const char *apply_default_whitespace;
 extern const char *apply_default_ignorewhitespace;
+extern const char *git_attributes_file;
 extern int zlib_compression_level;
 extern int core_compression_level;
 extern int core_compression_seen;
@@ -734,7 +736,7 @@ int safe_create_leading_directories(char *path);
 int safe_create_leading_directories_const(const char *path);
 int mkdir_in_gitdir(const char *path);
 extern char *expand_user_path(const char *path);
-char *enter_repo(char *path, int strict);
+const char *enter_repo(const char *path, int strict);
 static inline int is_absolute_path(const char *path)
 {
        return is_dir_sep(path[0]) || has_dos_drive_prefix(path);
@@ -819,10 +821,51 @@ static inline int get_sha1_with_context(const char *str, unsigned char *sha1, st
 {
        return get_sha1_with_context_1(str, sha1, orc, 0, NULL);
 }
+
+/*
+ * Try to read a SHA1 in hexadecimal format from the 40 characters
+ * starting at hex.  Write the 20-byte result to sha1 in binary form.
+ * Return 0 on success.  Reading stops if a NUL is encountered in the
+ * input, so it is safe to pass this function an arbitrary
+ * null-terminated string.
+ */
 extern int get_sha1_hex(const char *hex, unsigned char *sha1);
+
 extern char *sha1_to_hex(const unsigned char *sha1);   /* static buffer result! */
 extern int read_ref(const char *filename, unsigned char *sha1);
-extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *);
+
+/*
+ * Resolve a reference, recursively following symbolic refererences.
+ *
+ * Store the referred-to object's name in sha1 and return the name of
+ * the non-symbolic reference that ultimately pointed at it.  The
+ * return value, if not NULL, is a pointer into either a static buffer
+ * or the input ref.
+ *
+ * If the reference cannot be resolved to an object, the behavior
+ * depends on the "reading" argument:
+ *
+ * - If reading is set, return NULL.
+ *
+ * - If reading is not set, clear sha1 and return the name of the last
+ *   reference name in the chain, which will either be a non-symbolic
+ *   reference or an undefined reference.  If this is a prelude to
+ *   "writing" to the ref, the return value is the name of the ref
+ *   that will actually be created or changed.
+ *
+ * If flag is non-NULL, set the value that it points to the
+ * combination of REF_ISPACKED (if the reference was found among the
+ * packed references) and REF_ISSYMREF (if the initial reference was a
+ * symbolic reference).
+ *
+ * If ref is not a properly-formatted, normalized reference, return
+ * NULL.  If more than MAXDEPTH recursive symbolic lookups are needed,
+ * give up and return NULL.
+ *
+ * errno is sometimes set on errors, but not always.
+ */
+extern const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *flag);
+
 extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
 extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
 extern int interpret_branch_name(const char *str, struct strbuf *);
@@ -1014,6 +1057,7 @@ extern struct packed_git *add_packed_git(const char *, int, int);
 extern const unsigned char *nth_packed_object_sha1(struct packed_git *, uint32_t);
 extern off_t nth_packed_object_offset(const struct packed_git *, uint32_t);
 extern off_t find_pack_entry_one(const unsigned char *, struct packed_git *);
+extern int is_pack_valid(struct packed_git *);
 extern void *unpack_entry(struct packed_git *, off_t, enum object_type *, unsigned long *);
 extern unsigned long unpack_object_header_buffer(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep);
 extern unsigned long get_size_from_delta(struct packed_git *, struct pack_window **, off_t);
index 95bf18cf06b7c77789e68bd8ab0a312395fb5d86..a36ee9b0150278e8fc4b61e7226df18409b334be 100644 (file)
@@ -130,5 +130,6 @@ git-upload-pack                         synchelpers
 git-var                                 plumbinginterrogators
 git-verify-pack                         plumbinginterrogators
 git-verify-tag                          ancillaryinterrogators
+gitweb                                  ancillaryinterrogators
 git-whatchanged                         ancillaryinterrogators
 git-write-tree                          plumbingmanipulators
index 97b43279cdf46159462d5f56abc20f2161f4c7ea..73b7e00292ba2de33fa43b5f028fd807a460af34 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -39,6 +39,18 @@ struct commit *lookup_commit_reference(const unsigned char *sha1)
        return lookup_commit_reference_gently(sha1, 0);
 }
 
+struct commit *lookup_commit_or_die(const unsigned char *sha1, const char *ref_name)
+{
+       struct commit *c = lookup_commit_reference(sha1);
+       if (!c)
+               die(_("could not parse %s"), ref_name);
+       if (hashcmp(sha1, c->object.sha1)) {
+               warning(_("%s %s is not a commit!"),
+                       ref_name, sha1_to_hex(sha1));
+       }
+       return c;
+}
+
 struct commit *lookup_commit(const unsigned char *sha1)
 {
        struct object *obj = lookup_object(sha1);
@@ -430,6 +442,20 @@ void clear_commit_marks(struct commit *commit, unsigned int mark)
        }
 }
 
+void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark)
+{
+       struct object *object;
+       struct commit *commit;
+       unsigned int i;
+
+       for (i = 0; i < a->nr; i++) {
+               object = a->objects[i].item;
+               commit = lookup_commit_reference_gently(object->sha1, 1);
+               if (commit)
+                       clear_commit_marks(commit, mark);
+       }
+}
+
 struct commit *pop_commit(struct commit_list **stack)
 {
        struct commit_list *top = *stack;
index 12d100b8b6fcd092f3a6886a75c720011ef1b7dc..009b113e5bb5d04bdfb116897cc17dc5f5a2fa9c 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -38,6 +38,13 @@ struct commit *lookup_commit_reference_gently(const unsigned char *sha1,
                                              int quiet);
 struct commit *lookup_commit_reference_by_name(const char *name);
 
+/*
+ * Look up object named by "sha1", dereference tag as necessary,
+ * get a commit and return it. If "sha1" does not dereference to
+ * a commit, use ref_name to report an error and die.
+ */
+struct commit *lookup_commit_or_die(const unsigned char *sha1, const char *ref_name);
+
 int parse_commit_buffer(struct commit *item, const void *buffer, unsigned long size);
 int parse_commit(struct commit *item);
 
@@ -126,6 +133,7 @@ struct commit *pop_most_recent_commit(struct commit_list **list,
 struct commit *pop_commit(struct commit_list **stack);
 
 void clear_commit_marks(struct commit *commit, unsigned int mark);
+void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark);
 
 /*
  * Performs an in-place topological sort of list supplied.
index ea249c6ac6423fd4ef865c1a9d0149ac0ba0cc46..60b5a1d0f8262baca70247923a399fdb4ee3cac1 100644 (file)
@@ -98,7 +98,9 @@ inet_ntop6(const u_char *src, char *dst, size_t size)
        for (i = 0; i < NS_IN6ADDRSZ; i++)
                words[i / 2] |= (src[i] << ((1 - (i % 2)) << 3));
        best.base = -1;
+       best.len = 0;
        cur.base = -1;
+       cur.len = 0;
        for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) {
                if (words[i] == 0) {
                        if (cur.base == -1)
index 6ef0cc4f99becd772a6fed1cfe2484a67b3a9000..efdc703257c9589018e59dc5bc93e9980e0f30b6 100644 (file)
@@ -1183,7 +1183,7 @@ static int WSAAPI getaddrinfo_stub(const char *node, const char *service,
        }
        ai->ai_addrlen = sizeof(struct sockaddr_in);
        if (hints && (hints->ai_flags & AI_CANONNAME))
-               ai->ai_canonname = h ? strdup(h->h_name) : NULL;
+               ai->ai_canonname = h ? xstrdup(h->h_name) : NULL;
        else
                ai->ai_canonname = NULL;
 
@@ -1321,6 +1321,13 @@ static void ensure_socket_initialization(void)
        initialized = 1;
 }
 
+#undef gethostname
+int mingw_gethostname(char *name, int namelen)
+{
+    ensure_socket_initialization();
+    return gethostname(name, namelen);
+}
+
 #undef gethostbyname
 struct hostent *mingw_gethostbyname(const char *host)
 {
index ce9dd980eb211c0301c4008249c61fb0b09f1280..fecf0d07760d2211fac6db2e19ecbedaac81c57e 100644 (file)
@@ -190,6 +190,9 @@ char *mingw_getcwd(char *pointer, int len);
 char *mingw_getenv(const char *name);
 #define getenv mingw_getenv
 
+int mingw_gethostname(char *host, int namelen);
+#define gethostname mingw_gethostname
+
 struct hostent *mingw_gethostbyname(const char *host);
 #define gethostbyname mingw_gethostbyname
 
index a33b01c032b1ab948d87b29447ad0787c86f14f1..aa4b56315ae2488ea656d695f0f660e4dbd745ef 100644 (file)
@@ -4,6 +4,7 @@
 #include <direct.h>
 #include <process.h>
 #include <malloc.h>
+#include <io.h>
 
 /* porting function */
 #define inline __inline
index a89ab5b8e88d5641f9e5cca5b3f9681d12069d39..e276ccd7b38faf3d96778c300ebca0d0a22fe410 100644 (file)
@@ -173,7 +173,7 @@ _obstack_begin (struct obstack *h,
                                               alignment - 1);
   h->chunk_limit = chunk->limit
     = (char *) chunk + h->chunk_size;
-  chunk->prev = 0;
+  chunk->prev = NULL;
   /* The initial chunk now contains no empty object.  */
   h->maybe_empty_object = 0;
   h->alloc_failed = 0;
@@ -221,7 +221,7 @@ _obstack_begin_1 (struct obstack *h, int size, int alignment,
                                               alignment - 1);
   h->chunk_limit = chunk->limit
     = (char *) chunk + h->chunk_size;
-  chunk->prev = 0;
+  chunk->prev = NULL;
   /* The initial chunk now contains no empty object.  */
   h->maybe_empty_object = 0;
   h->alloc_failed = 0;
@@ -321,12 +321,12 @@ _obstack_allocated_p (struct obstack *h, void *obj)
   /* We use >= rather than > since the object cannot be exactly at
      the beginning of the chunk but might be an empty object exactly
      at the end of an adjacent chunk.  */
-  while (lp != 0 && ((void *) lp >= obj || (void *) (lp)->limit < obj))
+  while (lp != NULL && ((void *) lp >= obj || (void *) (lp)->limit < obj))
     {
       plp = lp->prev;
       lp = plp;
     }
-  return lp != 0;
+  return lp != NULL;
 }
 \f
 /* Free objects in obstack H, including OBJ and everything allocate
@@ -344,7 +344,7 @@ obstack_free (struct obstack *h, void *obj)
   /* We use >= because there cannot be an object at the beginning of a chunk.
      But there can be an empty object at that address
      at the end of another chunk.  */
-  while (lp != 0 && ((void *) lp >= obj || (void *) (lp)->limit < obj))
+  while (lp != NULL && ((void *) lp >= obj || (void *) (lp)->limit < obj))
     {
       plp = lp->prev;
       CALL_FREEFUN (h, lp);
@@ -359,7 +359,7 @@ obstack_free (struct obstack *h, void *obj)
       h->chunk_limit = lp->limit;
       h->chunk = lp;
     }
-  else if (obj != 0)
+  else if (obj != NULL)
     /* obj is not in any of the chunks! */
     abort ();
 }
@@ -376,7 +376,7 @@ _obstack_memory_used (struct obstack *h)
   register struct _obstack_chunk* lp;
   register int nbytes = 0;
 
-  for (lp = h->chunk; lp != 0; lp = lp->prev)
+  for (lp = h->chunk; lp != NULL; lp = lp->prev)
     {
       nbytes += lp->limit - (char *) lp;
     }
@@ -395,7 +395,6 @@ _obstack_memory_used (struct obstack *h)
 # endif
 
 static void
-__attribute__ ((noreturn))
 print_and_abort (void)
 {
   /* Don't change any of these strings.  Yes, it would be possible to add
index d93dce2cf8fa33bd1fbefe131d2e41a6b954da61..9574d537bd38d3aa72ee040b106456088ee16a07 100644 (file)
@@ -55,7 +55,7 @@ void git_qsort(void *b, size_t n, size_t s,
                msort_with_tmp(b, n, s, cmp, buf);
        } else {
                /* It's somewhat large, so malloc it.  */
-               char *tmp = malloc(size);
+               char *tmp = xmalloc(size);
                msort_with_tmp(b, n, s, cmp, tmp);
                free(tmp);
        }
diff --git a/compat/strtoimax.c b/compat/strtoimax.c
new file mode 100644 (file)
index 0000000..ac09ed8
--- /dev/null
@@ -0,0 +1,10 @@
+#include "../git-compat-util.h"
+
+intmax_t gitstrtoimax (const char *nptr, char **endptr, int base)
+{
+#if defined(NO_STRTOULL)
+       return strtol(nptr, endptr, base);
+#else
+       return strtoll(nptr, endptr, base);
+#endif
+}
diff --git a/compat/vcbuild/include/arpa/inet.h b/compat/vcbuild/include/arpa/inet.h
deleted file mode 100644 (file)
index 0d8552a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/grp.h b/compat/vcbuild/include/grp.h
deleted file mode 100644 (file)
index 0d8552a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/inttypes.h b/compat/vcbuild/include/inttypes.h
deleted file mode 100644 (file)
index 0d8552a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/netdb.h b/compat/vcbuild/include/netdb.h
deleted file mode 100644 (file)
index 0d8552a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/netinet/in.h b/compat/vcbuild/include/netinet/in.h
deleted file mode 100644 (file)
index 0d8552a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/netinet/tcp.h b/compat/vcbuild/include/netinet/tcp.h
deleted file mode 100644 (file)
index 0d8552a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/pwd.h b/compat/vcbuild/include/pwd.h
deleted file mode 100644 (file)
index 0d8552a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/sys/ioctl.h b/compat/vcbuild/include/sys/ioctl.h
deleted file mode 100644 (file)
index 0d8552a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/sys/select.h b/compat/vcbuild/include/sys/select.h
deleted file mode 100644 (file)
index 0d8552a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/sys/socket.h b/compat/vcbuild/include/sys/socket.h
deleted file mode 100644 (file)
index 0d8552a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/sys/wait.h b/compat/vcbuild/include/sys/wait.h
deleted file mode 100644 (file)
index 0d8552a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/vcbuild/include/termios.h b/compat/vcbuild/include/termios.h
deleted file mode 100644 (file)
index 0d8552a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/* Intentionally empty file to support building git with MSVC */
diff --git a/compat/win32/poll.c b/compat/win32/poll.c
new file mode 100644 (file)
index 0000000..403eaa7
--- /dev/null
@@ -0,0 +1,606 @@
+/* Emulation for poll(2)
+   Contributed by Paolo Bonzini.
+
+   Copyright 2001-2003, 2006-2011 Free Software Foundation, Inc.
+
+   This file is part of gnulib.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License along
+   with this program; if not, write to the Free Software Foundation,
+   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
+
+/* Tell gcc not to warn about the (nfd < 0) tests, below.  */
+#if (__GNUC__ == 4 && 3 <= __GNUC_MINOR__) || 4 < __GNUC__
+# pragma GCC diagnostic ignored "-Wtype-limits"
+#endif
+
+#include <malloc.h>
+
+#include <sys/types.h>
+
+/* Specification.  */
+#include <poll.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <assert.h>
+
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+# define WIN32_NATIVE
+# if defined (_MSC_VER)
+#  define _WIN32_WINNT 0x0502
+# endif
+# include <winsock2.h>
+# include <windows.h>
+# include <io.h>
+# include <stdio.h>
+# include <conio.h>
+#else
+# include <sys/time.h>
+# include <sys/socket.h>
+# include <sys/select.h>
+# include <unistd.h>
+#endif
+
+#ifdef HAVE_SYS_IOCTL_H
+# include <sys/ioctl.h>
+#endif
+#ifdef HAVE_SYS_FILIO_H
+# include <sys/filio.h>
+#endif
+
+#include <time.h>
+
+#ifndef INFTIM
+# define INFTIM (-1)
+#endif
+
+/* BeOS does not have MSG_PEEK.  */
+#ifndef MSG_PEEK
+# define MSG_PEEK 0
+#endif
+
+#ifdef WIN32_NATIVE
+
+#define IsConsoleHandle(h) (((long) (h) & 3) == 3)
+
+static BOOL
+IsSocketHandle (HANDLE h)
+{
+  WSANETWORKEVENTS ev;
+
+  if (IsConsoleHandle (h))
+    return FALSE;
+
+  /* Under Wine, it seems that getsockopt returns 0 for pipes too.
+     WSAEnumNetworkEvents instead distinguishes the two correctly.  */
+  ev.lNetworkEvents = 0xDEADBEEF;
+  WSAEnumNetworkEvents ((SOCKET) h, NULL, &ev);
+  return ev.lNetworkEvents != 0xDEADBEEF;
+}
+
+/* Declare data structures for ntdll functions.  */
+typedef struct _FILE_PIPE_LOCAL_INFORMATION {
+  ULONG NamedPipeType;
+  ULONG NamedPipeConfiguration;
+  ULONG MaximumInstances;
+  ULONG CurrentInstances;
+  ULONG InboundQuota;
+  ULONG ReadDataAvailable;
+  ULONG OutboundQuota;
+  ULONG WriteQuotaAvailable;
+  ULONG NamedPipeState;
+  ULONG NamedPipeEnd;
+} FILE_PIPE_LOCAL_INFORMATION, *PFILE_PIPE_LOCAL_INFORMATION;
+
+typedef struct _IO_STATUS_BLOCK
+{
+  union {
+    DWORD Status;
+    PVOID Pointer;
+  } u;
+  ULONG_PTR Information;
+} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
+
+typedef enum _FILE_INFORMATION_CLASS {
+  FilePipeLocalInformation = 24
+} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS;
+
+typedef DWORD (WINAPI *PNtQueryInformationFile)
+        (HANDLE, IO_STATUS_BLOCK *, VOID *, ULONG, FILE_INFORMATION_CLASS);
+
+# ifndef PIPE_BUF
+#  define PIPE_BUF      512
+# endif
+
+/* Compute revents values for file handle H.  If some events cannot happen
+   for the handle, eliminate them from *P_SOUGHT.  */
+
+static int
+win32_compute_revents (HANDLE h, int *p_sought)
+{
+  int i, ret, happened;
+  INPUT_RECORD *irbuffer;
+  DWORD avail, nbuffer;
+  BOOL bRet;
+  IO_STATUS_BLOCK iosb;
+  FILE_PIPE_LOCAL_INFORMATION fpli;
+  static PNtQueryInformationFile NtQueryInformationFile;
+  static BOOL once_only;
+
+  switch (GetFileType (h))
+    {
+    case FILE_TYPE_PIPE:
+      if (!once_only)
+       {
+         NtQueryInformationFile = (PNtQueryInformationFile)
+           GetProcAddress (GetModuleHandle ("ntdll.dll"),
+                           "NtQueryInformationFile");
+         once_only = TRUE;
+       }
+
+      happened = 0;
+      if (PeekNamedPipe (h, NULL, 0, NULL, &avail, NULL) != 0)
+       {
+         if (avail)
+           happened |= *p_sought & (POLLIN | POLLRDNORM);
+       }
+      else if (GetLastError () == ERROR_BROKEN_PIPE)
+       happened |= POLLHUP;
+
+      else
+       {
+         /* It was the write-end of the pipe.  Check if it is writable.
+            If NtQueryInformationFile fails, optimistically assume the pipe is
+            writable.  This could happen on Win9x, where NtQueryInformationFile
+            is not available, or if we inherit a pipe that doesn't permit
+            FILE_READ_ATTRIBUTES access on the write end (I think this should
+            not happen since WinXP SP2; WINE seems fine too).  Otherwise,
+            ensure that enough space is available for atomic writes.  */
+         memset (&iosb, 0, sizeof (iosb));
+         memset (&fpli, 0, sizeof (fpli));
+
+         if (!NtQueryInformationFile
+             || NtQueryInformationFile (h, &iosb, &fpli, sizeof (fpli),
+                                        FilePipeLocalInformation)
+             || fpli.WriteQuotaAvailable >= PIPE_BUF
+             || (fpli.OutboundQuota < PIPE_BUF &&
+                 fpli.WriteQuotaAvailable == fpli.OutboundQuota))
+           happened |= *p_sought & (POLLOUT | POLLWRNORM | POLLWRBAND);
+       }
+      return happened;
+
+    case FILE_TYPE_CHAR:
+      ret = WaitForSingleObject (h, 0);
+      if (!IsConsoleHandle (h))
+       return ret == WAIT_OBJECT_0 ? *p_sought & ~(POLLPRI | POLLRDBAND) : 0;
+
+      nbuffer = avail = 0;
+      bRet = GetNumberOfConsoleInputEvents (h, &nbuffer);
+      if (bRet)
+       {
+         /* Input buffer.  */
+         *p_sought &= POLLIN | POLLRDNORM;
+         if (nbuffer == 0)
+           return POLLHUP;
+         if (!*p_sought)
+           return 0;
+
+         irbuffer = (INPUT_RECORD *) alloca (nbuffer * sizeof (INPUT_RECORD));
+         bRet = PeekConsoleInput (h, irbuffer, nbuffer, &avail);
+         if (!bRet || avail == 0)
+           return POLLHUP;
+
+         for (i = 0; i < avail; i++)
+           if (irbuffer[i].EventType == KEY_EVENT)
+             return *p_sought;
+         return 0;
+       }
+      else
+       {
+         /* Screen buffer.  */
+         *p_sought &= POLLOUT | POLLWRNORM | POLLWRBAND;
+         return *p_sought;
+       }
+
+    default:
+      ret = WaitForSingleObject (h, 0);
+      if (ret == WAIT_OBJECT_0)
+       return *p_sought & ~(POLLPRI | POLLRDBAND);
+
+      return *p_sought & (POLLOUT | POLLWRNORM | POLLWRBAND);
+    }
+}
+
+/* Convert fd_sets returned by select into revents values.  */
+
+static int
+win32_compute_revents_socket (SOCKET h, int sought, long lNetworkEvents)
+{
+  int happened = 0;
+
+  if ((lNetworkEvents & (FD_READ | FD_ACCEPT | FD_CLOSE)) == FD_ACCEPT)
+    happened |= (POLLIN | POLLRDNORM) & sought;
+
+  else if (lNetworkEvents & (FD_READ | FD_ACCEPT | FD_CLOSE))
+    {
+      int r, error;
+
+      char data[64];
+      WSASetLastError (0);
+      r = recv (h, data, sizeof (data), MSG_PEEK);
+      error = WSAGetLastError ();
+      WSASetLastError (0);
+
+      if (r > 0 || error == WSAENOTCONN)
+       happened |= (POLLIN | POLLRDNORM) & sought;
+
+      /* Distinguish hung-up sockets from other errors.  */
+      else if (r == 0 || error == WSAESHUTDOWN || error == WSAECONNRESET
+              || error == WSAECONNABORTED || error == WSAENETRESET)
+       happened |= POLLHUP;
+
+      else
+       happened |= POLLERR;
+    }
+
+  if (lNetworkEvents & (FD_WRITE | FD_CONNECT))
+    happened |= (POLLOUT | POLLWRNORM | POLLWRBAND) & sought;
+
+  if (lNetworkEvents & FD_OOB)
+    happened |= (POLLPRI | POLLRDBAND) & sought;
+
+  return happened;
+}
+
+#else /* !MinGW */
+
+/* Convert select(2) returned fd_sets into poll(2) revents values.  */
+static int
+compute_revents (int fd, int sought, fd_set *rfds, fd_set *wfds, fd_set *efds)
+{
+  int happened = 0;
+  if (FD_ISSET (fd, rfds))
+    {
+      int r;
+      int socket_errno;
+
+# if defined __MACH__ && defined __APPLE__
+      /* There is a bug in Mac OS X that causes it to ignore MSG_PEEK
+        for some kinds of descriptors.  Detect if this descriptor is a
+        connected socket, a server socket, or something else using a
+        0-byte recv, and use ioctl(2) to detect POLLHUP.  */
+      r = recv (fd, NULL, 0, MSG_PEEK);
+      socket_errno = (r < 0) ? errno : 0;
+      if (r == 0 || socket_errno == ENOTSOCK)
+       ioctl (fd, FIONREAD, &r);
+# else
+      char data[64];
+      r = recv (fd, data, sizeof (data), MSG_PEEK);
+      socket_errno = (r < 0) ? errno : 0;
+# endif
+      if (r == 0)
+       happened |= POLLHUP;
+
+      /* If the event happened on an unconnected server socket,
+        that's fine. */
+      else if (r > 0 || ( /* (r == -1) && */ socket_errno == ENOTCONN))
+       happened |= (POLLIN | POLLRDNORM) & sought;
+
+      /* Distinguish hung-up sockets from other errors.  */
+      else if (socket_errno == ESHUTDOWN || socket_errno == ECONNRESET
+              || socket_errno == ECONNABORTED || socket_errno == ENETRESET)
+       happened |= POLLHUP;
+
+      else
+       happened |= POLLERR;
+    }
+
+  if (FD_ISSET (fd, wfds))
+    happened |= (POLLOUT | POLLWRNORM | POLLWRBAND) & sought;
+
+  if (FD_ISSET (fd, efds))
+    happened |= (POLLPRI | POLLRDBAND) & sought;
+
+  return happened;
+}
+#endif /* !MinGW */
+
+int
+poll (struct pollfd *pfd, nfds_t nfd, int timeout)
+{
+#ifndef WIN32_NATIVE
+  fd_set rfds, wfds, efds;
+  struct timeval tv;
+  struct timeval *ptv;
+  int maxfd, rc;
+  nfds_t i;
+
+# ifdef _SC_OPEN_MAX
+  static int sc_open_max = -1;
+
+  if (nfd < 0
+      || (nfd > sc_open_max
+         && (sc_open_max != -1
+             || nfd > (sc_open_max = sysconf (_SC_OPEN_MAX)))))
+    {
+      errno = EINVAL;
+      return -1;
+    }
+# else /* !_SC_OPEN_MAX */
+#  ifdef OPEN_MAX
+  if (nfd < 0 || nfd > OPEN_MAX)
+    {
+      errno = EINVAL;
+      return -1;
+    }
+#  endif /* OPEN_MAX -- else, no check is needed */
+# endif /* !_SC_OPEN_MAX */
+
+  /* EFAULT is not necessary to implement, but let's do it in the
+     simplest case. */
+  if (!pfd)
+    {
+      errno = EFAULT;
+      return -1;
+    }
+
+  /* convert timeout number into a timeval structure */
+  if (timeout == 0)
+    {
+      ptv = &tv;
+      ptv->tv_sec = 0;
+      ptv->tv_usec = 0;
+    }
+  else if (timeout > 0)
+    {
+      ptv = &tv;
+      ptv->tv_sec = timeout / 1000;
+      ptv->tv_usec = (timeout % 1000) * 1000;
+    }
+  else if (timeout == INFTIM)
+    /* wait forever */
+    ptv = NULL;
+  else
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  /* create fd sets and determine max fd */
+  maxfd = -1;
+  FD_ZERO (&rfds);
+  FD_ZERO (&wfds);
+  FD_ZERO (&efds);
+  for (i = 0; i < nfd; i++)
+    {
+      if (pfd[i].fd < 0)
+       continue;
+
+      if (pfd[i].events & (POLLIN | POLLRDNORM))
+       FD_SET (pfd[i].fd, &rfds);
+
+      /* see select(2): "the only exceptional condition detectable
+        is out-of-band data received on a socket", hence we push
+        POLLWRBAND events onto wfds instead of efds. */
+      if (pfd[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND))
+       FD_SET (pfd[i].fd, &wfds);
+      if (pfd[i].events & (POLLPRI | POLLRDBAND))
+       FD_SET (pfd[i].fd, &efds);
+      if (pfd[i].fd >= maxfd
+         && (pfd[i].events & (POLLIN | POLLOUT | POLLPRI
+                              | POLLRDNORM | POLLRDBAND
+                              | POLLWRNORM | POLLWRBAND)))
+       {
+         maxfd = pfd[i].fd;
+         if (maxfd > FD_SETSIZE)
+           {
+             errno = EOVERFLOW;
+             return -1;
+           }
+       }
+    }
+
+  /* examine fd sets */
+  rc = select (maxfd + 1, &rfds, &wfds, &efds, ptv);
+  if (rc < 0)
+    return rc;
+
+  /* establish results */
+  rc = 0;
+  for (i = 0; i < nfd; i++)
+    if (pfd[i].fd < 0)
+      pfd[i].revents = 0;
+    else
+      {
+       int happened = compute_revents (pfd[i].fd, pfd[i].events,
+                                       &rfds, &wfds, &efds);
+       if (happened)
+         {
+           pfd[i].revents = happened;
+           rc++;
+         }
+      }
+
+  return rc;
+#else
+  static struct timeval tv0;
+  static HANDLE hEvent;
+  WSANETWORKEVENTS ev;
+  HANDLE h, handle_array[FD_SETSIZE + 2];
+  DWORD ret, wait_timeout, nhandles;
+  fd_set rfds, wfds, xfds;
+  BOOL poll_again;
+  MSG msg;
+  int rc = 0;
+  nfds_t i;
+
+  if (nfd < 0 || timeout < -1)
+    {
+      errno = EINVAL;
+      return -1;
+    }
+
+  if (!hEvent)
+    hEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
+
+restart:
+  handle_array[0] = hEvent;
+  nhandles = 1;
+  FD_ZERO (&rfds);
+  FD_ZERO (&wfds);
+  FD_ZERO (&xfds);
+
+  /* Classify socket handles and create fd sets. */
+  for (i = 0; i < nfd; i++)
+    {
+      int sought = pfd[i].events;
+      pfd[i].revents = 0;
+      if (pfd[i].fd < 0)
+       continue;
+      if (!(sought & (POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM | POLLWRBAND
+                     | POLLPRI | POLLRDBAND)))
+       continue;
+
+      h = (HANDLE) _get_osfhandle (pfd[i].fd);
+      assert (h != NULL);
+      if (IsSocketHandle (h))
+       {
+         int requested = FD_CLOSE;
+
+         /* see above; socket handles are mapped onto select.  */
+         if (sought & (POLLIN | POLLRDNORM))
+           {
+             requested |= FD_READ | FD_ACCEPT;
+             FD_SET ((SOCKET) h, &rfds);
+           }
+         if (sought & (POLLOUT | POLLWRNORM | POLLWRBAND))
+           {
+             requested |= FD_WRITE | FD_CONNECT;
+             FD_SET ((SOCKET) h, &wfds);
+           }
+         if (sought & (POLLPRI | POLLRDBAND))
+           {
+             requested |= FD_OOB;
+             FD_SET ((SOCKET) h, &xfds);
+           }
+
+         if (requested)
+           WSAEventSelect ((SOCKET) h, hEvent, requested);
+       }
+      else
+       {
+         /* Poll now.  If we get an event, do not poll again.  Also,
+            screen buffer handles are waitable, and they'll block until
+            a character is available.  win32_compute_revents eliminates
+            bits for the "wrong" direction. */
+         pfd[i].revents = win32_compute_revents (h, &sought);
+         if (sought)
+           handle_array[nhandles++] = h;
+         if (pfd[i].revents)
+           timeout = 0;
+       }
+    }
+
+  if (select (0, &rfds, &wfds, &xfds, &tv0) > 0)
+    {
+      /* Do MsgWaitForMultipleObjects anyway to dispatch messages, but
+        no need to call select again.  */
+      poll_again = FALSE;
+      wait_timeout = 0;
+    }
+  else
+    {
+      poll_again = TRUE;
+      if (timeout == INFTIM)
+       wait_timeout = INFINITE;
+      else
+       wait_timeout = timeout;
+    }
+
+  for (;;)
+    {
+      ret = MsgWaitForMultipleObjects (nhandles, handle_array, FALSE,
+                                      wait_timeout, QS_ALLINPUT);
+
+      if (ret == WAIT_OBJECT_0 + nhandles)
+       {
+         /* new input of some other kind */
+         BOOL bRet;
+         while ((bRet = PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) != 0)
+           {
+             TranslateMessage (&msg);
+             DispatchMessage (&msg);
+           }
+       }
+      else
+       break;
+    }
+
+  if (poll_again)
+    select (0, &rfds, &wfds, &xfds, &tv0);
+
+  /* Place a sentinel at the end of the array.  */
+  handle_array[nhandles] = NULL;
+  nhandles = 1;
+  for (i = 0; i < nfd; i++)
+    {
+      int happened;
+
+      if (pfd[i].fd < 0)
+       continue;
+      if (!(pfd[i].events & (POLLIN | POLLRDNORM |
+                            POLLOUT | POLLWRNORM | POLLWRBAND)))
+       continue;
+
+      h = (HANDLE) _get_osfhandle (pfd[i].fd);
+      if (h != handle_array[nhandles])
+       {
+         /* It's a socket.  */
+         WSAEnumNetworkEvents ((SOCKET) h, NULL, &ev);
+         WSAEventSelect ((SOCKET) h, 0, 0);
+
+         /* If we're lucky, WSAEnumNetworkEvents already provided a way
+            to distinguish FD_READ and FD_ACCEPT; this saves a recv later.  */
+         if (FD_ISSET ((SOCKET) h, &rfds)
+             && !(ev.lNetworkEvents & (FD_READ | FD_ACCEPT)))
+           ev.lNetworkEvents |= FD_READ | FD_ACCEPT;
+         if (FD_ISSET ((SOCKET) h, &wfds))
+           ev.lNetworkEvents |= FD_WRITE | FD_CONNECT;
+         if (FD_ISSET ((SOCKET) h, &xfds))
+           ev.lNetworkEvents |= FD_OOB;
+
+         happened = win32_compute_revents_socket ((SOCKET) h, pfd[i].events,
+                                                  ev.lNetworkEvents);
+       }
+      else
+       {
+         /* Not a socket.  */
+         int sought = pfd[i].events;
+         happened = win32_compute_revents (h, &sought);
+         nhandles++;
+       }
+
+       if ((pfd[i].revents |= happened) != 0)
+       rc++;
+    }
+
+  if (!rc && timeout == INFTIM)
+    {
+      SwitchToThread();
+      goto restart;
+    }
+
+  return rc;
+#endif
+}
diff --git a/compat/win32/poll.h b/compat/win32/poll.h
new file mode 100644 (file)
index 0000000..b7aa59d
--- /dev/null
@@ -0,0 +1,53 @@
+/* Header for poll(2) emulation
+   Contributed by Paolo Bonzini.
+
+   Copyright 2001, 2002, 2003, 2007, 2009, 2010 Free Software Foundation, Inc.
+
+   This file is part of gnulib.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License along
+   with this program; if not, write to the Free Software Foundation,
+   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
+
+#ifndef _GL_POLL_H
+#define _GL_POLL_H
+
+/* fake a poll(2) environment */
+#define POLLIN      0x0001      /* any readable data available   */
+#define POLLPRI     0x0002      /* OOB/Urgent readable data      */
+#define POLLOUT     0x0004      /* file descriptor is writeable  */
+#define POLLERR     0x0008      /* some poll error occurred      */
+#define POLLHUP     0x0010      /* file descriptor was "hung up" */
+#define POLLNVAL    0x0020      /* requested events "invalid"    */
+#define POLLRDNORM  0x0040
+#define POLLRDBAND  0x0080
+#define POLLWRNORM  0x0100
+#define POLLWRBAND  0x0200
+
+struct pollfd
+{
+  int fd;                       /* which file descriptor to poll */
+  short events;                 /* events we are interested in   */
+  short revents;                /* events found on return        */
+};
+
+typedef unsigned long nfds_t;
+
+extern int poll (struct pollfd *pfd, nfds_t nfd, int timeout);
+
+/* Define INFTIM only if doing so conforms to POSIX.  */
+#if !defined (_POSIX_C_SOURCE) && !defined (_XOPEN_SOURCE)
+#define INFTIM (-1)
+#endif
+
+#endif /* _GL_POLL_H */
diff --git a/compat/win32/sys/poll.c b/compat/win32/sys/poll.c
deleted file mode 100644 (file)
index 708a6c9..0000000
+++ /dev/null
@@ -1,599 +0,0 @@
-/* Emulation for poll(2)
-   Contributed by Paolo Bonzini.
-
-   Copyright 2001-2003, 2006-2010 Free Software Foundation, Inc.
-
-   This file is part of gnulib.
-
-   This program is free software; you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2, or (at your option)
-   any later version.
-
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License along
-   with this program; if not, write to the Free Software Foundation,
-   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
-
-/* Tell gcc not to warn about the (nfd < 0) tests, below.  */
-#if (__GNUC__ == 4 && 3 <= __GNUC_MINOR__) || 4 < __GNUC__
-# pragma GCC diagnostic ignored "-Wtype-limits"
-#endif
-
-#include <malloc.h>
-
-#include <sys/types.h>
-#include "poll.h"
-#include <errno.h>
-#include <limits.h>
-#include <assert.h>
-
-#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
-# define WIN32_NATIVE
-# if defined (_MSC_VER)
-#  define _WIN32_WINNT 0x0502
-# endif
-# include <winsock2.h>
-# include <windows.h>
-# include <io.h>
-# include <stdio.h>
-# include <conio.h>
-#else
-# include <sys/time.h>
-# include <sys/socket.h>
-# include <sys/select.h>
-# include <unistd.h>
-#endif
-
-#ifdef HAVE_SYS_IOCTL_H
-# include <sys/ioctl.h>
-#endif
-#ifdef HAVE_SYS_FILIO_H
-# include <sys/filio.h>
-#endif
-
-#include <time.h>
-
-#ifndef INFTIM
-# define INFTIM (-1)
-#endif
-
-/* BeOS does not have MSG_PEEK.  */
-#ifndef MSG_PEEK
-# define MSG_PEEK 0
-#endif
-
-#ifdef WIN32_NATIVE
-
-#define IsConsoleHandle(h) (((long) (h) & 3) == 3)
-
-static BOOL
-IsSocketHandle (HANDLE h)
-{
-  WSANETWORKEVENTS ev;
-
-  if (IsConsoleHandle (h))
-    return FALSE;
-
-  /* Under Wine, it seems that getsockopt returns 0 for pipes too.
-     WSAEnumNetworkEvents instead distinguishes the two correctly.  */
-  ev.lNetworkEvents = 0xDEADBEEF;
-  WSAEnumNetworkEvents ((SOCKET) h, NULL, &ev);
-  return ev.lNetworkEvents != 0xDEADBEEF;
-}
-
-/* Declare data structures for ntdll functions.  */
-typedef struct _FILE_PIPE_LOCAL_INFORMATION {
-  ULONG NamedPipeType;
-  ULONG NamedPipeConfiguration;
-  ULONG MaximumInstances;
-  ULONG CurrentInstances;
-  ULONG InboundQuota;
-  ULONG ReadDataAvailable;
-  ULONG OutboundQuota;
-  ULONG WriteQuotaAvailable;
-  ULONG NamedPipeState;
-  ULONG NamedPipeEnd;
-} FILE_PIPE_LOCAL_INFORMATION, *PFILE_PIPE_LOCAL_INFORMATION;
-
-typedef struct _IO_STATUS_BLOCK
-{
-  union {
-    DWORD Status;
-    PVOID Pointer;
-  } u;
-  ULONG_PTR Information;
-} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
-
-typedef enum _FILE_INFORMATION_CLASS {
-  FilePipeLocalInformation = 24
-} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS;
-
-typedef DWORD (WINAPI *PNtQueryInformationFile)
-        (HANDLE, IO_STATUS_BLOCK *, VOID *, ULONG, FILE_INFORMATION_CLASS);
-
-# ifndef PIPE_BUF
-#  define PIPE_BUF      512
-# endif
-
-/* Compute revents values for file handle H.  If some events cannot happen
-   for the handle, eliminate them from *P_SOUGHT.  */
-
-static int
-win32_compute_revents (HANDLE h, int *p_sought)
-{
-  int i, ret, happened;
-  INPUT_RECORD *irbuffer;
-  DWORD avail, nbuffer;
-  BOOL bRet;
-  IO_STATUS_BLOCK iosb;
-  FILE_PIPE_LOCAL_INFORMATION fpli;
-  static PNtQueryInformationFile NtQueryInformationFile;
-  static BOOL once_only;
-
-  switch (GetFileType (h))
-    {
-    case FILE_TYPE_PIPE:
-      if (!once_only)
-       {
-         NtQueryInformationFile = (PNtQueryInformationFile)
-           GetProcAddress (GetModuleHandle ("ntdll.dll"),
-                           "NtQueryInformationFile");
-         once_only = TRUE;
-       }
-
-      happened = 0;
-      if (PeekNamedPipe (h, NULL, 0, NULL, &avail, NULL) != 0)
-       {
-         if (avail)
-           happened |= *p_sought & (POLLIN | POLLRDNORM);
-       }
-      else if (GetLastError () == ERROR_BROKEN_PIPE)
-       happened |= POLLHUP;
-
-      else
-       {
-         /* It was the write-end of the pipe.  Check if it is writable.
-            If NtQueryInformationFile fails, optimistically assume the pipe is
-            writable.  This could happen on Win9x, where NtQueryInformationFile
-            is not available, or if we inherit a pipe that doesn't permit
-            FILE_READ_ATTRIBUTES access on the write end (I think this should
-            not happen since WinXP SP2; WINE seems fine too).  Otherwise,
-            ensure that enough space is available for atomic writes.  */
-         memset (&iosb, 0, sizeof (iosb));
-         memset (&fpli, 0, sizeof (fpli));
-
-         if (!NtQueryInformationFile
-             || NtQueryInformationFile (h, &iosb, &fpli, sizeof (fpli),
-                                        FilePipeLocalInformation)
-             || fpli.WriteQuotaAvailable >= PIPE_BUF
-             || (fpli.OutboundQuota < PIPE_BUF &&
-                 fpli.WriteQuotaAvailable == fpli.OutboundQuota))
-           happened |= *p_sought & (POLLOUT | POLLWRNORM | POLLWRBAND);
-       }
-      return happened;
-
-    case FILE_TYPE_CHAR:
-      ret = WaitForSingleObject (h, 0);
-      if (!IsConsoleHandle (h))
-       return ret == WAIT_OBJECT_0 ? *p_sought & ~(POLLPRI | POLLRDBAND) : 0;
-
-      nbuffer = avail = 0;
-      bRet = GetNumberOfConsoleInputEvents (h, &nbuffer);
-      if (bRet)
-       {
-         /* Input buffer.  */
-         *p_sought &= POLLIN | POLLRDNORM;
-         if (nbuffer == 0)
-           return POLLHUP;
-         if (!*p_sought)
-           return 0;
-
-         irbuffer = (INPUT_RECORD *) alloca (nbuffer * sizeof (INPUT_RECORD));
-         bRet = PeekConsoleInput (h, irbuffer, nbuffer, &avail);
-         if (!bRet || avail == 0)
-           return POLLHUP;
-
-         for (i = 0; i < avail; i++)
-           if (irbuffer[i].EventType == KEY_EVENT)
-             return *p_sought;
-         return 0;
-       }
-      else
-       {
-         /* Screen buffer.  */
-         *p_sought &= POLLOUT | POLLWRNORM | POLLWRBAND;
-         return *p_sought;
-       }
-
-    default:
-      ret = WaitForSingleObject (h, 0);
-      if (ret == WAIT_OBJECT_0)
-       return *p_sought & ~(POLLPRI | POLLRDBAND);
-
-      return *p_sought & (POLLOUT | POLLWRNORM | POLLWRBAND);
-    }
-}
-
-/* Convert fd_sets returned by select into revents values.  */
-
-static int
-win32_compute_revents_socket (SOCKET h, int sought, long lNetworkEvents)
-{
-  int happened = 0;
-
-  if ((lNetworkEvents & (FD_READ | FD_ACCEPT | FD_CLOSE)) == FD_ACCEPT)
-    happened |= (POLLIN | POLLRDNORM) & sought;
-
-  else if (lNetworkEvents & (FD_READ | FD_ACCEPT | FD_CLOSE))
-    {
-      int r, error;
-
-      char data[64];
-      WSASetLastError (0);
-      r = recv (h, data, sizeof (data), MSG_PEEK);
-      error = WSAGetLastError ();
-      WSASetLastError (0);
-
-      if (r > 0 || error == WSAENOTCONN)
-       happened |= (POLLIN | POLLRDNORM) & sought;
-
-      /* Distinguish hung-up sockets from other errors.  */
-      else if (r == 0 || error == WSAESHUTDOWN || error == WSAECONNRESET
-              || error == WSAECONNABORTED || error == WSAENETRESET)
-       happened |= POLLHUP;
-
-      else
-       happened |= POLLERR;
-    }
-
-  if (lNetworkEvents & (FD_WRITE | FD_CONNECT))
-    happened |= (POLLOUT | POLLWRNORM | POLLWRBAND) & sought;
-
-  if (lNetworkEvents & FD_OOB)
-    happened |= (POLLPRI | POLLRDBAND) & sought;
-
-  return happened;
-}
-
-#else /* !MinGW */
-
-/* Convert select(2) returned fd_sets into poll(2) revents values.  */
-static int
-compute_revents (int fd, int sought, fd_set *rfds, fd_set *wfds, fd_set *efds)
-{
-  int happened = 0;
-  if (FD_ISSET (fd, rfds))
-    {
-      int r;
-      int socket_errno;
-
-# if defined __MACH__ && defined __APPLE__
-      /* There is a bug in Mac OS X that causes it to ignore MSG_PEEK
-        for some kinds of descriptors.  Detect if this descriptor is a
-        connected socket, a server socket, or something else using a
-        0-byte recv, and use ioctl(2) to detect POLLHUP.  */
-      r = recv (fd, NULL, 0, MSG_PEEK);
-      socket_errno = (r < 0) ? errno : 0;
-      if (r == 0 || socket_errno == ENOTSOCK)
-       ioctl (fd, FIONREAD, &r);
-# else
-      char data[64];
-      r = recv (fd, data, sizeof (data), MSG_PEEK);
-      socket_errno = (r < 0) ? errno : 0;
-# endif
-      if (r == 0)
-       happened |= POLLHUP;
-
-      /* If the event happened on an unconnected server socket,
-        that's fine. */
-      else if (r > 0 || ( /* (r == -1) && */ socket_errno == ENOTCONN))
-       happened |= (POLLIN | POLLRDNORM) & sought;
-
-      /* Distinguish hung-up sockets from other errors.  */
-      else if (socket_errno == ESHUTDOWN || socket_errno == ECONNRESET
-              || socket_errno == ECONNABORTED || socket_errno == ENETRESET)
-       happened |= POLLHUP;
-
-      else
-       happened |= POLLERR;
-    }
-
-  if (FD_ISSET (fd, wfds))
-    happened |= (POLLOUT | POLLWRNORM | POLLWRBAND) & sought;
-
-  if (FD_ISSET (fd, efds))
-    happened |= (POLLPRI | POLLRDBAND) & sought;
-
-  return happened;
-}
-#endif /* !MinGW */
-
-int
-poll (pfd, nfd, timeout)
-     struct pollfd *pfd;
-     nfds_t nfd;
-     int timeout;
-{
-#ifndef WIN32_NATIVE
-  fd_set rfds, wfds, efds;
-  struct timeval tv;
-  struct timeval *ptv;
-  int maxfd, rc;
-  nfds_t i;
-
-# ifdef _SC_OPEN_MAX
-  static int sc_open_max = -1;
-
-  if (nfd < 0
-      || (nfd > sc_open_max
-         && (sc_open_max != -1
-             || nfd > (sc_open_max = sysconf (_SC_OPEN_MAX)))))
-    {
-      errno = EINVAL;
-      return -1;
-    }
-# else /* !_SC_OPEN_MAX */
-#  ifdef OPEN_MAX
-  if (nfd < 0 || nfd > OPEN_MAX)
-    {
-      errno = EINVAL;
-      return -1;
-    }
-#  endif /* OPEN_MAX -- else, no check is needed */
-# endif /* !_SC_OPEN_MAX */
-
-  /* EFAULT is not necessary to implement, but let's do it in the
-     simplest case. */
-  if (!pfd)
-    {
-      errno = EFAULT;
-      return -1;
-    }
-
-  /* convert timeout number into a timeval structure */
-  if (timeout == 0)
-    {
-      ptv = &tv;
-      ptv->tv_sec = 0;
-      ptv->tv_usec = 0;
-    }
-  else if (timeout > 0)
-    {
-      ptv = &tv;
-      ptv->tv_sec = timeout / 1000;
-      ptv->tv_usec = (timeout % 1000) * 1000;
-    }
-  else if (timeout == INFTIM)
-    /* wait forever */
-    ptv = NULL;
-  else
-    {
-      errno = EINVAL;
-      return -1;
-    }
-
-  /* create fd sets and determine max fd */
-  maxfd = -1;
-  FD_ZERO (&rfds);
-  FD_ZERO (&wfds);
-  FD_ZERO (&efds);
-  for (i = 0; i < nfd; i++)
-    {
-      if (pfd[i].fd < 0)
-       continue;
-
-      if (pfd[i].events & (POLLIN | POLLRDNORM))
-       FD_SET (pfd[i].fd, &rfds);
-
-      /* see select(2): "the only exceptional condition detectable
-        is out-of-band data received on a socket", hence we push
-        POLLWRBAND events onto wfds instead of efds. */
-      if (pfd[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND))
-       FD_SET (pfd[i].fd, &wfds);
-      if (pfd[i].events & (POLLPRI | POLLRDBAND))
-       FD_SET (pfd[i].fd, &efds);
-      if (pfd[i].fd >= maxfd
-         && (pfd[i].events & (POLLIN | POLLOUT | POLLPRI
-                              | POLLRDNORM | POLLRDBAND
-                              | POLLWRNORM | POLLWRBAND)))
-       {
-         maxfd = pfd[i].fd;
-         if (maxfd > FD_SETSIZE)
-           {
-             errno = EOVERFLOW;
-             return -1;
-           }
-       }
-    }
-
-  /* examine fd sets */
-  rc = select (maxfd + 1, &rfds, &wfds, &efds, ptv);
-  if (rc < 0)
-    return rc;
-
-  /* establish results */
-  rc = 0;
-  for (i = 0; i < nfd; i++)
-    if (pfd[i].fd < 0)
-      pfd[i].revents = 0;
-    else
-      {
-       int happened = compute_revents (pfd[i].fd, pfd[i].events,
-                                       &rfds, &wfds, &efds);
-       if (happened)
-         {
-           pfd[i].revents = happened;
-           rc++;
-         }
-      }
-
-  return rc;
-#else
-  static struct timeval tv0;
-  static HANDLE hEvent;
-  WSANETWORKEVENTS ev;
-  HANDLE h, handle_array[FD_SETSIZE + 2];
-  DWORD ret, wait_timeout, nhandles;
-  fd_set rfds, wfds, xfds;
-  BOOL poll_again;
-  MSG msg;
-  int rc = 0;
-  nfds_t i;
-
-  if (nfd < 0 || timeout < -1)
-    {
-      errno = EINVAL;
-      return -1;
-    }
-
-  if (!hEvent)
-    hEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
-
-  handle_array[0] = hEvent;
-  nhandles = 1;
-  FD_ZERO (&rfds);
-  FD_ZERO (&wfds);
-  FD_ZERO (&xfds);
-
-  /* Classify socket handles and create fd sets. */
-  for (i = 0; i < nfd; i++)
-    {
-      int sought = pfd[i].events;
-      pfd[i].revents = 0;
-      if (pfd[i].fd < 0)
-       continue;
-      if (!(sought & (POLLIN | POLLRDNORM | POLLOUT | POLLWRNORM | POLLWRBAND
-                     | POLLPRI | POLLRDBAND)))
-       continue;
-
-      h = (HANDLE) _get_osfhandle (pfd[i].fd);
-      assert (h != NULL);
-      if (IsSocketHandle (h))
-       {
-         int requested = FD_CLOSE;
-
-         /* see above; socket handles are mapped onto select.  */
-         if (sought & (POLLIN | POLLRDNORM))
-           {
-             requested |= FD_READ | FD_ACCEPT;
-             FD_SET ((SOCKET) h, &rfds);
-           }
-         if (sought & (POLLOUT | POLLWRNORM | POLLWRBAND))
-           {
-             requested |= FD_WRITE | FD_CONNECT;
-             FD_SET ((SOCKET) h, &wfds);
-           }
-         if (sought & (POLLPRI | POLLRDBAND))
-           {
-             requested |= FD_OOB;
-             FD_SET ((SOCKET) h, &xfds);
-           }
-
-         if (requested)
-           WSAEventSelect ((SOCKET) h, hEvent, requested);
-       }
-      else
-       {
-         /* Poll now.  If we get an event, do not poll again.  Also,
-            screen buffer handles are waitable, and they'll block until
-            a character is available.  win32_compute_revents eliminates
-            bits for the "wrong" direction. */
-         pfd[i].revents = win32_compute_revents (h, &sought);
-         if (sought)
-           handle_array[nhandles++] = h;
-         if (pfd[i].revents)
-           timeout = 0;
-       }
-    }
-
-  if (select (0, &rfds, &wfds, &xfds, &tv0) > 0)
-    {
-      /* Do MsgWaitForMultipleObjects anyway to dispatch messages, but
-        no need to call select again.  */
-      poll_again = FALSE;
-      wait_timeout = 0;
-    }
-  else
-    {
-      poll_again = TRUE;
-      if (timeout == INFTIM)
-       wait_timeout = INFINITE;
-      else
-       wait_timeout = timeout;
-    }
-
-  for (;;)
-    {
-      ret = MsgWaitForMultipleObjects (nhandles, handle_array, FALSE,
-                                      wait_timeout, QS_ALLINPUT);
-
-      if (ret == WAIT_OBJECT_0 + nhandles)
-       {
-         /* new input of some other kind */
-         BOOL bRet;
-         while ((bRet = PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) != 0)
-           {
-             TranslateMessage (&msg);
-             DispatchMessage (&msg);
-           }
-       }
-      else
-       break;
-    }
-
-  if (poll_again)
-    select (0, &rfds, &wfds, &xfds, &tv0);
-
-  /* Place a sentinel at the end of the array.  */
-  handle_array[nhandles] = NULL;
-  nhandles = 1;
-  for (i = 0; i < nfd; i++)
-    {
-      int happened;
-
-      if (pfd[i].fd < 0)
-       continue;
-      if (!(pfd[i].events & (POLLIN | POLLRDNORM |
-                            POLLOUT | POLLWRNORM | POLLWRBAND)))
-       continue;
-
-      h = (HANDLE) _get_osfhandle (pfd[i].fd);
-      if (h != handle_array[nhandles])
-       {
-         /* It's a socket.  */
-         WSAEnumNetworkEvents ((SOCKET) h, NULL, &ev);
-         WSAEventSelect ((SOCKET) h, 0, 0);
-
-         /* If we're lucky, WSAEnumNetworkEvents already provided a way
-            to distinguish FD_READ and FD_ACCEPT; this saves a recv later.  */
-         if (FD_ISSET ((SOCKET) h, &rfds)
-             && !(ev.lNetworkEvents & (FD_READ | FD_ACCEPT)))
-           ev.lNetworkEvents |= FD_READ | FD_ACCEPT;
-         if (FD_ISSET ((SOCKET) h, &wfds))
-           ev.lNetworkEvents |= FD_WRITE | FD_CONNECT;
-         if (FD_ISSET ((SOCKET) h, &xfds))
-           ev.lNetworkEvents |= FD_OOB;
-
-         happened = win32_compute_revents_socket ((SOCKET) h, pfd[i].events,
-                                                  ev.lNetworkEvents);
-       }
-      else
-       {
-         /* Not a socket.  */
-         int sought = pfd[i].events;
-         happened = win32_compute_revents (h, &sought);
-         nhandles++;
-       }
-
-       if ((pfd[i].revents |= happened) != 0)
-       rc++;
-    }
-
-  return rc;
-#endif
-}
diff --git a/compat/win32/sys/poll.h b/compat/win32/sys/poll.h
deleted file mode 100644 (file)
index b7aa59d..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-/* Header for poll(2) emulation
-   Contributed by Paolo Bonzini.
-
-   Copyright 2001, 2002, 2003, 2007, 2009, 2010 Free Software Foundation, Inc.
-
-   This file is part of gnulib.
-
-   This program is free software; you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2, or (at your option)
-   any later version.
-
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License along
-   with this program; if not, write to the Free Software Foundation,
-   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
-
-#ifndef _GL_POLL_H
-#define _GL_POLL_H
-
-/* fake a poll(2) environment */
-#define POLLIN      0x0001      /* any readable data available   */
-#define POLLPRI     0x0002      /* OOB/Urgent readable data      */
-#define POLLOUT     0x0004      /* file descriptor is writeable  */
-#define POLLERR     0x0008      /* some poll error occurred      */
-#define POLLHUP     0x0010      /* file descriptor was "hung up" */
-#define POLLNVAL    0x0020      /* requested events "invalid"    */
-#define POLLRDNORM  0x0040
-#define POLLRDBAND  0x0080
-#define POLLWRNORM  0x0100
-#define POLLWRBAND  0x0200
-
-struct pollfd
-{
-  int fd;                       /* which file descriptor to poll */
-  short events;                 /* events we are interested in   */
-  short revents;                /* events found on return        */
-};
-
-typedef unsigned long nfds_t;
-
-extern int poll (struct pollfd *pfd, nfds_t nfd, int timeout);
-
-/* Define INFTIM only if doing so conforms to POSIX.  */
-#if !defined (_POSIX_C_SOURCE) && !defined (_XOPEN_SOURCE)
-#define INFTIM (-1)
-#endif
-
-#endif /* _GL_POLL_H */
index 42b95a9b5169125fac7bde3caabb15f4cb6a351d..d015e436d57472bf9560a6f604705f52938c9f19 100644 (file)
@@ -1,5 +1,4 @@
 #include "../../git-compat-util.h"
-#include "../../strbuf.h"
 
 static HANDLE ms_eventlog;
 
@@ -16,13 +15,8 @@ void openlog(const char *ident, int logopt, int facility)
 
 void syslog(int priority, const char *fmt, ...)
 {
-       struct strbuf sb = STRBUF_INIT;
-       struct strbuf_expand_dict_entry dict[] = {
-               {"1", "% 1"},
-               {NULL, NULL}
-       };
        WORD logtype;
-       char *str;
+       char *str, *pos;
        int str_len;
        va_list ap;
 
@@ -39,11 +33,24 @@ void syslog(int priority, const char *fmt, ...)
        }
 
        str = malloc(str_len + 1);
+       if (!str) {
+               warning("malloc failed: '%s'", strerror(errno));
+               return;
+       }
+
        va_start(ap, fmt);
        vsnprintf(str, str_len + 1, fmt, ap);
        va_end(ap);
-       strbuf_expand(&sb, str, strbuf_expand_dict_cb, &dict);
-       free(str);
+
+       while ((pos = strstr(str, "%1")) != NULL) {
+               str = realloc(str, ++str_len + 1);
+               if (!str) {
+                       warning("realloc failed: '%s'", strerror(errno));
+                       return;
+               }
+               memmove(pos + 2, pos + 1, strlen(pos));
+               pos[1] = ' ';
+       }
 
        switch (priority) {
        case LOG_EMERG:
@@ -66,7 +73,6 @@ void syslog(int priority, const char *fmt, ...)
        }
 
        ReportEventA(ms_eventlog, logtype, 0, 0, NULL, 1, 0,
-           (const char **)&sb.buf, NULL);
-
-       strbuf_release(&sb);
+           (const char **)&str, NULL);
+       free(str);
 }
index a9e23594bd18b1cde49b55a40b2af8e2f3b74439..5ea101fb251d27eadac20c665a7f01fb210c20d1 100644 (file)
--- a/config.c
+++ b/config.c
@@ -333,7 +333,7 @@ static int git_parse_file(config_fn_t fn, void *data)
        die("bad config file line %d in %s", cf->linenr, cf->name);
 }
 
-static int parse_unit_factor(const char *end, unsigned long *val)
+static int parse_unit_factor(const char *end, uintmax_t *val)
 {
        if (!*end)
                return 1;
@@ -356,11 +356,23 @@ static int git_parse_long(const char *value, long *ret)
 {
        if (value && *value) {
                char *end;
-               long val = strtol(value, &end, 0);
-               unsigned long factor = 1;
+               intmax_t val;
+               uintmax_t uval;
+               uintmax_t factor = 1;
+
+               errno = 0;
+               val = strtoimax(value, &end, 0);
+               if (errno == ERANGE)
+                       return 0;
                if (!parse_unit_factor(end, &factor))
                        return 0;
-               *ret = val * factor;
+               uval = abs(val);
+               uval *= factor;
+               if ((uval > maximum_signed_value_of_type(long)) ||
+                   (abs(val) > uval))
+                       return 0;
+               val *= factor;
+               *ret = val;
                return 1;
        }
        return 0;
@@ -370,9 +382,19 @@ int git_parse_ulong(const char *value, unsigned long *ret)
 {
        if (value && *value) {
                char *end;
-               unsigned long val = strtoul(value, &end, 0);
+               uintmax_t val;
+               uintmax_t oldval;
+
+               errno = 0;
+               val = strtoumax(value, &end, 0);
+               if (errno == ERANGE)
+                       return 0;
+               oldval = val;
                if (!parse_unit_factor(end, &val))
                        return 0;
+               if ((val > maximum_unsigned_value_of_type(long)) ||
+                   (oldval > val))
+                       return 0;
                *ret = val;
                return 1;
        }
@@ -491,6 +513,9 @@ static int git_default_core_config(const char *var, const char *value)
                return 0;
        }
 
+       if (!strcmp(var, "core.attributesfile"))
+               return git_config_pathname(&git_attributes_file, var, value);
+
        if (!strcmp(var, "core.bare")) {
                is_bare_repository_cfg = git_config_bool(var, value);
                return 0;
@@ -550,7 +575,7 @@ static int git_default_core_config(const char *var, const char *value)
 
        if (!strcmp(var, "core.packedgitwindowsize")) {
                int pgsz_x2 = getpagesize() * 2;
-               packed_git_window_size = git_config_int(var, value);
+               packed_git_window_size = git_config_ulong(var, value);
 
                /* This value must be multiple of (pagesize * 2) */
                packed_git_window_size /= pgsz_x2;
@@ -561,18 +586,17 @@ static int git_default_core_config(const char *var, const char *value)
        }
 
        if (!strcmp(var, "core.bigfilethreshold")) {
-               long n = git_config_int(var, value);
-               big_file_threshold = 0 < n ? n : 0;
+               big_file_threshold = git_config_ulong(var, value);
                return 0;
        }
 
        if (!strcmp(var, "core.packedgitlimit")) {
-               packed_git_limit = git_config_int(var, value);
+               packed_git_limit = git_config_ulong(var, value);
                return 0;
        }
 
        if (!strcmp(var, "core.deltabasecachelimit")) {
-               delta_base_cache_limit = git_config_int(var, value);
+               delta_base_cache_limit = git_config_ulong(var, value);
                return 0;
        }
 
@@ -862,12 +886,12 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config)
 
        home = getenv("HOME");
        if (home) {
-               char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
+               char buf[PATH_MAX];
+               char *user_config = mksnpath(buf, sizeof(buf), "%s/.gitconfig", home);
                if (!access(user_config, R_OK)) {
                        ret += git_config_from_file(fn, user_config, data);
                        found += 1;
                }
-               free(user_config);
        }
 
        if (repo_config && !access(repo_config, R_OK)) {
index ee1d4b4b46f789168c155eb170b87dbed85fb590..51990fa0cb300a95b125b0727f10133961d0167b 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -22,7 +22,7 @@ static int check_ref(const char *name, int len, unsigned int flags)
        len -= 5;
 
        /* REF_NORMAL means that we don't want the magic fake tag refs */
-       if ((flags & REF_NORMAL) && check_ref_format(name) < 0)
+       if ((flags & REF_NORMAL) && check_refname_format(name, 0))
                return 0;
 
        /* REF_HEADS means that we want regular branch heads */
index 8648a36e7b8239e969dc8971f451431c298e405e..b7c1edf1cc763199d41a490b212a3b65ea581a86 100755 (executable)
@@ -110,6 +110,7 @@ __git_ps1_show_upstream ()
        local upstream=git legacy="" verbose=""
 
        # get some config options from git-config
+       local output="$(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')"
        while read key value; do
                case "$key" in
                bash.showupstream)
@@ -125,7 +126,7 @@ __git_ps1_show_upstream ()
                        upstream=svn+git # default upstream is SVN if available, else git
                        ;;
                esac
-       done < <(git config -z --get-regexp '^(svn-remote\..*\.url|bash\.showupstream)$' 2>/dev/null | tr '\0\n' '\n ')
+       done <<< "$output"
 
        # parse configuration values
        for option in ${GIT_PS1_SHOWUPSTREAM}; do
@@ -1259,12 +1260,9 @@ _git_commit ()
                        " "" "${cur##--cleanup=}"
                return
                ;;
-       --reuse-message=*)
-               __gitcomp "$(__git_refs)" "" "${cur##--reuse-message=}"
-               return
-               ;;
-       --reedit-message=*)
-               __gitcomp "$(__git_refs)" "" "${cur##--reedit-message=}"
+       --reuse-message=*|--reedit-message=*|\
+       --fixup=*|--squash=*)
+               __gitcomp "$(__git_refs)" "" "${cur#*=}"
                return
                ;;
        --untracked-files=*)
@@ -1278,7 +1276,7 @@ _git_commit ()
                        --dry-run --reuse-message= --reedit-message=
                        --reset-author --file= --message= --template=
                        --cleanup= --untracked-files --untracked-files=
-                       --verbose --quiet
+                       --verbose --quiet --fixup= --squash=
                        "
                return
        esac
@@ -1432,6 +1430,10 @@ _git_gitk ()
        _gitk
 }
 
+__git_match_ctag() {
+       awk "/^${1////\\/}/ { print \$1 }" "$2"
+}
+
 _git_grep ()
 {
        __git_has_doubledash && return
@@ -1454,6 +1456,15 @@ _git_grep ()
                ;;
        esac
 
+       case "$cword,$prev" in
+       2,*|*,-*)
+               if test -r tags; then
+                       __gitcomp "$(__git_match_ctag "$cur" tags)"
+                       return
+               fi
+               ;;
+       esac
+
        __gitcomp "$(__git_refs)"
 }
 
@@ -1556,14 +1567,9 @@ _git_log ()
                merge="--merge"
        fi
        case "$cur" in
-       --pretty=*)
+       --pretty=*|--format=*)
                __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases)
-                       " "" "${cur##--pretty=}"
-               return
-               ;;
-       --format=*)
-               __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases)
-                       " "" "${cur##--format=}"
+                       " "" "${cur#*=}"
                return
                ;;
        --date=*)
@@ -1671,11 +1677,9 @@ _git_notes ()
                        ;;
                esac
                ;;
-       add,--reuse-message=*|append,--reuse-message=*)
-               __gitcomp "$(__git_refs)" "" "${cur##--reuse-message=}"
-               ;;
+       add,--reuse-message=*|append,--reuse-message=*|\
        add,--reedit-message=*|append,--reedit-message=*)
-               __gitcomp "$(__git_refs)" "" "${cur##--reedit-message=}"
+               __gitcomp "$(__git_refs)" "" "${cur#*=}"
                ;;
        add,--*|append,--*)
                __gitcomp '--file= --message= --reedit-message=
@@ -1733,7 +1737,7 @@ _git_push ()
        --*)
                __gitcomp "
                        --all --mirror --tags --dry-run --force --verbose
-                       --receive-pack= --repo=
+                       --receive-pack= --repo= --set-upstream
                "
                return
                ;;
@@ -2370,14 +2374,9 @@ _git_show ()
        __git_has_doubledash && return
 
        case "$cur" in
-       --pretty=*)
-               __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases)
-                       " "" "${cur##--pretty=}"
-               return
-               ;;
-       --format=*)
+       --pretty=*|--format=*)
                __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases)
-                       " "" "${cur##--format=}"
+                       " "" "${cur#*=}"
                return
                ;;
        --*)
diff --git a/contrib/diff-highlight/README b/contrib/diff-highlight/README
new file mode 100644 (file)
index 0000000..1b7b6df
--- /dev/null
@@ -0,0 +1,57 @@
+diff-highlight
+==============
+
+Line oriented diffs are great for reviewing code, because for most
+hunks, you want to see the old and the new segments of code next to each
+other. Sometimes, though, when an old line and a new line are very
+similar, it's hard to immediately see the difference.
+
+You can use "--color-words" to highlight only the changed portions of
+lines. However, this can often be hard to read for code, as it loses
+the line structure, and you end up with oddly formatted bits.
+
+Instead, this script post-processes the line-oriented diff, finds pairs
+of lines, and highlights the differing segments.  It's currently very
+simple and stupid about doing these tasks. In particular:
+
+  1. It will only highlight a pair of lines if they are the only two
+     lines in a hunk.  It could instead try to match up "before" and
+     "after" lines for a given hunk into pairs of similar lines.
+     However, this may end up visually distracting, as the paired
+     lines would have other highlighted lines in between them. And in
+     practice, the lines which most need attention called to their
+     small, hard-to-see changes are touching only a single line.
+
+  2. It will find the common prefix and suffix of two lines, and
+     consider everything in the middle to be "different". It could
+     instead do a real diff of the characters between the two lines and
+     find common subsequences. However, the point of the highlight is to
+     call attention to a certain area. Even if some small subset of the
+     highlighted area actually didn't change, that's OK. In practice it
+     ends up being more readable to just have a single blob on the line
+     showing the interesting bit.
+
+The goal of the script is therefore not to be exact about highlighting
+changes, but to call attention to areas of interest without being
+visually distracting.  Non-diff lines and existing diff coloration is
+preserved; the intent is that the output should look exactly the same as
+the input, except for the occasional highlight.
+
+Use
+---
+
+You can try out the diff-highlight program with:
+
+---------------------------------------------
+git log -p --color | /path/to/diff-highlight
+---------------------------------------------
+
+If you want to use it all the time, drop it in your $PATH and put the
+following in your git configuration:
+
+---------------------------------------------
+[pager]
+       log = diff-highlight | less
+       show = diff-highlight | less
+       diff = diff-highlight | less
+---------------------------------------------
diff --git a/contrib/diff-highlight/diff-highlight b/contrib/diff-highlight/diff-highlight
new file mode 100755 (executable)
index 0000000..d893898
--- /dev/null
@@ -0,0 +1,124 @@
+#!/usr/bin/perl
+
+# Highlight by reversing foreground and background. You could do
+# other things like bold or underline if you prefer.
+my $HIGHLIGHT   = "\x1b[7m";
+my $UNHIGHLIGHT = "\x1b[27m";
+my $COLOR = qr/\x1b\[[0-9;]*m/;
+
+my @window;
+
+while (<>) {
+       # We highlight only single-line changes, so we need
+       # a 4-line window to make a decision on whether
+       # to highlight.
+       push @window, $_;
+       next if @window < 4;
+       if ($window[0] =~ /^$COLOR*(\@| )/ &&
+           $window[1] =~ /^$COLOR*-/ &&
+           $window[2] =~ /^$COLOR*\+/ &&
+           $window[3] !~ /^$COLOR*\+/) {
+               print shift @window;
+               show_pair(shift @window, shift @window);
+       }
+       else {
+               print shift @window;
+       }
+
+       # Most of the time there is enough output to keep things streaming,
+       # but for something like "git log -Sfoo", you can get one early
+       # commit and then many seconds of nothing. We want to show
+       # that one commit as soon as possible.
+       #
+       # Since we can receive arbitrary input, there's no optimal
+       # place to flush. Flushing on a blank line is a heuristic that
+       # happens to match git-log output.
+       if (!length) {
+               local $| = 1;
+       }
+}
+
+# Special case a single-line hunk at the end of file.
+if (@window == 3 &&
+    $window[0] =~ /^$COLOR*(\@| )/ &&
+    $window[1] =~ /^$COLOR*-/ &&
+    $window[2] =~ /^$COLOR*\+/) {
+       print shift @window;
+       show_pair(shift @window, shift @window);
+}
+
+# And then flush any remaining lines.
+while (@window) {
+       print shift @window;
+}
+
+exit 0;
+
+sub show_pair {
+       my @a = split_line(shift);
+       my @b = split_line(shift);
+
+       # Find common prefix, taking care to skip any ansi
+       # color codes.
+       my $seen_plusminus;
+       my ($pa, $pb) = (0, 0);
+       while ($pa < @a && $pb < @b) {
+               if ($a[$pa] =~ /$COLOR/) {
+                       $pa++;
+               }
+               elsif ($b[$pb] =~ /$COLOR/) {
+                       $pb++;
+               }
+               elsif ($a[$pa] eq $b[$pb]) {
+                       $pa++;
+                       $pb++;
+               }
+               elsif (!$seen_plusminus && $a[$pa] eq '-' && $b[$pb] eq '+') {
+                       $seen_plusminus = 1;
+                       $pa++;
+                       $pb++;
+               }
+               else {
+                       last;
+               }
+       }
+
+       # Find common suffix, ignoring colors.
+       my ($sa, $sb) = ($#a, $#b);
+       while ($sa >= $pa && $sb >= $pb) {
+               if ($a[$sa] =~ /$COLOR/) {
+                       $sa--;
+               }
+               elsif ($b[$sb] =~ /$COLOR/) {
+                       $sb--;
+               }
+               elsif ($a[$sa] eq $b[$sb]) {
+                       $sa--;
+                       $sb--;
+               }
+               else {
+                       last;
+               }
+       }
+
+       print highlight(\@a, $pa, $sa);
+       print highlight(\@b, $pb, $sb);
+}
+
+sub split_line {
+       local $_ = shift;
+       return map { /$COLOR/ ? $_ : (split //) }
+              split /($COLOR*)/;
+}
+
+sub highlight {
+       my ($line, $prefix, $suffix) = @_;
+
+       return join('',
+               @{$line}[0..($prefix-1)],
+               $HIGHLIGHT,
+               @{$line}[$prefix..$suffix],
+               $UNHIGHLIGHT,
+               @{$line}[($suffix+1)..$#$line]
+       );
+}
index 2f7b270566471ebe8088cd2f024c0ca5e49eee4c..7fd8bf031e1eb10f6dda2342f32c86454ef7efb3 100755 (executable)
@@ -22,36 +22,39 @@ def p4_build_cmd(cmd):
     location. It means that hooking into the environment, or other configuration
     can be done more easily.
     """
-    real_cmd = "%s " % "p4"
+    real_cmd = ["p4"]
 
     user = gitConfig("git-p4.user")
     if len(user) > 0:
-        real_cmd += "-u %s " % user
+        real_cmd += ["-u",user]
 
     password = gitConfig("git-p4.password")
     if len(password) > 0:
-        real_cmd += "-P %s " % password
+        real_cmd += ["-P", password]
 
     port = gitConfig("git-p4.port")
     if len(port) > 0:
-        real_cmd += "-p %s " % port
+        real_cmd += ["-p", port]
 
     host = gitConfig("git-p4.host")
     if len(host) > 0:
-        real_cmd += "-h %s " % host
+        real_cmd += ["-h", host]
 
     client = gitConfig("git-p4.client")
     if len(client) > 0:
-        real_cmd += "-c %s " % client
+        real_cmd += ["-c", client]
 
-    real_cmd += "%s" % (cmd)
-    if verbose:
-        print real_cmd
+
+    if isinstance(cmd,basestring):
+        real_cmd = ' '.join(real_cmd) + ' ' + cmd
+    else:
+        real_cmd += cmd
     return real_cmd
 
 def chdir(dir):
-    if os.name == 'nt':
-        os.environ['PWD']=dir
+    # P4 uses the PWD environment variable rather than getcwd(). Since we're
+    # not using the shell, we have to set it ourselves.
+    os.environ['PWD']=dir
     os.chdir(dir)
 
 def die(msg):
@@ -61,29 +64,34 @@ def die(msg):
         sys.stderr.write(msg + "\n")
         sys.exit(1)
 
-def write_pipe(c, str):
+def write_pipe(c, stdin):
     if verbose:
-        sys.stderr.write('Writing pipe: %s\n' % c)
+        sys.stderr.write('Writing pipe: %s\n' % str(c))
 
-    pipe = os.popen(c, 'w')
-    val = pipe.write(str)
-    if pipe.close():
-        die('Command failed: %s' % c)
+    expand = isinstance(c,basestring)
+    p = subprocess.Popen(c, stdin=subprocess.PIPE, shell=expand)
+    pipe = p.stdin
+    val = pipe.write(stdin)
+    pipe.close()
+    if p.wait():
+        die('Command failed: %s' % str(c))
 
     return val
 
-def p4_write_pipe(c, str):
+def p4_write_pipe(c, stdin):
     real_cmd = p4_build_cmd(c)
-    return write_pipe(real_cmd, str)
+    return write_pipe(real_cmd, stdin)
 
 def read_pipe(c, ignore_error=False):
     if verbose:
-        sys.stderr.write('Reading pipe: %s\n' % c)
+        sys.stderr.write('Reading pipe: %s\n' % str(c))
 
-    pipe = os.popen(c, 'rb')
+    expand = isinstance(c,basestring)
+    p = subprocess.Popen(c, stdout=subprocess.PIPE, shell=expand)
+    pipe = p.stdout
     val = pipe.read()
-    if pipe.close() and not ignore_error:
-        die('Command failed: %s' % c)
+    if p.wait() and not ignore_error:
+        die('Command failed: %s' % str(c))
 
     return val
 
@@ -93,12 +101,14 @@ def p4_read_pipe(c, ignore_error=False):
 
 def read_pipe_lines(c):
     if verbose:
-        sys.stderr.write('Reading pipe: %s\n' % c)
-    ## todo: check return status
-    pipe = os.popen(c, 'rb')
+        sys.stderr.write('Reading pipe: %s\n' % str(c))
+
+    expand = isinstance(c, basestring)
+    p = subprocess.Popen(c, stdout=subprocess.PIPE, shell=expand)
+    pipe = p.stdout
     val = pipe.readlines()
-    if pipe.close():
-        die('Command failed: %s' % c)
+    if pipe.close() or p.wait():
+        die('Command failed: %s' % str(c))
 
     return val
 
@@ -108,23 +118,73 @@ def p4_read_pipe_lines(c):
     return read_pipe_lines(real_cmd)
 
 def system(cmd):
+    expand = isinstance(cmd,basestring)
     if verbose:
-        sys.stderr.write("executing %s\n" % cmd)
-    if os.system(cmd) != 0:
-        die("command failed: %s" % cmd)
+        sys.stderr.write("executing %s\n" % str(cmd))
+    subprocess.check_call(cmd, shell=expand)
 
 def p4_system(cmd):
     """Specifically invoke p4 as the system command. """
     real_cmd = p4_build_cmd(cmd)
-    return system(real_cmd)
+    expand = isinstance(real_cmd, basestring)
+    subprocess.check_call(real_cmd, shell=expand)
+
+def p4_integrate(src, dest):
+    p4_system(["integrate", "-Dt", src, dest])
+
+def p4_sync(path):
+    p4_system(["sync", path])
+
+def p4_add(f):
+    p4_system(["add", f])
+
+def p4_delete(f):
+    p4_system(["delete", f])
+
+def p4_edit(f):
+    p4_system(["edit", f])
+
+def p4_revert(f):
+    p4_system(["revert", f])
 
-def isP4Exec(kind):
-    """Determine if a Perforce 'kind' should have execute permission
+def p4_reopen(type, file):
+    p4_system(["reopen", "-t", type, file])
+
+#
+# Canonicalize the p4 type and return a tuple of the
+# base type, plus any modifiers.  See "p4 help filetypes"
+# for a list and explanation.
+#
+def split_p4_type(p4type):
+
+    p4_filetypes_historical = {
+        "ctempobj": "binary+Sw",
+        "ctext": "text+C",
+        "cxtext": "text+Cx",
+        "ktext": "text+k",
+        "kxtext": "text+kx",
+        "ltext": "text+F",
+        "tempobj": "binary+FSw",
+        "ubinary": "binary+F",
+        "uresource": "resource+F",
+        "uxbinary": "binary+Fx",
+        "xbinary": "binary+x",
+        "xltext": "text+Fx",
+        "xtempobj": "binary+Swx",
+        "xtext": "text+x",
+        "xunicode": "unicode+x",
+        "xutf16": "utf16+x",
+    }
+    if p4type in p4_filetypes_historical:
+        p4type = p4_filetypes_historical[p4type]
+    mods = ""
+    s = p4type.split("+")
+    base = s[0]
+    mods = ""
+    if len(s) > 1:
+        mods = s[1]
+    return (base, mods)
 
-    'p4 help filetypes' gives a list of the types.  If it starts with 'x',
-    or x follows one of a few letters.  Otherwise, if there is an 'x' after
-    a plus sign, it is also executable"""
-    return (re.search(r"(^[cku]?x)|\+.*x", kind) != None)
 
 def setP4ExecBit(file, mode):
     # Reopens an already open file and changes the execute bit to match
@@ -139,12 +199,12 @@ def setP4ExecBit(file, mode):
         if p4Type[-1] == "+":
             p4Type = p4Type[0:-1]
 
-    p4_system("reopen -t %s %s" % (p4Type, file))
+    p4_reopen(p4Type, file)
 
 def getP4OpenedType(file):
     # Returns the perforce file type for the given file.
 
-    result = p4_read_pipe("opened %s" % file)
+    result = p4_read_pipe(["opened", file])
     match = re.match(".*\((.+)\)\r?$", result)
     if match:
         return match.group(1)
@@ -200,9 +260,17 @@ def isModeExecChanged(src_mode, dst_mode):
     return isModeExec(src_mode) != isModeExec(dst_mode)
 
 def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None):
-    cmd = p4_build_cmd("-G %s" % (cmd))
+
+    if isinstance(cmd,basestring):
+        cmd = "-G " + cmd
+        expand = True
+    else:
+        cmd = ["-G"] + cmd
+        expand = False
+
+    cmd = p4_build_cmd(cmd)
     if verbose:
-        sys.stderr.write("Opening pipe: %s\n" % cmd)
+        sys.stderr.write("Opening pipe: %s\n" % str(cmd))
 
     # Use a temporary file to avoid deadlocks without
     # subprocess.communicate(), which would put another copy
@@ -210,11 +278,16 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None):
     stdin_file = None
     if stdin is not None:
         stdin_file = tempfile.TemporaryFile(prefix='p4-stdin', mode=stdin_mode)
-        stdin_file.write(stdin)
+        if isinstance(stdin,basestring):
+            stdin_file.write(stdin)
+        else:
+            for i in stdin:
+                stdin_file.write(i + '\n')
         stdin_file.flush()
         stdin_file.seek(0)
 
-    p4 = subprocess.Popen(cmd, shell=True,
+    p4 = subprocess.Popen(cmd,
+                          shell=expand,
                           stdin=stdin_file,
                           stdout=subprocess.PIPE)
 
@@ -247,7 +320,7 @@ def p4Where(depotPath):
     if not depotPath.endswith("/"):
         depotPath += "/"
     depotPath = depotPath + "..."
-    outputList = p4CmdList("where %s" % depotPath)
+    outputList = p4CmdList(["where", depotPath])
     output = None
     for entry in outputList:
         if "depotFile" in entry:
@@ -449,8 +522,10 @@ def originP4BranchesExist():
 
 def p4ChangesForPaths(depotPaths, changeRange):
     assert depotPaths
-    output = p4_read_pipe_lines("changes " + ' '.join (["%s...%s" % (p, changeRange)
-                                                        for p in depotPaths]))
+    cmd = ['changes']
+    for p in depotPaths:
+        cmd += ["%s...%s" % (p, changeRange)]
+    output = p4_read_pipe_lines(cmd)
 
     changes = {}
     for line in output:
@@ -533,7 +608,7 @@ class P4Debug(Command):
 
     def run(self, args):
         j = 0
-        for output in p4CmdList(" ".join(args)):
+        for output in p4CmdList(args):
             print 'Element: %d' % j
             j += 1
             print output
@@ -687,7 +762,7 @@ class P4Submit(Command, P4UserMap):
                 break
         if not client:
             die("could not get client spec")
-        results = p4CmdList("changes -c %s -m 1" % client)
+        results = p4CmdList(["changes", "-c", client, "-m", "1"])
         for r in results:
             if r.has_key('change'):
                 return r['change']
@@ -750,7 +825,7 @@ class P4Submit(Command, P4UserMap):
         # remove lines in the Files section that show changes to files outside the depot path we're committing into
         template = ""
         inFilesSection = False
-        for line in p4_read_pipe_lines("change -o"):
+        for line in p4_read_pipe_lines(['change', '-o']):
             if line.endswith("\r\n"):
                 line = line[:-2] + "\n"
             if inFilesSection:
@@ -772,6 +847,38 @@ class P4Submit(Command, P4UserMap):
 
         return template
 
+    def edit_template(self, template_file):
+        """Invoke the editor to let the user change the submission
+           message.  Return true if okay to continue with the submit."""
+
+        # if configured to skip the editing part, just submit
+        if gitConfig("git-p4.skipSubmitEdit") == "true":
+            return True
+
+        # look at the modification time, to check later if the user saved
+        # the file
+        mtime = os.stat(template_file).st_mtime
+
+        # invoke the editor
+        if os.environ.has_key("P4EDITOR"):
+            editor = os.environ.get("P4EDITOR")
+        else:
+            editor = read_pipe("git var GIT_EDITOR").strip()
+        system(editor + " " + template_file)
+
+        # If the file was not saved, prompt to see if this patch should
+        # be skipped.  But skip this verification step if configured so.
+        if gitConfig("git-p4.skipSubmitEditCheck") == "true":
+            return True
+
+        if os.stat(template_file).st_mtime <= mtime:
+            while True:
+                response = raw_input("Submit template unchanged. Submit anyway? [y]es, [n]o (skip this patch) ")
+                if response == 'y':
+                    return True
+                if response == 'n':
+                    return False
+
     def applyCommit(self, id):
         print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
 
@@ -807,7 +914,7 @@ class P4Submit(Command, P4UserMap):
             modifier = diff['status']
             path = diff['src']
             if modifier == "M":
-                p4_system("edit \"%s\"" % path)
+                p4_edit(path)
                 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
                     filesToChangeExecBit[path] = diff['dst_mode']
                 editedFiles.add(path)
@@ -822,21 +929,21 @@ class P4Submit(Command, P4UserMap):
                     filesToAdd.remove(path)
             elif modifier == "C":
                 src, dest = diff['src'], diff['dst']
-                p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest))
+                p4_integrate(src, dest)
                 if diff['src_sha1'] != diff['dst_sha1']:
-                    p4_system("edit \"%s\"" % (dest))
+                    p4_edit(dest)
                 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
-                    p4_system("edit \"%s\"" % (dest))
+                    p4_edit(dest)
                     filesToChangeExecBit[dest] = diff['dst_mode']
                 os.unlink(dest)
                 editedFiles.add(dest)
             elif modifier == "R":
                 src, dest = diff['src'], diff['dst']
-                p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest))
+                p4_integrate(src, dest)
                 if diff['src_sha1'] != diff['dst_sha1']:
-                    p4_system("edit \"%s\"" % (dest))
+                    p4_edit(dest)
                 if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
-                    p4_system("edit \"%s\"" % (dest))
+                    p4_edit(dest)
                     filesToChangeExecBit[dest] = diff['dst_mode']
                 os.unlink(dest)
                 editedFiles.add(dest)
@@ -859,9 +966,9 @@ class P4Submit(Command, P4UserMap):
             if response == "s":
                 print "Skipping! Good luck with the next patches..."
                 for f in editedFiles:
-                    p4_system("revert \"%s\"" % f);
+                    p4_revert(f)
                 for f in filesToAdd:
-                    system("rm %s" %f)
+                    os.remove(f)
                 return
             elif response == "a":
                 os.system(applyPatchCmd)
@@ -882,10 +989,10 @@ class P4Submit(Command, P4UserMap):
         system(applyPatchCmd)
 
         for f in filesToAdd:
-            p4_system("add \"%s\"" % f)
+            p4_add(f)
         for f in filesToDelete:
-            p4_system("revert \"%s\"" % f)
-            p4_system("delete \"%s\"" % f)
+            p4_revert(f)
+            p4_delete(f)
 
         # Set/clear executable bits
         for f in filesToChangeExecBit.keys():
@@ -907,7 +1014,7 @@ class P4Submit(Command, P4UserMap):
                 del(os.environ["P4DIFF"])
             diff = ""
             for editedFile in editedFiles:
-                diff += p4_read_pipe("diff -du %r" % editedFile)
+                diff += p4_read_pipe(['diff', '-du', editedFile])
 
             newdiff = ""
             for newFile in filesToAdd:
@@ -926,7 +1033,7 @@ class P4Submit(Command, P4UserMap):
 
             separatorLine = "######## everything below this line is just the diff #######\n"
 
-            [handle, fileName] = tempfile.mkstemp()
+            (handle, fileName) = tempfile.mkstemp()
             tmpFile = os.fdopen(handle, "w+")
             if self.isWindows:
                 submitTemplate = submitTemplate.replace("\n", "\r\n")
@@ -934,46 +1041,31 @@ class P4Submit(Command, P4UserMap):
                 newdiff = newdiff.replace("\n", "\r\n")
             tmpFile.write(submitTemplate + separatorLine + diff + newdiff)
             tmpFile.close()
-            mtime = os.stat(fileName).st_mtime
-            if os.environ.has_key("P4EDITOR"):
-                editor = os.environ.get("P4EDITOR")
-            else:
-                editor = read_pipe("git var GIT_EDITOR").strip()
-            system(editor + " " + fileName)
-
-            if gitConfig("git-p4.skipSubmitEditCheck") == "true":
-                checkModTime = False
-            else:
-                checkModTime = True
-
-            response = "y"
-            if checkModTime and (os.stat(fileName).st_mtime <= mtime):
-                response = "x"
-                while response != "y" and response != "n":
-                    response = raw_input("Submit template unchanged. Submit anyway? [y]es, [n]o (skip this patch) ")
 
-            if response == "y":
+            if self.edit_template(fileName):
+                # read the edited message and submit
                 tmpFile = open(fileName, "rb")
                 message = tmpFile.read()
                 tmpFile.close()
                 submitTemplate = message[:message.index(separatorLine)]
                 if self.isWindows:
                     submitTemplate = submitTemplate.replace("\r\n", "\n")
-                p4_write_pipe("submit -i", submitTemplate)
+                p4_write_pipe(['submit', '-i'], submitTemplate)
 
                 if self.preserveUser:
                     if p4User:
                         # Get last changelist number. Cannot easily get it from
-                        # the submit command output as the output is unmarshalled.
+                        # the submit command output as the output is
+                        # unmarshalled.
                         changelist = self.lastP4Changelist()
                         self.modifyChangelistUser(changelist, p4User)
-
             else:
+                # skip this patch
                 for f in editedFiles:
-                    p4_system("revert \"%s\"" % f);
+                    p4_revert(f)
                 for f in filesToAdd:
-                    p4_system("revert \"%s\"" % f);
-                    system("rm %s" %f)
+                    p4_revert(f)
+                    os.remove(f)
 
             os.remove(fileName)
         else:
@@ -1026,8 +1118,7 @@ class P4Submit(Command, P4UserMap):
 
         chdir(self.clientPath)
         print "Synchronizing p4 checkout..."
-        p4_system("sync ...")
-
+        p4_sync("...")
         self.check()
 
         commits = []
@@ -1219,38 +1310,66 @@ class P4Sync(Command, P4UserMap):
     # - helper for streamP4Files
 
     def streamOneP4File(self, file, contents):
-        if file["type"] == "apple":
-            print "\nfile %s is a strange apple file that forks. Ignoring" % \
-                file['depotFile']
-            return
-
         relPath = self.stripRepoPath(file['depotFile'], self.branchPrefixes)
         relPath = self.wildcard_decode(relPath)
         if verbose:
             sys.stderr.write("%s\n" % relPath)
 
-        mode = "644"
-        if isP4Exec(file["type"]):
-            mode = "755"
-        elif file["type"] == "symlink":
-            mode = "120000"
-            # p4 print on a symlink contains "target\n", so strip it off
+        (type_base, type_mods) = split_p4_type(file["type"])
+
+        git_mode = "100644"
+        if "x" in type_mods:
+            git_mode = "100755"
+        if type_base == "symlink":
+            git_mode = "120000"
+            # p4 print on a symlink contains "target\n"; remove the newline
             data = ''.join(contents)
             contents = [data[:-1]]
 
-        if self.isWindows and file["type"].endswith("text"):
+        if type_base == "utf16":
+            # p4 delivers different text in the python output to -G
+            # than it does when using "print -o", or normal p4 client
+            # operations.  utf16 is converted to ascii or utf8, perhaps.
+            # But ascii text saved as -t utf16 is completely mangled.
+            # Invoke print -o to get the real contents.
+            text = p4_read_pipe(['print', '-q', '-o', '-', file['depotFile']])
+            contents = [ text ]
+
+        if type_base == "apple":
+            # Apple filetype files will be streamed as a concatenation of
+            # its appledouble header and the contents.  This is useless
+            # on both macs and non-macs.  If using "print -q -o xx", it
+            # will create "xx" with the data, and "%xx" with the header.
+            # This is also not very useful.
+            #
+            # Ideally, someday, this script can learn how to generate
+            # appledouble files directly and import those to git, but
+            # non-mac machines can never find a use for apple filetype.
+            print "\nIgnoring apple filetype file %s" % file['depotFile']
+            return
+
+        # Perhaps windows wants unicode, utf16 newlines translated too;
+        # but this is not doing it.
+        if self.isWindows and type_base == "text":
             mangled = []
             for data in contents:
                 data = data.replace("\r\n", "\n")
                 mangled.append(data)
             contents = mangled
 
-        if file['type'] in ('text+ko', 'unicode+ko', 'binary+ko'):
-            contents = map(lambda text: re.sub(r'(?i)\$(Id|Header):[^$]*\$',r'$\1$', text), contents)
-        elif file['type'] in ('text+k', 'ktext', 'kxtext', 'unicode+k', 'binary+k'):
-            contents = map(lambda text: re.sub(r'\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$\n]*\$',r'$\1$', text), contents)
+        # Note that we do not try to de-mangle keywords on utf16 files,
+        # even though in theory somebody may want that.
+        if type_base in ("text", "unicode", "binary"):
+            if "ko" in type_mods:
+                text = ''.join(contents)
+                text = re.sub(r'\$(Id|Header):[^$]*\$', r'$\1$', text)
+                contents = [ text ]
+            elif "k" in type_mods:
+                text = ''.join(contents)
+                text = re.sub(r'\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$]*\$', r'$\1$', text)
+                contents = [ text ]
 
-        self.gitStream.write("M %s inline %s\n" % (mode, relPath))
+        self.gitStream.write("M %s inline %s\n" % (git_mode, relPath))
 
         # total length...
         length = 0
@@ -1322,10 +1441,11 @@ class P4Sync(Command, P4UserMap):
             def streamP4FilesCbSelf(entry):
                 self.streamP4FilesCb(entry)
 
-            p4CmdList("-x - print",
-                '\n'.join(['%s#%s' % (f['path'], f['rev'])
-                                                  for f in filesToRead]),
-                cb=streamP4FilesCbSelf)
+            fileArgs = ['%s#%s' % (f['path'], f['rev']) for f in filesToRead]
+
+            p4CmdList(["-x", "-", "print"],
+                      stdin=fileArgs,
+                      cb=streamP4FilesCbSelf)
 
             # do the last chunk
             if self.stream_file.has_key('depotFile'):
@@ -1386,8 +1506,8 @@ class P4Sync(Command, P4UserMap):
             if self.verbose:
                 print "Change %s is labelled %s" % (change, labelDetails)
 
-            files = p4CmdList("files " + ' '.join (["%s...@%s" % (p, change)
-                                                    for p in branchPrefixes]))
+            files = p4CmdList(["files"] + ["%s...@%s" % (p, change)
+                                                    for p in branchPrefixes])
 
             if len(files) == len(labelRevisions):
 
@@ -1435,9 +1555,9 @@ class P4Sync(Command, P4UserMap):
             newestChange = 0
             if self.verbose:
                 print "Querying files for label %s" % label
-            for file in p4CmdList("files "
-                                  +  ' '.join (["%s...@%s" % (p, label)
-                                                for p in self.depotPaths])):
+            for file in p4CmdList(["files"] +
+                                      ["%s...@%s" % (p, label)
+                                          for p in self.depotPaths]):
                 revisions[file["depotFile"]] = file["rev"]
                 change = int(file["change"])
                 if change > newestChange:
@@ -1692,10 +1812,9 @@ class P4Sync(Command, P4UserMap):
         newestRevision = 0
 
         fileCnt = 0
-        for info in p4CmdList("files "
-                              +  ' '.join(["%s...%s"
-                                           % (p, revision)
-                                           for p in self.depotPaths])):
+        fileArgs = ["%s...%s" % (p,revision) for p in self.depotPaths]
+
+        for info in p4CmdList(["files"] + fileArgs):
 
             if 'code' in info and info['code'] == 'error':
                 sys.stderr.write("p4 returned an error: %s\n"
index 52003ae9045626077009811cd8e10c1135d69cd0..5044a121e01668d941fdc09124b9db354213c460 100644 (file)
@@ -202,11 +202,24 @@ able to find the relevant client.  This client spec will be used to
 both filter the files cloned by git and set the directory layout as
 specified in the client (this implies --keep-path style semantics).
 
-git-p4.skipSubmitModTimeCheck
+git-p4.skipSubmitEdit
 
-  git config [--global] git-p4.skipSubmitModTimeCheck false
+  git config [--global] git-p4.skipSubmitEdit false
 
-If true, submit will not check if the p4 change template has been modified.
+Normally, git-p4 invokes an editor after each commit is applied so
+that you can make changes to the submit message.  Setting this
+variable to true will skip the editing step, submitting the change as is.
+
+git-p4.skipSubmitEditCheck
+
+  git config [--global] git-p4.skipSubmitEditCheck false
+
+After the editor is invoked, git-p4 normally makes sure you saved the
+change description, as an indication that you did indeed read it over
+and edit it.  You can quit without saving to abort the submit (or skip
+this change and continue).  Setting this variable to true will cause
+git-p4 not to check if you saved the change description.  This variable
+only matters if git-p4.skipSubmitEdit has not been set to true.
 
 git-p4.preserveUser
 
diff --git a/contrib/git-jump/README b/contrib/git-jump/README
new file mode 100644 (file)
index 0000000..1cebc32
--- /dev/null
@@ -0,0 +1,92 @@
+git-jump
+========
+
+Git-jump is a script for helping you jump to "interesting" parts of your
+project in your editor. It works by outputting a set of interesting
+spots in the "quickfix" format, which editors like vim can use as a
+queue of places to visit (this feature is usually used to jump to errors
+produced by a compiler). For example, given a diff like this:
+
+------------------------------------
+diff --git a/foo.c b/foo.c
+index a655540..5a59044 100644
+--- a/foo.c
++++ b/foo.c
+@@ -1,3 +1,3 @@
+ int main(void) {
+-  printf("hello word!\n");
++  printf("hello world!\n");
+ }
+-----------------------------------
+
+git-jump will feed this to the editor:
+
+-----------------------------------
+foo.c:2: printf("hello word!\n");
+-----------------------------------
+
+Obviously this trivial case isn't that interesting; you could just open
+`foo.c` yourself. But when you have many changes scattered across a
+project, you can use the editor's support to "jump" from point to point.
+
+Git-jump can generate three types of interesting lists:
+
+  1. The beginning of any diff hunks.
+
+  2. The beginning of any merge conflict markers.
+
+  3. Any grep matches.
+
+
+Using git-jump
+--------------
+
+To use it, just drop git-jump in your PATH, and then invoke it like
+this:
+
+--------------------------------------------------
+# jump to changes not yet staged for commit
+git jump diff
+
+# jump to changes that are staged for commit; you can give
+# arbitrary diff options
+git jump diff --cached
+
+# jump to merge conflicts
+git jump merge
+
+# jump to all instances of foo_bar
+git jump grep foo_bar
+
+# same as above, but case-insensitive; you can give
+# arbitrary grep options
+git jump grep -i foo_bar
+--------------------------------------------------
+
+
+Related Programs
+----------------
+
+You can accomplish some of the same things with individual tools. For
+example, you can use `git mergetool` to start vimdiff on each unmerged
+file. `git jump merge` is for the vim-wielding luddite who just wants to
+jump straight to the conflict text with no fanfare.
+
+As of git v1.7.2, `git grep` knows the `--open-files-in-pager` option,
+which does something similar to `git jump grep`. However, it is limited
+to positioning the cursor to the correct line in only the first file,
+leaving you to locate subsequent hits in that file or other files using
+the editor or pager. By contrast, git-jump provides the editor with a
+complete list of files and line numbers for each match.
+
+
+Limitations
+-----------
+
+This scripts was written and tested with vim. Given that the quickfix
+format is the same as what gcc produces, I expect emacs users have a
+similar feature for iterating through the list, but I know nothing about
+how to activate it.
+
+The shell snippets to generate the quickfix lines will almost certainly
+choke on filenames with exotic characters (like newlines).
diff --git a/contrib/git-jump/git-jump b/contrib/git-jump/git-jump
new file mode 100755 (executable)
index 0000000..a33674e
--- /dev/null
@@ -0,0 +1,69 @@
+#!/bin/sh
+
+usage() {
+       cat <<\EOF
+usage: git jump <mode> [<args>]
+
+Jump to interesting elements in an editor.
+The <mode> parameter is one of:
+
+diff: elements are diff hunks. Arguments are given to diff.
+
+merge: elements are merge conflicts. Arguments are ignored.
+
+grep: elements are grep hits. Arguments are given to grep.
+EOF
+}
+
+open_editor() {
+       editor=`git var GIT_EDITOR`
+       eval "$editor -q \$1"
+}
+
+mode_diff() {
+       git diff --relative "$@" |
+       perl -ne '
+       if (m{^\+\+\+ b/(.*)}) { $file = $1; next }
+       defined($file) or next;
+       if (m/^@@ .*\+(\d+)/) { $line = $1; next }
+       defined($line) or next;
+       if (/^ /) { $line++; next }
+       if (/^[-+]\s*(.*)/) {
+               print "$file:$line: $1\n";
+               $line = undef;
+       }
+       '
+}
+
+mode_merge() {
+       git ls-files -u |
+       perl -pe 's/^.*?\t//' |
+       sort -u |
+       while IFS= read fn; do
+               grep -Hn '^<<<<<<<' "$fn"
+       done
+}
+
+# Grep -n generates nice quickfix-looking lines by itself,
+# but let's clean up extra whitespace, so they look better if the
+# editor shows them to us in the status bar.
+mode_grep() {
+       git grep -n "$@" |
+       perl -pe '
+       s/[ \t]+/ /g;
+       s/^ *//;
+       '
+}
+
+if test $# -lt 1; then
+       usage >&2
+       exit 1
+fi
+mode=$1; shift
+
+trap 'rm -f "$tmp"' 0 1 2 3 15
+tmp=`mktemp -t git-jump.XXXXXX` || exit 1
+type "mode_$mode" >/dev/null 2>&1 || { usage >&2; exit 1; }
+"mode_$mode" "$@" >"$tmp"
+test -s "$tmp" || exit 0
+open_editor "$tmp"
diff --git a/contrib/mw-to-git/git-remote-mediawiki b/contrib/mw-to-git/git-remote-mediawiki
new file mode 100755 (executable)
index 0000000..c18bfa1
--- /dev/null
@@ -0,0 +1,827 @@
+#! /usr/bin/perl
+
+# Copyright (C) 2011
+#     Jérémie Nikaes <jeremie.nikaes@ensimag.imag.fr>
+#     Arnaud Lacurie <arnaud.lacurie@ensimag.imag.fr>
+#     Claire Fousse <claire.fousse@ensimag.imag.fr>
+#     David Amouyal <david.amouyal@ensimag.imag.fr>
+#     Matthieu Moy <matthieu.moy@grenoble-inp.fr>
+# License: GPL v2 or later
+
+# Gateway between Git and MediaWiki.
+#   https://github.com/Bibzball/Git-Mediawiki/wiki
+#
+# Known limitations:
+#
+# - Only wiki pages are managed, no support for [[File:...]]
+#   attachments.
+#
+# - Poor performance in the best case: it takes forever to check
+#   whether we're up-to-date (on fetch or push) or to fetch a few
+#   revisions from a large wiki, because we use exclusively a
+#   page-based synchronization. We could switch to a wiki-wide
+#   synchronization when the synchronization involves few revisions
+#   but the wiki is large.
+#
+# - Git renames could be turned into MediaWiki renames (see TODO
+#   below)
+#
+# - login/password support requires the user to write the password
+#   cleartext in a file (see TODO below).
+#
+# - No way to import "one page, and all pages included in it"
+#
+# - Multiple remote MediaWikis have not been very well tested.
+
+use strict;
+use MediaWiki::API;
+use DateTime::Format::ISO8601;
+use encoding 'utf8';
+
+# use encoding 'utf8' doesn't change STDERROR
+# but we're going to output UTF-8 filenames to STDERR
+binmode STDERR, ":utf8";
+
+use URI::Escape;
+use warnings;
+
+# Mediawiki filenames can contain forward slashes. This variable decides by which pattern they should be replaced
+use constant SLASH_REPLACEMENT => "%2F";
+
+# It's not always possible to delete pages (may require some
+# priviledges). Deleted pages are replaced with this content.
+use constant DELETED_CONTENT => "[[Category:Deleted]]\n";
+
+# It's not possible to create empty pages. New empty files in Git are
+# sent with this content instead.
+use constant EMPTY_CONTENT => "<!-- empty page -->\n";
+
+# used to reflect file creation or deletion in diff.
+use constant NULL_SHA1 => "0000000000000000000000000000000000000000";
+
+my $remotename = $ARGV[0];
+my $url = $ARGV[1];
+
+# Accept both space-separated and multiple keys in config file.
+# Spaces should be written as _ anyway because we'll use chomp.
+my @tracked_pages = split(/[ \n]/, run_git("config --get-all remote.". $remotename .".pages"));
+chomp(@tracked_pages);
+
+# Just like @tracked_pages, but for MediaWiki categories.
+my @tracked_categories = split(/[ \n]/, run_git("config --get-all remote.". $remotename .".categories"));
+chomp(@tracked_categories);
+
+my $wiki_login = run_git("config --get remote.". $remotename .".mwLogin");
+# TODO: ideally, this should be able to read from keyboard, but we're
+# inside a remote helper, so our stdin is connect to git, not to a
+# terminal.
+my $wiki_passwd = run_git("config --get remote.". $remotename .".mwPassword");
+my $wiki_domain = run_git("config --get remote.". $remotename .".mwDomain");
+chomp($wiki_login);
+chomp($wiki_passwd);
+chomp($wiki_domain);
+
+# Import only last revisions (both for clone and fetch)
+my $shallow_import = run_git("config --get --bool remote.". $remotename .".shallow");
+chomp($shallow_import);
+$shallow_import = ($shallow_import eq "true");
+
+# Dumb push: don't update notes and mediawiki ref to reflect the last push.
+#
+# Configurable with mediawiki.dumbPush, or per-remote with
+# remote.<remotename>.dumbPush.
+#
+# This means the user will have to re-import the just-pushed
+# revisions. On the other hand, this means that the Git revisions
+# corresponding to MediaWiki revisions are all imported from the wiki,
+# regardless of whether they were initially created in Git or from the
+# web interface, hence all users will get the same history (i.e. if
+# the push from Git to MediaWiki loses some information, everybody
+# will get the history with information lost). If the import is
+# deterministic, this means everybody gets the same sha1 for each
+# MediaWiki revision.
+my $dumb_push = run_git("config --get --bool remote.$remotename.dumbPush");
+unless ($dumb_push) {
+       $dumb_push = run_git("config --get --bool mediawiki.dumbPush");
+}
+chomp($dumb_push);
+$dumb_push = ($dumb_push eq "true");
+
+my $wiki_name = $url;
+$wiki_name =~ s/[^\/]*:\/\///;
+# If URL is like http://user:password@example.com/, we clearly don't
+# want the password in $wiki_name. While we're there, also remove user
+# and '@' sign, to avoid author like MWUser@HTTPUser@host.com
+$wiki_name =~ s/^.*@//;
+
+# Commands parser
+my $entry;
+my @cmd;
+while (<STDIN>) {
+       chomp;
+       @cmd = split(/ /);
+       if (defined($cmd[0])) {
+               # Line not blank
+               if ($cmd[0] eq "capabilities") {
+                       die("Too many arguments for capabilities") unless (!defined($cmd[1]));
+                       mw_capabilities();
+               } elsif ($cmd[0] eq "list") {
+                       die("Too many arguments for list") unless (!defined($cmd[2]));
+                       mw_list($cmd[1]);
+               } elsif ($cmd[0] eq "import") {
+                       die("Invalid arguments for import") unless ($cmd[1] ne "" && !defined($cmd[2]));
+                       mw_import($cmd[1]);
+               } elsif ($cmd[0] eq "option") {
+                       die("Too many arguments for option") unless ($cmd[1] ne "" && $cmd[2] ne "" && !defined($cmd[3]));
+                       mw_option($cmd[1],$cmd[2]);
+               } elsif ($cmd[0] eq "push") {
+                       mw_push($cmd[1]);
+               } else {
+                       print STDERR "Unknown command. Aborting...\n";
+                       last;
+               }
+       } else {
+               # blank line: we should terminate
+               last;
+       }
+
+       BEGIN { $| = 1 } # flush STDOUT, to make sure the previous
+                        # command is fully processed.
+}
+
+########################## Functions ##############################
+
+# MediaWiki API instance, created lazily.
+my $mediawiki;
+
+sub mw_connect_maybe {
+       if ($mediawiki) {
+           return;
+       }
+       $mediawiki = MediaWiki::API->new;
+       $mediawiki->{config}->{api_url} = "$url/api.php";
+       if ($wiki_login) {
+               if (!$mediawiki->login({
+                       lgname => $wiki_login,
+                       lgpassword => $wiki_passwd,
+                       lgdomain => $wiki_domain,
+               })) {
+                       print STDERR "Failed to log in mediawiki user \"$wiki_login\" on $url\n";
+                       print STDERR "(error " .
+                           $mediawiki->{error}->{code} . ': ' .
+                           $mediawiki->{error}->{details} . ")\n";
+                       exit 1;
+               } else {
+                       print STDERR "Logged in with user \"$wiki_login\".\n";
+               }
+       }
+}
+
+sub get_mw_first_pages {
+       my $some_pages = shift;
+       my @some_pages = @{$some_pages};
+
+       my $pages = shift;
+
+       # pattern 'page1|page2|...' required by the API
+       my $titles = join('|', @some_pages);
+
+       my $mw_pages = $mediawiki->api({
+               action => 'query',
+               titles => $titles,
+       });
+       if (!defined($mw_pages)) {
+               print STDERR "fatal: could not query the list of wiki pages.\n";
+               print STDERR "fatal: '$url' does not appear to be a mediawiki\n";
+               print STDERR "fatal: make sure '$url/api.php' is a valid page.\n";
+               exit 1;
+       }
+       while (my ($id, $page) = each(%{$mw_pages->{query}->{pages}})) {
+               if ($id < 0) {
+                       print STDERR "Warning: page $page->{title} not found on wiki\n";
+               } else {
+                       $pages->{$page->{title}} = $page;
+               }
+       }
+}
+
+sub get_mw_pages {
+       mw_connect_maybe();
+
+       my %pages; # hash on page titles to avoid duplicates
+       my $user_defined;
+       if (@tracked_pages) {
+               $user_defined = 1;
+               # The user provided a list of pages titles, but we
+               # still need to query the API to get the page IDs.
+
+               my @some_pages = @tracked_pages;
+               while (@some_pages) {
+                       my $last = 50;
+                       if ($#some_pages < $last) {
+                               $last = $#some_pages;
+                       }
+                       my @slice = @some_pages[0..$last];
+                       get_mw_first_pages(\@slice, \%pages);
+                       @some_pages = @some_pages[51..$#some_pages];
+               }
+       }
+       if (@tracked_categories) {
+               $user_defined = 1;
+               foreach my $category (@tracked_categories) {
+                       if (index($category, ':') < 0) {
+                               # Mediawiki requires the Category
+                               # prefix, but let's not force the user
+                               # to specify it.
+                               $category = "Category:" . $category;
+                       }
+                       my $mw_pages = $mediawiki->list( {
+                               action => 'query',
+                               list => 'categorymembers',
+                               cmtitle => $category,
+                               cmlimit => 'max' } )
+                           || die $mediawiki->{error}->{code} . ': ' . $mediawiki->{error}->{details};
+                       foreach my $page (@{$mw_pages}) {
+                               $pages{$page->{title}} = $page;
+                       }
+               }
+       }
+       if (!$user_defined) {
+               # No user-provided list, get the list of pages from
+               # the API.
+               my $mw_pages = $mediawiki->list({
+                       action => 'query',
+                       list => 'allpages',
+                       aplimit => 500,
+               });
+               if (!defined($mw_pages)) {
+                       print STDERR "fatal: could not get the list of wiki pages.\n";
+                       print STDERR "fatal: '$url' does not appear to be a mediawiki\n";
+                       print STDERR "fatal: make sure '$url/api.php' is a valid page.\n";
+                       exit 1;
+               }
+               foreach my $page (@{$mw_pages}) {
+                       $pages{$page->{title}} = $page;
+               }
+       }
+       return values(%pages);
+}
+
+sub run_git {
+       open(my $git, "-|:encoding(UTF-8)", "git " . $_[0]);
+       my $res = do { local $/; <$git> };
+       close($git);
+
+       return $res;
+}
+
+
+sub get_last_local_revision {
+       # Get note regarding last mediawiki revision
+       my $note = run_git("notes --ref=$remotename/mediawiki show refs/mediawiki/$remotename/master 2>/dev/null");
+       my @note_info = split(/ /, $note);
+
+       my $lastrevision_number;
+       if (!(defined($note_info[0]) && $note_info[0] eq "mediawiki_revision:")) {
+               print STDERR "No previous mediawiki revision found";
+               $lastrevision_number = 0;
+       } else {
+               # Notes are formatted : mediawiki_revision: #number
+               $lastrevision_number = $note_info[1];
+               chomp($lastrevision_number);
+               print STDERR "Last local mediawiki revision found is $lastrevision_number";
+       }
+       return $lastrevision_number;
+}
+
+# Remember the timestamp corresponding to a revision id.
+my %basetimestamps;
+
+sub get_last_remote_revision {
+       mw_connect_maybe();
+
+       my @pages = get_mw_pages();
+
+       my $max_rev_num = 0;
+
+       foreach my $page (@pages) {
+               my $id = $page->{pageid};
+
+               my $query = {
+                       action => 'query',
+                       prop => 'revisions',
+                       rvprop => 'ids|timestamp',
+                       pageids => $id,
+               };
+
+               my $result = $mediawiki->api($query);
+
+               my $lastrev = pop(@{$result->{query}->{pages}->{$id}->{revisions}});
+
+               $basetimestamps{$lastrev->{revid}} = $lastrev->{timestamp};
+
+               $max_rev_num = ($lastrev->{revid} > $max_rev_num ? $lastrev->{revid} : $max_rev_num);
+       }
+
+       print STDERR "Last remote revision found is $max_rev_num.\n";
+       return $max_rev_num;
+}
+
+# Clean content before sending it to MediaWiki
+sub mediawiki_clean {
+       my $string = shift;
+       my $page_created = shift;
+       # Mediawiki does not allow blank space at the end of a page and ends with a single \n.
+       # This function right trims a string and adds a \n at the end to follow this rule
+       $string =~ s/\s+$//;
+       if ($string eq "" && $page_created) {
+               # Creating empty pages is forbidden.
+               $string = EMPTY_CONTENT;
+       }
+       return $string."\n";
+}
+
+# Filter applied on MediaWiki data before adding them to Git
+sub mediawiki_smudge {
+       my $string = shift;
+       if ($string eq EMPTY_CONTENT) {
+               $string = "";
+       }
+       # This \n is important. This is due to mediawiki's way to handle end of files.
+       return $string."\n";
+}
+
+sub mediawiki_clean_filename {
+       my $filename = shift;
+       $filename =~ s/@{[SLASH_REPLACEMENT]}/\//g;
+       # [, ], |, {, and } are forbidden by MediaWiki, even URL-encoded.
+       # Do a variant of URL-encoding, i.e. looks like URL-encoding,
+       # but with _ added to prevent MediaWiki from thinking this is
+       # an actual special character.
+       $filename =~ s/[\[\]\{\}\|]/sprintf("_%%_%x", ord($&))/ge;
+       # If we use the uri escape before
+       # we should unescape here, before anything
+
+       return $filename;
+}
+
+sub mediawiki_smudge_filename {
+       my $filename = shift;
+       $filename =~ s/\//@{[SLASH_REPLACEMENT]}/g;
+       $filename =~ s/ /_/g;
+       # Decode forbidden characters encoded in mediawiki_clean_filename
+       $filename =~ s/_%_([0-9a-fA-F][0-9a-fA-F])/sprintf("%c", hex($1))/ge;
+       return $filename;
+}
+
+sub literal_data {
+       my ($content) = @_;
+       print STDOUT "data ", bytes::length($content), "\n", $content;
+}
+
+sub mw_capabilities {
+       # Revisions are imported to the private namespace
+       # refs/mediawiki/$remotename/ by the helper and fetched into
+       # refs/remotes/$remotename later by fetch.
+       print STDOUT "refspec refs/heads/*:refs/mediawiki/$remotename/*\n";
+       print STDOUT "import\n";
+       print STDOUT "list\n";
+       print STDOUT "push\n";
+       print STDOUT "\n";
+}
+
+sub mw_list {
+       # MediaWiki do not have branches, we consider one branch arbitrarily
+       # called master, and HEAD pointing to it.
+       print STDOUT "? refs/heads/master\n";
+       print STDOUT "\@refs/heads/master HEAD\n";
+       print STDOUT "\n";
+}
+
+sub mw_option {
+       print STDERR "remote-helper command 'option $_[0]' not yet implemented\n";
+       print STDOUT "unsupported\n";
+}
+
+sub fetch_mw_revisions_for_page {
+       my $page = shift;
+       my $id = shift;
+       my $fetch_from = shift;
+       my @page_revs = ();
+       my $query = {
+               action => 'query',
+               prop => 'revisions',
+               rvprop => 'ids',
+               rvdir => 'newer',
+               rvstartid => $fetch_from,
+               rvlimit => 500,
+               pageids => $id,
+       };
+
+       my $revnum = 0;
+       # Get 500 revisions at a time due to the mediawiki api limit
+       while (1) {
+               my $result = $mediawiki->api($query);
+
+               # Parse each of those 500 revisions
+               foreach my $revision (@{$result->{query}->{pages}->{$id}->{revisions}}) {
+                       my $page_rev_ids;
+                       $page_rev_ids->{pageid} = $page->{pageid};
+                       $page_rev_ids->{revid} = $revision->{revid};
+                       push(@page_revs, $page_rev_ids);
+                       $revnum++;
+               }
+               last unless $result->{'query-continue'};
+               $query->{rvstartid} = $result->{'query-continue'}->{revisions}->{rvstartid};
+       }
+       if ($shallow_import && @page_revs) {
+               print STDERR "  Found 1 revision (shallow import).\n";
+               @page_revs = sort {$b->{revid} <=> $a->{revid}} (@page_revs);
+               return $page_revs[0];
+       }
+       print STDERR "  Found ", $revnum, " revision(s).\n";
+       return @page_revs;
+}
+
+sub fetch_mw_revisions {
+       my $pages = shift; my @pages = @{$pages};
+       my $fetch_from = shift;
+
+       my @revisions = ();
+       my $n = 1;
+       foreach my $page (@pages) {
+               my $id = $page->{pageid};
+
+               print STDERR "page $n/", scalar(@pages), ": ". $page->{title} ."\n";
+               $n++;
+               my @page_revs = fetch_mw_revisions_for_page($page, $id, $fetch_from);
+               @revisions = (@page_revs, @revisions);
+       }
+
+       return ($n, @revisions);
+}
+
+sub import_file_revision {
+       my $commit = shift;
+       my %commit = %{$commit};
+       my $full_import = shift;
+       my $n = shift;
+
+       my $title = $commit{title};
+       my $comment = $commit{comment};
+       my $content = $commit{content};
+       my $author = $commit{author};
+       my $date = $commit{date};
+
+       print STDOUT "commit refs/mediawiki/$remotename/master\n";
+       print STDOUT "mark :$n\n";
+       print STDOUT "committer $author <$author\@$wiki_name> ", $date->epoch, " +0000\n";
+       literal_data($comment);
+
+       # If it's not a clone, we need to know where to start from
+       if (!$full_import && $n == 1) {
+               print STDOUT "from refs/mediawiki/$remotename/master^0\n";
+       }
+       if ($content ne DELETED_CONTENT) {
+               print STDOUT "M 644 inline $title.mw\n";
+               literal_data($content);
+               print STDOUT "\n\n";
+       } else {
+               print STDOUT "D $title.mw\n";
+       }
+
+       # mediawiki revision number in the git note
+       if ($full_import && $n == 1) {
+               print STDOUT "reset refs/notes/$remotename/mediawiki\n";
+       }
+       print STDOUT "commit refs/notes/$remotename/mediawiki\n";
+       print STDOUT "committer $author <$author\@$wiki_name> ", $date->epoch, " +0000\n";
+       literal_data("Note added by git-mediawiki during import");
+       if (!$full_import && $n == 1) {
+               print STDOUT "from refs/notes/$remotename/mediawiki^0\n";
+       }
+       print STDOUT "N inline :$n\n";
+       literal_data("mediawiki_revision: " . $commit{mw_revision});
+       print STDOUT "\n\n";
+}
+
+# parse a sequence of
+# <cmd> <arg1>
+# <cmd> <arg2>
+# \n
+# (like batch sequence of import and sequence of push statements)
+sub get_more_refs {
+       my $cmd = shift;
+       my @refs;
+       while (1) {
+               my $line = <STDIN>;
+               if ($line =~ m/^$cmd (.*)$/) {
+                       push(@refs, $1);
+               } elsif ($line eq "\n") {
+                       return @refs;
+               } else {
+                       die("Invalid command in a '$cmd' batch: ". $_);
+               }
+       }
+}
+
+sub mw_import {
+       # multiple import commands can follow each other.
+       my @refs = (shift, get_more_refs("import"));
+       foreach my $ref (@refs) {
+               mw_import_ref($ref);
+       }
+       print STDOUT "done\n";
+}
+
+sub mw_import_ref {
+       my $ref = shift;
+       # The remote helper will call "import HEAD" and
+       # "import refs/heads/master".
+       # Since HEAD is a symbolic ref to master (by convention,
+       # followed by the output of the command "list" that we gave),
+       # we don't need to do anything in this case.
+       if ($ref eq "HEAD") {
+               return;
+       }
+
+       mw_connect_maybe();
+
+       my @pages = get_mw_pages();
+
+       print STDERR "Searching revisions...\n";
+       my $last_local = get_last_local_revision();
+       my $fetch_from = $last_local + 1;
+       if ($fetch_from == 1) {
+               print STDERR ", fetching from beginning.\n";
+       } else {
+               print STDERR ", fetching from here.\n";
+       }
+       my ($n, @revisions) = fetch_mw_revisions(\@pages, $fetch_from);
+
+       # Creation of the fast-import stream
+       print STDERR "Fetching & writing export data...\n";
+
+       $n = 0;
+       my $last_timestamp = 0; # Placeholer in case $rev->timestamp is undefined
+
+       foreach my $pagerevid (sort {$a->{revid} <=> $b->{revid}} @revisions) {
+               # fetch the content of the pages
+               my $query = {
+                       action => 'query',
+                       prop => 'revisions',
+                       rvprop => 'content|timestamp|comment|user|ids',
+                       revids => $pagerevid->{revid},
+               };
+
+               my $result = $mediawiki->api($query);
+
+               my $rev = pop(@{$result->{query}->{pages}->{$pagerevid->{pageid}}->{revisions}});
+
+               $n++;
+
+               my %commit;
+               $commit{author} = $rev->{user} || 'Anonymous';
+               $commit{comment} = $rev->{comment} || '*Empty MediaWiki Message*';
+               $commit{title} = mediawiki_smudge_filename(
+                       $result->{query}->{pages}->{$pagerevid->{pageid}}->{title}
+                   );
+               $commit{mw_revision} = $pagerevid->{revid};
+               $commit{content} = mediawiki_smudge($rev->{'*'});
+
+               if (!defined($rev->{timestamp})) {
+                       $last_timestamp++;
+               } else {
+                       $last_timestamp = $rev->{timestamp};
+               }
+               $commit{date} = DateTime::Format::ISO8601->parse_datetime($last_timestamp);
+
+               print STDERR "$n/", scalar(@revisions), ": Revision #$pagerevid->{revid} of $commit{title}\n";
+
+               import_file_revision(\%commit, ($fetch_from == 1), $n);
+       }
+
+       if ($fetch_from == 1 && $n == 0) {
+               print STDERR "You appear to have cloned an empty MediaWiki.\n";
+               # Something has to be done remote-helper side. If nothing is done, an error is
+               # thrown saying that HEAD is refering to unknown object 0000000000000000000
+               # and the clone fails.
+       }
+}
+
+sub error_non_fast_forward {
+       my $advice = run_git("config --bool advice.pushNonFastForward");
+       chomp($advice);
+       if ($advice ne "false") {
+               # Native git-push would show this after the summary.
+               # We can't ask it to display it cleanly, so print it
+               # ourselves before.
+               print STDERR "To prevent you from losing history, non-fast-forward updates were rejected\n";
+               print STDERR "Merge the remote changes (e.g. 'git pull') before pushing again. See the\n";
+               print STDERR "'Note about fast-forwards' section of 'git push --help' for details.\n";
+       }
+       print STDOUT "error $_[0] \"non-fast-forward\"\n";
+       return 0;
+}
+
+sub mw_push_file {
+       my $diff_info = shift;
+       # $diff_info contains a string in this format:
+       # 100644 100644 <sha1_of_blob_before_commit> <sha1_of_blob_now> <status>
+       my @diff_info_split = split(/[ \t]/, $diff_info);
+
+       # Filename, including .mw extension
+       my $complete_file_name = shift;
+       # Commit message
+       my $summary = shift;
+       # MediaWiki revision number. Keep the previous one by default,
+       # in case there's no edit to perform.
+       my $newrevid = shift;
+
+       my $new_sha1 = $diff_info_split[3];
+       my $old_sha1 = $diff_info_split[2];
+       my $page_created = ($old_sha1 eq NULL_SHA1);
+       my $page_deleted = ($new_sha1 eq NULL_SHA1);
+       $complete_file_name = mediawiki_clean_filename($complete_file_name);
+
+       if (substr($complete_file_name,-3) eq ".mw") {
+               my $title = substr($complete_file_name,0,-3);
+
+               my $file_content;
+               if ($page_deleted) {
+                       # Deleting a page usually requires
+                       # special priviledges. A common
+                       # convention is to replace the page
+                       # with this content instead:
+                       $file_content = DELETED_CONTENT;
+               } else {
+                       $file_content = run_git("cat-file blob $new_sha1");
+               }
+
+               mw_connect_maybe();
+
+               my $result = $mediawiki->edit( {
+                       action => 'edit',
+                       summary => $summary,
+                       title => $title,
+                       basetimestamp => $basetimestamps{$newrevid},
+                       text => mediawiki_clean($file_content, $page_created),
+                                 }, {
+                                         skip_encoding => 1 # Helps with names with accentuated characters
+                                 });
+               if (!$result) {
+                       if ($mediawiki->{error}->{code} == 3) {
+                               # edit conflicts, considered as non-fast-forward
+                               print STDERR 'Warning: Error ' .
+                                   $mediawiki->{error}->{code} .
+                                   ' from mediwiki: ' . $mediawiki->{error}->{details} .
+                                   ".\n";
+                               return ($newrevid, "non-fast-forward");
+                       } else {
+                               # Other errors. Shouldn't happen => just die()
+                               die 'Fatal: Error ' .
+                                   $mediawiki->{error}->{code} .
+                                   ' from mediwiki: ' . $mediawiki->{error}->{details};
+                       }
+               }
+               $newrevid = $result->{edit}->{newrevid};
+               print STDERR "Pushed file: $new_sha1 - $title\n";
+       } else {
+               print STDERR "$complete_file_name not a mediawiki file (Not pushable on this version of git-remote-mediawiki).\n"
+       }
+       return ($newrevid, "ok");
+}
+
+sub mw_push {
+       # multiple push statements can follow each other
+       my @refsspecs = (shift, get_more_refs("push"));
+       my $pushed;
+       for my $refspec (@refsspecs) {
+               my ($force, $local, $remote) = $refspec =~ /^(\+)?([^:]*):([^:]*)$/
+                   or die("Invalid refspec for push. Expected <src>:<dst> or +<src>:<dst>");
+               if ($force) {
+                       print STDERR "Warning: forced push not allowed on a MediaWiki.\n";
+               }
+               if ($local eq "") {
+                       print STDERR "Cannot delete remote branch on a MediaWiki\n";
+                       print STDOUT "error $remote cannot delete\n";
+                       next;
+               }
+               if ($remote ne "refs/heads/master") {
+                       print STDERR "Only push to the branch 'master' is supported on a MediaWiki\n";
+                       print STDOUT "error $remote only master allowed\n";
+                       next;
+               }
+               if (mw_push_revision($local, $remote)) {
+                       $pushed = 1;
+               }
+       }
+
+       # Notify Git that the push is done
+       print STDOUT "\n";
+
+       if ($pushed && $dumb_push) {
+               print STDERR "Just pushed some revisions to MediaWiki.\n";
+               print STDERR "The pushed revisions now have to be re-imported, and your current branch\n";
+               print STDERR "needs to be updated with these re-imported commits. You can do this with\n";
+               print STDERR "\n";
+               print STDERR "  git pull --rebase\n";
+               print STDERR "\n";
+       }
+}
+
+sub mw_push_revision {
+       my $local = shift;
+       my $remote = shift; # actually, this has to be "refs/heads/master" at this point.
+       my $last_local_revid = get_last_local_revision();
+       print STDERR ".\n"; # Finish sentence started by get_last_local_revision()
+       my $last_remote_revid = get_last_remote_revision();
+       my $mw_revision = $last_remote_revid;
+
+       # Get sha1 of commit pointed by local HEAD
+       my $HEAD_sha1 = run_git("rev-parse $local 2>/dev/null"); chomp($HEAD_sha1);
+       # Get sha1 of commit pointed by remotes/$remotename/master
+       my $remoteorigin_sha1 = run_git("rev-parse refs/remotes/$remotename/master 2>/dev/null");
+       chomp($remoteorigin_sha1);
+
+       if ($last_local_revid > 0 &&
+           $last_local_revid < $last_remote_revid) {
+               return error_non_fast_forward($remote);
+       }
+
+       if ($HEAD_sha1 eq $remoteorigin_sha1) {
+               # nothing to push
+               return 0;
+       }
+
+       # Get every commit in between HEAD and refs/remotes/origin/master,
+       # including HEAD and refs/remotes/origin/master
+       my @commit_pairs = ();
+       if ($last_local_revid > 0) {
+               my $parsed_sha1 = $remoteorigin_sha1;
+               # Find a path from last MediaWiki commit to pushed commit
+               while ($parsed_sha1 ne $HEAD_sha1) {
+                       my @commit_info =  grep(/^$parsed_sha1/, split(/\n/, run_git("rev-list --children $local")));
+                       if (!@commit_info) {
+                               return error_non_fast_forward($remote);
+                       }
+                       my @commit_info_split = split(/ |\n/, $commit_info[0]);
+                       # $commit_info_split[1] is the sha1 of the commit to export
+                       # $commit_info_split[0] is the sha1 of its direct child
+                       push(@commit_pairs, \@commit_info_split);
+                       $parsed_sha1 = $commit_info_split[1];
+               }
+       } else {
+               # No remote mediawiki revision. Export the whole
+               # history (linearized with --first-parent)
+               print STDERR "Warning: no common ancestor, pushing complete history\n";
+               my $history = run_git("rev-list --first-parent --children $local");
+               my @history = split('\n', $history);
+               @history = @history[1..$#history];
+               foreach my $line (reverse @history) {
+                       my @commit_info_split = split(/ |\n/, $line);
+                       push(@commit_pairs, \@commit_info_split);
+               }
+       }
+
+       foreach my $commit_info_split (@commit_pairs) {
+               my $sha1_child = @{$commit_info_split}[0];
+               my $sha1_commit = @{$commit_info_split}[1];
+               my $diff_infos = run_git("diff-tree -r --raw -z $sha1_child $sha1_commit");
+               # TODO: we could detect rename, and encode them with a #redirect on the wiki.
+               # TODO: for now, it's just a delete+add
+               my @diff_info_list = split(/\0/, $diff_infos);
+               # Keep the first line of the commit message as mediawiki comment for the revision
+               my $commit_msg = (split(/\n/, run_git("show --pretty=format:\"%s\" $sha1_commit")))[0];
+               chomp($commit_msg);
+               # Push every blob
+               while (@diff_info_list) {
+                       my $status;
+                       # git diff-tree -z gives an output like
+                       # <metadata>\0<filename1>\0
+                       # <metadata>\0<filename2>\0
+                       # and we've split on \0.
+                       my $info = shift(@diff_info_list);
+                       my $file = shift(@diff_info_list);
+                       ($mw_revision, $status) = mw_push_file($info, $file, $commit_msg, $mw_revision);
+                       if ($status eq "non-fast-forward") {
+                               # we may already have sent part of the
+                               # commit to MediaWiki, but it's too
+                               # late to cancel it. Stop the push in
+                               # the middle, but still give an
+                               # accurate error message.
+                               return error_non_fast_forward($remote);
+                       }
+                       if ($status ne "ok") {
+                               die("Unknown error from mw_push_file()");
+                       }
+               }
+               unless ($dumb_push) {
+                       run_git("notes --ref=$remotename/mediawiki add -m \"mediawiki_revision: $mw_revision\" $sha1_commit");
+                       run_git("update-ref -m \"Git-MediaWiki push\" refs/mediawiki/$remotename/master $sha1_commit $sha1_child");
+               }
+       }
+
+       print STDOUT "ok $remote\n";
+       return 1;
+}
diff --git a/contrib/mw-to-git/git-remote-mediawiki.txt b/contrib/mw-to-git/git-remote-mediawiki.txt
new file mode 100644 (file)
index 0000000..4d211f5
--- /dev/null
@@ -0,0 +1,7 @@
+Git-Mediawiki is a project which aims the creation of a gate
+between git and mediawiki, allowing git users to push and pull
+objects from mediawiki just as one would do with a classic git
+repository thanks to remote-helpers.
+
+For more information, visit the wiki at
+https://github.com/Bibzball/Git-Mediawiki/wiki
index 3bb5a4dd57c669bc59be0e2317ef33b64b024992..86e9c29ec05139844ff46868e2b52dfbc274cc39 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -641,7 +641,7 @@ static int ident_to_worktree(const char *path, const char *src, size_t len,
        return 1;
 }
 
-static int git_path_check_crlf(const char *path, struct git_attr_check *check)
+static enum crlf_action git_path_check_crlf(const char *path, struct git_attr_check *check)
 {
        const char *value = check->value;
 
@@ -658,7 +658,7 @@ static int git_path_check_crlf(const char *path, struct git_attr_check *check)
        return CRLF_GUESS;
 }
 
-static int git_path_check_eol(const char *path, struct git_attr_check *check)
+static enum eol git_path_check_eol(const char *path, struct git_attr_check *check)
 {
        const char *value = check->value;
 
@@ -811,7 +811,7 @@ int renormalize_buffer(const char *path, const char *src, size_t len, struct str
                src = dst->buf;
                len = dst->len;
        }
-       return ret | convert_to_git(path, src, len, dst, 0);
+       return ret | convert_to_git(path, src, len, dst, SAFE_CRLF_FALSE);
 }
 
 /*****************************************************************
index 4c8346d5a1fe1325de2dd335a4e61a2dad994211..fa283003ea8809a2defc63f3b36cebe135028ddb 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -20,6 +20,7 @@
 static int log_syslog;
 static int verbose;
 static int reuseaddr;
+static int informative_errors;
 
 static const char daemon_usage[] =
 "git daemon [--verbose] [--syslog] [--export-all]\n"
@@ -108,11 +109,11 @@ static void NORETURN daemon_die(const char *err, va_list params)
        exit(1);
 }
 
-static char *path_ok(char *directory)
+static const char *path_ok(char *directory)
 {
        static char rpath[PATH_MAX];
        static char interp_path[PATH_MAX];
-       char *path;
+       const char *path;
        char *dir;
 
        dir = directory;
@@ -247,6 +248,14 @@ static int git_daemon_config(const char *var, const char *value, void *cb)
        return 0;
 }
 
+static int daemon_error(const char *dir, const char *msg)
+{
+       if (!informative_errors)
+               msg = "access denied or repository not exported";
+       packet_write(1, "ERR %s: %s", msg, dir);
+       return -1;
+}
+
 static int run_service(char *dir, struct daemon_service *service)
 {
        const char *path;
@@ -257,11 +266,11 @@ static int run_service(char *dir, struct daemon_service *service)
        if (!enabled && !service->overridable) {
                logerror("'%s': service not enabled.", service->name);
                errno = EACCES;
-               return -1;
+               return daemon_error(dir, "service not enabled");
        }
 
        if (!(path = path_ok(dir)))
-               return -1;
+               return daemon_error(dir, "no such repository");
 
        /*
         * Security on the cheap.
@@ -277,7 +286,7 @@ static int run_service(char *dir, struct daemon_service *service)
        if (!export_all_trees && access("git-daemon-export-ok", F_OK)) {
                logerror("'%s': repository not exported.", path);
                errno = EACCES;
-               return -1;
+               return daemon_error(dir, "repository not exported");
        }
 
        if (service->overridable) {
@@ -291,7 +300,7 @@ static int run_service(char *dir, struct daemon_service *service)
                logerror("'%s': service not enabled for '%s'",
                         service->name, path);
                errno = EACCES;
-               return -1;
+               return daemon_error(dir, "service not enabled");
        }
 
        /*
@@ -734,6 +743,29 @@ struct socketlist {
        size_t alloc;
 };
 
+static const char *ip2str(int family, struct sockaddr *sin, socklen_t len)
+{
+#ifdef NO_IPV6
+       static char ip[INET_ADDRSTRLEN];
+#else
+       static char ip[INET6_ADDRSTRLEN];
+#endif
+
+       switch (family) {
+#ifndef NO_IPV6
+       case AF_INET6:
+               inet_ntop(family, &((struct sockaddr_in6*)sin)->sin6_addr, ip, len);
+               break;
+#endif
+       case AF_INET:
+               inet_ntop(family, &((struct sockaddr_in*)sin)->sin_addr, ip, len);
+               break;
+       default:
+               strcpy(ip, "<unknown>");
+       }
+       return ip;
+}
+
 #ifndef NO_IPV6
 
 static int setup_named_sock(char *listen_addr, int listen_port, struct socketlist *socklist)
@@ -780,15 +812,22 @@ static int setup_named_sock(char *listen_addr, int listen_port, struct socketlis
 #endif
 
                if (set_reuse_addr(sockfd)) {
+                       logerror("Could not set SO_REUSEADDR: %s", strerror(errno));
                        close(sockfd);
                        continue;
                }
 
                if (bind(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) {
+                       logerror("Could not bind to %s: %s",
+                                ip2str(ai->ai_family, ai->ai_addr, ai->ai_addrlen),
+                                strerror(errno));
                        close(sockfd);
                        continue;       /* not fatal */
                }
                if (listen(sockfd, 5) < 0) {
+                       logerror("Could not listen to %s: %s",
+                                ip2str(ai->ai_family, ai->ai_addr, ai->ai_addrlen),
+                                strerror(errno));
                        close(sockfd);
                        continue;       /* not fatal */
                }
@@ -835,16 +874,23 @@ static int setup_named_sock(char *listen_addr, int listen_port, struct socketlis
                return 0;
 
        if (set_reuse_addr(sockfd)) {
+               logerror("Could not set SO_REUSEADDR: %s", strerror(errno));
                close(sockfd);
                return 0;
        }
 
        if ( bind(sockfd, (struct sockaddr *)&sin, sizeof sin) < 0 ) {
+               logerror("Could not listen to %s: %s",
+                        ip2str(AF_INET, (struct sockaddr *)&sin, sizeof(sin)),
+                        strerror(errno));
                close(sockfd);
                return 0;
        }
 
        if (listen(sockfd, 5) < 0) {
+               logerror("Could not listen to %s: %s",
+                        ip2str(AF_INET, (struct sockaddr *)&sin, sizeof(sin)),
+                        strerror(errno));
                close(sockfd);
                return 0;
        }
@@ -1167,6 +1213,14 @@ int main(int argc, char **argv)
                        make_service_overridable(arg + 18, 0);
                        continue;
                }
+               if (!prefixcmp(arg, "--informative-errors")) {
+                       informative_errors = 1;
+                       continue;
+               }
+               if (!prefixcmp(arg, "--no-informative-errors")) {
+                       informative_errors = 0;
+                       continue;
+               }
                if (!strcmp(arg, "--")) {
                        ok_paths = &argv[i+1];
                        break;
diff --git a/date.c b/date.c
index 896fbb48060c673b8dc2f2aa6ea3de3c1042fbd2..353e0a5e53f13c36549e65452931f254de3423c4 100644 (file)
--- a/date.c
+++ b/date.c
@@ -552,23 +552,35 @@ static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt
 static int match_tz(const char *date, int *offp)
 {
        char *end;
-       int offset = strtoul(date+1, &end, 10);
-       int min, hour;
-       int n = end - date - 1;
+       int hour = strtoul(date + 1, &end, 10);
+       int n = end - (date + 1);
+       int min = 0;
 
-       min = offset % 100;
-       hour = offset / 100;
+       if (n == 4) {
+               /* hhmm */
+               min = hour % 100;
+               hour = hour / 100;
+       } else if (n != 2) {
+               min = 99; /* random crap */
+       } else if (*end == ':') {
+               /* hh:mm? */
+               min = strtoul(end + 1, &end, 10);
+               if (end - (date + 1) != 5)
+                       min = 99; /* random crap */
+       } /* otherwise we parsed "hh" */
 
        /*
-        * Don't accept any random crap.. At least 3 digits, and
-        * a valid minute. We might want to check that the minutes
-        * are divisible by 30 or something too.
+        * Don't accept any random crap. Even though some places have
+        * offset larger than 12 hours (e.g. Pacific/Kiritimati is at
+        * UTC+14), there is something wrong if hour part is much
+        * larger than that. We might also want to check that the
+        * minutes are divisible by 15 or something too. (Offset of
+        * Kathmandu, Nepal is UTC+5:45)
         */
-       if (min < 60 && n > 2) {
-               offset = hour*60+min;
+       if (min < 60 && hour < 24) {
+               int offset = hour * 60 + min;
                if (*date == '-')
                        offset = -offset;
-
                *offp = offset;
        }
        return end - date;
index ebe751e72d0ca9db2f0a9d2576f3dc3f41ffc31d..62f4cd94cfbc4d3fe9e46c84f318af6624349a48 100644 (file)
@@ -289,7 +289,7 @@ static void show_new_file(struct rev_info *revs,
 
        /*
         * New file in the index: it might actually be different in
-        * the working copy.
+        * the working tree.
         */
        if (get_stat_data(new, &sha1, &mode, cached, match_missing,
            &dirty_submodule, &revs->diffopt) < 0)
diff --git a/diff.c b/diff.c
index fcc0078074c364d0a4c2bd75a6d390e517eb7f87..374ecf3b486e218c52f94ba14481a715c19b80db 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -2169,6 +2169,8 @@ static void builtin_diff(const char *name_a,
                xecfg.ctxlen = o->context;
                xecfg.interhunkctxlen = o->interhunkcontext;
                xecfg.flags = XDL_EMIT_FUNCNAMES;
+               if (DIFF_OPT_TST(o, FUNCCONTEXT))
+                       xecfg.flags |= XDL_EMIT_FUNCCONTEXT;
                if (pe)
                        xdiff_set_find_func(&xecfg, pe->pattern, pe->cflags);
                if (!diffopts)
@@ -2274,6 +2276,8 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
                memset(&xpp, 0, sizeof(xpp));
                memset(&xecfg, 0, sizeof(xecfg));
                xpp.flags = o->xdl_opts;
+               xecfg.ctxlen = o->context;
+               xecfg.interhunkctxlen = o->interhunkcontext;
                xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat,
                              &xpp, &xecfg);
        }
@@ -3385,6 +3389,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        }
 
        /* xdiff options */
+       else if (!strcmp(arg, "--minimal"))
+               DIFF_XDL_SET(options, NEED_MINIMAL);
+       else if (!strcmp(arg, "--no-minimal"))
+               DIFF_XDL_CLR(options, NEED_MINIMAL);
        else if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space"))
                DIFF_XDL_SET(options, IGNORE_WHITESPACE);
        else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change"))
@@ -3530,6 +3538,12 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        else if (opt_arg(arg, '\0', "inter-hunk-context",
                         &options->interhunkcontext))
                ;
+       else if (!strcmp(arg, "-W"))
+               DIFF_OPT_SET(options, FUNCCONTEXT);
+       else if (!strcmp(arg, "--function-context"))
+               DIFF_OPT_SET(options, FUNCCONTEXT);
+       else if (!strcmp(arg, "--no-function-context"))
+               DIFF_OPT_CLR(options, FUNCCONTEXT);
        else if ((argcount = parse_long_opt("output", av, &optarg))) {
                options->file = fopen(optarg, "w");
                if (!options->file)
diff --git a/diff.h b/diff.h
index 8c66b59517305546d3e2f66fca652284468e1da1..0c51724493f76461b67d6ef8f5556852819ad809 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -79,6 +79,7 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data)
 #define DIFF_OPT_IGNORE_DIRTY_SUBMODULES (1 << 26)
 #define DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG (1 << 27)
 #define DIFF_OPT_DIRSTAT_BY_LINE     (1 << 28)
+#define DIFF_OPT_FUNCCONTEXT         (1 << 29)
 
 #define DIFF_OPT_TST(opts, flag)    ((opts)->flags & DIFF_OPT_##flag)
 #define DIFF_OPT_SET(opts, flag)    ((opts)->flags |= DIFF_OPT_##flag)
index c3760cfefd5dd123481b869eac3081b6e7d93201..380a837b5b136ae88d374ae8a7326c87597107fc 100644 (file)
@@ -8,6 +8,46 @@
 #include "xdiff-interface.h"
 #include "kwset.h"
 
+typedef int (*pickaxe_fn)(struct diff_filepair *p, struct diff_options *o, regex_t *regexp, kwset_t kws);
+
+static void pickaxe(struct diff_queue_struct *q, struct diff_options *o,
+                   regex_t *regexp, kwset_t kws, pickaxe_fn fn)
+{
+       int i;
+       struct diff_queue_struct outq;
+
+       DIFF_QUEUE_CLEAR(&outq);
+
+       if (o->pickaxe_opts & DIFF_PICKAXE_ALL) {
+               /* Showing the whole changeset if needle exists */
+               for (i = 0; i < q->nr; i++) {
+                       struct diff_filepair *p = q->queue[i];
+                       if (fn(p, o, regexp, kws))
+                               return; /* do not munge the queue */
+               }
+
+               /*
+                * Otherwise we will clear the whole queue by copying
+                * the empty outq at the end of this function, but
+                * first clear the current entries in the queue.
+                */
+               for (i = 0; i < q->nr; i++)
+                       diff_free_filepair(q->queue[i]);
+       } else {
+               /* Showing only the filepairs that has the needle */
+               for (i = 0; i < q->nr; i++) {
+                       struct diff_filepair *p = q->queue[i];
+                       if (fn(p, o, regexp, kws))
+                               diff_q(&outq, p);
+                       else
+                               diff_free_filepair(p);
+               }
+       }
+
+       free(q->queue);
+       *q = outq;
+}
+
 struct diffgrep_cb {
        regex_t *regexp;
        int hit;
@@ -45,7 +85,8 @@ static void fill_one(struct diff_filespec *one,
        }
 }
 
-static int diff_grep(struct diff_filepair *p, regex_t *regexp, struct diff_options *o)
+static int diff_grep(struct diff_filepair *p, struct diff_options *o,
+                    regex_t *regexp, kwset_t kws)
 {
        regmatch_t regmatch;
        struct userdiff_driver *textconv_one = NULL;
@@ -95,12 +136,8 @@ static int diff_grep(struct diff_filepair *p, regex_t *regexp, struct diff_optio
 
 static void diffcore_pickaxe_grep(struct diff_options *o)
 {
-       struct diff_queue_struct *q = &diff_queued_diff;
-       int i, has_changes, err;
+       int err;
        regex_t regex;
-       struct diff_queue_struct outq;
-       outq.queue = NULL;
-       outq.nr = outq.alloc = 0;
 
        err = regcomp(&regex, o->pickaxe, REG_EXTENDED | REG_NEWLINE);
        if (err) {
@@ -110,51 +147,21 @@ static void diffcore_pickaxe_grep(struct diff_options *o)
                die("invalid log-grep regex: %s", errbuf);
        }
 
-       if (o->pickaxe_opts & DIFF_PICKAXE_ALL) {
-               /* Showing the whole changeset if needle exists */
-               for (i = has_changes = 0; !has_changes && i < q->nr; i++) {
-                       struct diff_filepair *p = q->queue[i];
-                       if (diff_grep(p, &regex, o))
-                               has_changes++;
-               }
-               if (has_changes)
-                       return; /* do not munge the queue */
-
-               /*
-                * Otherwise we will clear the whole queue by copying
-                * the empty outq at the end of this function, but
-                * first clear the current entries in the queue.
-                */
-               for (i = 0; i < q->nr; i++)
-                       diff_free_filepair(q->queue[i]);
-       } else {
-               /* Showing only the filepairs that has the needle */
-               for (i = 0; i < q->nr; i++) {
-                       struct diff_filepair *p = q->queue[i];
-                       if (diff_grep(p, &regex, o))
-                               diff_q(&outq, p);
-                       else
-                               diff_free_filepair(p);
-               }
-       }
+       pickaxe(&diff_queued_diff, o, &regex, NULL, diff_grep);
 
        regfree(&regex);
-
-       free(q->queue);
-       *q = outq;
        return;
 }
 
-static unsigned int contains(struct diff_filespec *one,
-                            const char *needle, unsigned long len,
+static unsigned int contains(struct diff_filespec *one, struct diff_options *o,
                             regex_t *regexp, kwset_t kws)
 {
        unsigned int cnt;
        unsigned long sz;
        const char *data;
-       if (diff_populate_filespec(one, 0))
+       if (!o->pickaxe[0])
                return 0;
-       if (!len)
+       if (diff_populate_filespec(one, 0))
                return 0;
 
        sz = one->size;
@@ -176,14 +183,15 @@ static unsigned int contains(struct diff_filespec *one,
 
        } else { /* Classic exact string match */
                while (sz) {
-                       size_t offset = kwsexec(kws, data, sz, NULL);
+                       struct kwsmatch kwsm;
+                       size_t offset = kwsexec(kws, data, sz, &kwsm);
                        const char *found;
                        if (offset == -1)
                                break;
                        else
                                found = data + offset;
-                       sz -= found - data + len;
-                       data = found + len;
+                       sz -= found - data + kwsm.size[0];
+                       data = found + kwsm.size[0];
                        cnt++;
                }
        }
@@ -191,17 +199,31 @@ static unsigned int contains(struct diff_filespec *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 */
+               /* 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);
+       }
+       return 0;
+}
+
 static void diffcore_pickaxe_count(struct diff_options *o)
 {
        const char *needle = o->pickaxe;
        int opts = o->pickaxe_opts;
-       struct diff_queue_struct *q = &diff_queued_diff;
        unsigned long len = strlen(needle);
-       int i, has_changes;
        regex_t regex, *regexp = NULL;
        kwset_t kws = NULL;
-       struct diff_queue_struct outq;
-       DIFF_QUEUE_CLEAR(&outq);
 
        if (opts & DIFF_PICKAXE_REGEX) {
                int err;
@@ -220,72 +242,12 @@ static void diffcore_pickaxe_count(struct diff_options *o)
                kwsprep(kws);
        }
 
-       if (opts & DIFF_PICKAXE_ALL) {
-               /* Showing the whole changeset if needle exists */
-               for (i = has_changes = 0; !has_changes && i < q->nr; i++) {
-                       struct diff_filepair *p = q->queue[i];
-                       if (!DIFF_FILE_VALID(p->one)) {
-                               if (!DIFF_FILE_VALID(p->two))
-                                       continue; /* ignore unmerged */
-                               /* created */
-                               if (contains(p->two, needle, len, regexp, kws))
-                                       has_changes++;
-                       }
-                       else if (!DIFF_FILE_VALID(p->two)) {
-                               if (contains(p->one, needle, len, regexp, kws))
-                                       has_changes++;
-                       }
-                       else if (!diff_unmodified_pair(p) &&
-                                contains(p->one, needle, len, regexp, kws) !=
-                                contains(p->two, needle, len, regexp, kws))
-                               has_changes++;
-               }
-               if (has_changes)
-                       return; /* not munge the queue */
-
-               /* otherwise we will clear the whole queue
-                * by copying the empty outq at the end of this
-                * function, but first clear the current entries
-                * in the queue.
-                */
-               for (i = 0; i < q->nr; i++)
-                       diff_free_filepair(q->queue[i]);
-       }
-       else
-               /* Showing only the filepairs that has the needle */
-               for (i = 0; i < q->nr; i++) {
-                       struct diff_filepair *p = q->queue[i];
-                       has_changes = 0;
-                       if (!DIFF_FILE_VALID(p->one)) {
-                               if (!DIFF_FILE_VALID(p->two))
-                                       ; /* ignore unmerged */
-                               /* created */
-                               else if (contains(p->two, needle, len, regexp,
-                                                 kws))
-                                       has_changes = 1;
-                       }
-                       else if (!DIFF_FILE_VALID(p->two)) {
-                               if (contains(p->one, needle, len, regexp, kws))
-                                       has_changes = 1;
-                       }
-                       else if (!diff_unmodified_pair(p) &&
-                                contains(p->one, needle, len, regexp, kws) !=
-                                contains(p->two, needle, len, regexp, kws))
-                               has_changes = 1;
-
-                       if (has_changes)
-                               diff_q(&outq, p);
-                       else
-                               diff_free_filepair(p);
-               }
+       pickaxe(&diff_queued_diff, o, regexp, kws, has_changes);
 
        if (opts & DIFF_PICKAXE_REGEX)
                regfree(&regex);
        else
                kwsfree(kws);
-
-       free(q->queue);
-       *q = outq;
        return;
 }
 
diff --git a/dir.c b/dir.c
index 08281d2ef74ea7790913e71f08d00299a1825765..0a78d00b545ac4f302ea89b6393773669907599e 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -34,49 +34,54 @@ int fnmatch_icase(const char *pattern, const char *string, int flags)
        return fnmatch(pattern, string, flags | (ignore_case ? FNM_CASEFOLD : 0));
 }
 
-static int common_prefix(const char **pathspec)
+static size_t common_prefix_len(const char **pathspec)
 {
-       const char *path, *slash, *next;
-       int prefix;
+       const char *n, *first;
+       size_t max = 0;
 
        if (!pathspec)
-               return 0;
+               return max;
+
+       first = *pathspec;
+       while ((n = *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))
+                               break;
+                       if (c == '/')
+                               len = i + 1;
+               }
+               if (first == n || len < max) {
+                       max = len;
+                       if (!max)
+                               break;
+               }
+       }
+       return max;
+}
 
-       path = *pathspec;
-       slash = strrchr(path, '/');
-       if (!slash)
-               return 0;
+/*
+ * Returns a copy of the longest leading path common among all
+ * pathspecs.
+ */
+char *common_prefix(const char **pathspec)
+{
+       unsigned long len = common_prefix_len(pathspec);
 
-       /*
-        * The first 'prefix' characters of 'path' are common leading
-        * path components among the pathspecs we have seen so far,
-        * including the trailing slash.
-        */
-       prefix = slash - path + 1;
-       while ((next = *++pathspec) != NULL) {
-               int len, last_matching_slash = -1;
-               for (len = 0; len < prefix && next[len] == path[len]; len++)
-                       if (next[len] == '/')
-                               last_matching_slash = len;
-               if (len == prefix)
-                       continue;
-               if (last_matching_slash < 0)
-                       return 0;
-               prefix = last_matching_slash + 1;
-       }
-       return prefix;
+       return len ? xmemdupz(*pathspec, len) : NULL;
 }
 
 int fill_directory(struct dir_struct *dir, const char **pathspec)
 {
        const char *path;
-       int len;
+       size_t len;
 
        /*
         * Calculate common prefix for the pathspec, and
         * use that to optimize the directory walk
         */
-       len = common_prefix(pathspec);
+       len = common_prefix_len(pathspec);
        path = "";
 
        if (len)
@@ -84,6 +89,8 @@ int fill_directory(struct dir_struct *dir, const char **pathspec)
 
        /* Read the directory and prune it */
        read_directory(dir, path, len, pathspec);
+       if (*path)
+               free((char *)path);
        return len;
 }
 
@@ -961,34 +968,34 @@ static int read_directory_recursive(struct dir_struct *dir,
 {
        DIR *fdir = opendir(*base ? base : ".");
        int contents = 0;
+       struct dirent *de;
+       char path[PATH_MAX + 1];
 
-       if (fdir) {
-               struct dirent *de;
-               char path[PATH_MAX + 1];
-               memcpy(path, base, baselen);
-
-               while ((de = readdir(fdir)) != NULL) {
-                       int len;
-                       switch (treat_path(dir, de, path, sizeof(path),
-                                          baselen, simplify, &len)) {
-                       case path_recurse:
-                               contents += read_directory_recursive
-                                       (dir, path, len, 0, simplify);
-                               continue;
-                       case path_ignored:
-                               continue;
-                       case path_handled:
-                               break;
-                       }
-                       contents++;
-                       if (check_only)
-                               goto exit_early;
-                       else
-                               dir_add_name(dir, path, len);
+       if (!fdir)
+               return 0;
+
+       memcpy(path, base, baselen);
+
+       while ((de = readdir(fdir)) != NULL) {
+               int len;
+               switch (treat_path(dir, de, path, sizeof(path),
+                                  baselen, simplify, &len)) {
+               case path_recurse:
+                       contents += read_directory_recursive(dir, path, len, 0, simplify);
+                       continue;
+               case path_ignored:
+                       continue;
+               case path_handled:
+                       break;
                }
-exit_early:
-               closedir(fdir);
+               contents++;
+               if (check_only)
+                       goto exit_early;
+               else
+                       dir_add_name(dir, path, len);
        }
+exit_early:
+       closedir(fdir);
 
        return contents;
 }
diff --git a/dir.h b/dir.h
index 433b5b4cd4c51e9b7557d3056570ed46b7ceba92..dd6947e1d46098732ff1d8c3f23087c1e7163fe9 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -64,6 +64,7 @@ struct dir_struct {
 #define MATCHED_RECURSIVELY 1
 #define MATCHED_FNMATCH 2
 #define MATCHED_EXACTLY 3
+extern char *common_prefix(const char **pathspec);
 extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen);
 extern int match_pathspec_depth(const struct pathspec *pathspec,
                                const char *name, int namelen,
index e96edcfebc4174a5166c11e7a511ea792e7a2639..0bee6a7a88299f8c89eedeee25cece1d3cdafef0 100644 (file)
@@ -29,6 +29,7 @@ const char *git_log_output_encoding;
 int shared_repository = PERM_UMASK;
 const char *apply_default_whitespace;
 const char *apply_default_ignorewhitespace;
+const char *git_attributes_file;
 int zlib_compression_level = Z_BEST_SPEED;
 int core_compression_level;
 int core_compression_seen;
@@ -106,7 +107,7 @@ static char *expand_namespace(const char *raw_namespace)
                if (strcmp((*c)->buf, "/") != 0)
                        strbuf_addf(&buf, "refs/namespaces/%s", (*c)->buf);
        strbuf_list_free(components);
-       if (check_ref_format(buf.buf) != CHECK_REF_FORMAT_OK)
+       if (check_refname_format(buf.buf, 0))
                die("bad git namespace path \"%s\"", raw_namespace);
        strbuf_addch(&buf, '/');
        return strbuf_detach(&buf, NULL);
index 742e7da6b8b58dd0803d89c6ad6c59589274a31b..8d8ea3c45c0be5481c7b452c27ee0d163d69fb00 100644 (file)
@@ -722,13 +722,8 @@ static struct branch *new_branch(const char *name)
 
        if (b)
                die("Invalid attempt to create duplicate branch: %s", name);
-       switch (check_ref_format(name)) {
-       case 0: break; /* its valid */
-       case CHECK_REF_FORMAT_ONELEVEL:
-               break; /* valid, but too few '/', allow anyway */
-       default:
+       if (check_refname_format(name, REFNAME_ALLOW_ONELEVEL))
                die("Branch name doesn't conform to GIT standards: %s", name);
-       }
 
        b = pool_calloc(1, sizeof(struct branch));
        b->name = pool_strdup(name);
@@ -2416,6 +2411,8 @@ static void note_change_n(struct branch *b, unsigned char old_fanout)
        /* <committish> */
        s = lookup_branch(p);
        if (s) {
+               if (is_null_sha1(s->sha1))
+                       die("Can't add a note on empty branch.");
                hashcpy(commit_sha1, s->sha1);
        } else if (*p == ':') {
                uintmax_t commit_mark = strtoumax(p + 1, NULL, 10);
@@ -2717,6 +2714,8 @@ static void parse_new_tag(void)
        from = strchr(command_buf.buf, ' ') + 1;
        s = lookup_branch(from);
        if (s) {
+               if (is_null_sha1(s->sha1))
+                       die("Can't tag an empty branch.");
                hashcpy(sha1, s->sha1);
                type = OBJ_COMMIT;
        } else if (*from == ':') {
index 2524060475ef369c0b9a4641aa88dc2ad6083da4..99efbe884528ffa730d6c0297e20d9de93d5ad9e 100755 (executable)
@@ -126,7 +126,8 @@ bisect_start() {
                start_head=$(cat "$GIT_DIR/BISECT_START")
                if test "z$mode" != "z--no-checkout"
                then
-                       git checkout "$start_head" --
+                       git checkout "$start_head" -- ||
+                       die "$(eval_gettext "Checking out '\$start_head' failed. Try 'git bisect reset <validbranch>'.")"
                fi
        else
                # Get rev from where we start.
index 5ef8ff76f6fd262c3109d25d706ebd3db35c73fa..77062ed2a66e1efda60ead8ce99abedc8d04ce71 100644 (file)
 #else
 #include <poll.h>
 #endif
-#ifndef __MINGW32__
+#if defined(__MINGW32__)
+/* pull in Windows compatibility stuff */
+#include "compat/mingw.h"
+#elif defined(_MSC_VER)
+#include "compat/msvc.h"
+#else
 #include <sys/wait.h>
 #include <sys/resource.h>
 #include <sys/socket.h>
 #include <grp.h>
 #define _ALL_SOURCE 1
 #endif
-#else  /* __MINGW32__ */
-/* pull in Windows compatibility stuff */
-#include "compat/mingw.h"
-#endif /* __MINGW32__ */
-#ifdef _MSC_VER
-#include "compat/msvc.h"
 #endif
 
 #ifndef NO_LIBGEN_H
@@ -219,7 +218,7 @@ extern char *gitbasename(char *);
 #define find_last_dir_sep(path) strrchr(path, '/')
 #endif
 
-#if __HP_cc >= 61000
+#if defined(__HP_cc) && (__HP_cc >= 61000)
 #define NORETURN __attribute__((noreturn))
 #define NORETURN_PTR
 #elif defined(__GNUC__) && !defined(NO_NORETURN)
@@ -351,6 +350,8 @@ extern size_t gitstrlcpy(char *, const char *, size_t);
 #ifdef NO_STRTOUMAX
 #define strtoumax gitstrtoumax
 extern uintmax_t gitstrtoumax(const char *, char **, int);
+#define strtoimax gitstrtoimax
+extern intmax_t gitstrtoimax(const char *, char **, int);
 #endif
 
 #ifdef NO_STRTOK_R
index 1b8bff2cac163a6588df397168f57214c30b4784..b8eddabc9477ea3ecc6311d88443109041a55c3c 100755 (executable)
@@ -109,14 +109,14 @@ my $usage =
     "    --strict-paths      : Don't allow recursing into subdirectories\n".
     "    --export-all        : Don't check for gitcvs.enabled in config\n".
     "    --version, -V       : Print version information and exit\n".
-    "    --help, -h, -H      : Print usage information and exit\n".
+    "    -h, -H              : Print usage information and exit\n".
     "\n".
     "<directory> ... is a list of allowed directories. If no directories\n".
     "are given, all are allowed. This is an additional restriction, gitcvs\n".
     "access still needs to be enabled by the gitcvs.enabled config option.\n".
     "Alternately, one directory may be specified in GIT_CVSSERVER_ROOT.\n";
 
-my @opts = ( 'help|h|H', 'version|V',
+my @opts = ( 'h|H', 'version|V',
             'base-path=s', 'strict-paths', 'export-all' );
 GetOptions( $state, @opts )
     or die $usage;
index 8452890be974d30942f0acaa3f19282c1b8b25b2..e6558d101062929241cc30cd0272556c3164310b 100755 (executable)
@@ -43,12 +43,15 @@ launch_merge_tool () {
                printf "\nViewing: '$MERGED'\n"
                if use_ext_cmd
                then
-                       printf "Hit return to launch '%s': " \
+                       printf "Launch '%s' [Y/n]: " \
                                "$GIT_DIFFTOOL_EXTCMD"
                else
-                       printf "Hit return to launch '%s': " "$merge_tool"
+                       printf "Launch '%s' [Y/n]: " "$merge_tool"
+               fi
+               if read ans && test "$ans" = n
+               then
+                       return
                fi
-               read ans
        fi
 
        if use_ext_cmd
index ced1615e216018bc20303cd91eaea05c69cb6b11..09b65f1770c09824df5543d208d1df0e9a60d831 100755 (executable)
@@ -97,7 +97,7 @@ sub generate_command
                        $prompt = 'yes';
                        next;
                }
-               if ($arg eq '-h' || $arg eq '--help') {
+               if ($arg eq '-h') {
                        usage();
                }
                push @command, $arg;
index fd6a43d0a29986d38094df2818b66beba0f49234..f8971603f77a495b253b5470f5546d51dd6efd14 100755 (executable)
@@ -854,6 +854,7 @@ set default_config(gui.fontdiff) [font configure font_diff]
 # TODO: this option should be added to the git-config documentation
 set default_config(gui.maxfilesdisplayed) 5000
 set default_config(gui.usettk) 1
+set default_config(gui.warndetachedcommit) 1
 set font_descs {
        {fontui   font_ui   {mc "Main Font"}}
        {fontdiff font_diff {mc "Diff/Console Font"}}
@@ -1526,7 +1527,7 @@ proc run_prepare_commit_msg_hook {} {
 
        # prepare-commit-msg requires PREPARE_COMMIT_MSG exist.  From git-gui
        # it will be .git/MERGE_MSG (merge), .git/SQUASH_MSG (squash), or an
-       # empty file but existant file.
+       # empty file but existent file.
 
        set fd_pcm [open [gitdir PREPARE_COMMIT_MSG] a]
 
index 61e358f960ca949cac5664c67442b82d0afca65f..691941e95948e7d332d6984aa6e2cc0956147550 100644 (file)
@@ -22,6 +22,7 @@ field w_asim     ; # text column: annotations (simple computation)
 field w_file     ; # text column: actual file data
 field w_cviewer  ; # pane showing commit message
 field finder     ; # find mini-dialog frame
+field gotoline   ; # line goto mini-dialog frame
 field status     ; # status mega-widget instance
 field old_height ; # last known height of $w.file_pane
 
@@ -231,6 +232,11 @@ constructor new {i_commit i_path i_jump} {
                -column [expr {[llength $w_columns] - 1}] \
                ]
 
+       set gotoline [::linebar::new \
+               $w.file_pane.out.lf $w_file \
+               -column [expr {[llength $w_columns] - 1}] \
+               ]
+
        set w_cviewer $w.file_pane.cm.t
        text $w_cviewer \
                -background white \
@@ -274,7 +280,11 @@ constructor new {i_commit i_path i_jump} {
        $w.ctxm add command \
                -label [mc "Find Text..."] \
                -accelerator F7 \
-               -command [list searchbar::show $finder]
+               -command [cb _show_finder]
+       $w.ctxm add command \
+               -label [mc "Goto Line..."] \
+               -accelerator "Ctrl-G" \
+               -command [cb _show_linebar]
        menu $w.ctxm.enc
        build_encoding_menu $w.ctxm.enc [cb _setencoding]
        $w.ctxm add cascade \
@@ -341,10 +351,13 @@ constructor new {i_commit i_path i_jump} {
        bind $w_cviewer <Tab>       "[list focus $w_file];break"
        bind $w_cviewer <Button-1>   [list focus $w_cviewer]
        bind $w_file    <Visibility> [cb _focus_search $w_file]
-       bind $top       <F7>         [list searchbar::show $finder]
+       bind $top       <F7>         [cb _show_finder]
+       bind $top       <Key-slash>  [cb _show_finder]
+       bind $top    <Control-Key-s> [cb _show_finder]
        bind $top       <Escape>     [list searchbar::hide $finder]
        bind $top       <F3>         [list searchbar::find_next $finder]
        bind $top       <Shift-F3>   [list searchbar::find_prev $finder]
+       bind $top    <Control-Key-g> [cb _show_linebar]
        catch { bind $top <Shift-Key-XF86_Switch_VT_3> [list searchbar::find_prev $finder] }
 
        grid configure $w.header -sticky ew
@@ -1298,9 +1311,9 @@ method _position_tooltip {} {
        set pos_y [expr {[winfo pointery .] + 10}]
 
        set g "${req_w}x${req_h}"
-       if {$pos_x >= 0} {append g +}
+       if {[tk windowingsystem] eq "win32" || $pos_x >= 0} {append g +}
        append g $pos_x
-       if {$pos_y >= 0} {append g +}
+       if {[tk windowingsystem] eq "win32" || $pos_y >= 0} {append g +}
        append g $pos_y
 
        wm geometry $tooltip_wm $g
@@ -1336,4 +1349,14 @@ method _resize {new_height} {
        set old_height $new_height
 }
 
+method _show_finder {} {
+       linebar::hide $gotoline
+       searchbar::show $finder
+}
+
+method _show_linebar {} {
+       searchbar::hide $finder
+       linebar::show $gotoline
+}
+
 }
index c12d5e1698a1fcdcfa39bb69b475633b2f6c34c9..54c7957a66aa5784d47708edc05a1c95178ed7a2 100644 (file)
@@ -610,9 +610,9 @@ method _position_tooltip {} {
        set pos_y [expr {[winfo pointery .] + 10}]
 
        set g "${req_w}x${req_h}"
-       if {$pos_x >= 0} {append g +}
+       if {[tk windowingsystem] eq "win32" || $pos_x >= 0} {append g +}
        append g $pos_x
-       if {$pos_y >= 0} {append g +}
+       if {[tk windowingsystem] eq "win32" || $pos_y >= 0} {append g +}
        append g $pos_y
 
        wm geometry $tooltip_wm $g
index 5ce46877bfb24701187f5ff5e94ce4aaf8b666b2..372bed9948390483d66036231fce2fe8964d7bb6 100644 (file)
@@ -260,8 +260,23 @@ proc commit_prehook_wait {fd_ph curHEAD msg_p} {
 }
 
 proc commit_commitmsg {curHEAD msg_p} {
+       global is_detached repo_config
        global pch_error
 
+       if {$is_detached && $repo_config(gui.warndetachedcommit)} {
+               set msg [mc "You are about to commit on a detached head.\
+This is a potentially dangerous thing to do because if you switch\
+to another branch you will loose your changes and it can be difficult\
+to retrieve them later from the reflog. You should probably cancel this\
+commit and create a new branch to continue.\n\
+\n\
+Do you really want to proceed with your Commit?"]
+               if {[ask_popup $msg] ne yes} {
+                       unlock_index
+                       return
+               }
+       }
+
        # -- Run the commit-msg hook.
        #
        set fd_ph [githook_read commit-msg $msg_p]
index 5d7bbf23eddeb2ff146487b28a79feee1d296289..e38b647b71ea335d6771121cfd079de919ced55b 100644 (file)
@@ -356,12 +356,21 @@ proc do_add_all {} {
        global file_states
 
        set paths [list]
+       set unknown_paths [list]
        foreach path [array names file_states] {
                switch -glob -- [lindex $file_states($path) 0] {
                U? {continue}
                ?M -
                ?T -
                ?D {lappend paths $path}
+               ?O {lappend unknown_paths $path}
+               }
+       }
+       if {[llength $unknown_paths]} {
+               set reply [ask_popup [mc "There are unknown files do you also want
+to stage those?"]]
+               if {$reply} {
+                       set paths [concat $paths $unknown_paths]
                }
        }
        add_helper {Adding all changed files} $paths
diff --git a/git-gui/lib/line.tcl b/git-gui/lib/line.tcl
new file mode 100644 (file)
index 0000000..c160012
--- /dev/null
@@ -0,0 +1,81 @@
+# goto line number
+# based on code from gitk, Copyright (C) Paul Mackerras
+
+class linebar {
+
+field w
+field ctext
+
+field linenum   {}
+
+constructor new {i_w i_text args} {
+       global use_ttk NS
+       set w      $i_w
+       set ctext  $i_text
+
+       ${NS}::frame  $w
+       ${NS}::label  $w.l       -text [mc "Goto Line:"]
+       entry  $w.ent \
+               -textvariable ${__this}::linenum \
+               -background lightgreen \
+               -validate key \
+               -validatecommand [cb _validate %P]
+       ${NS}::button $w.bn      -text [mc Go] -command [cb _goto]
+
+       pack   $w.l   -side left
+       pack   $w.bn  -side right
+       pack   $w.ent -side left -expand 1 -fill x
+
+       eval grid conf $w -sticky we $args
+       grid remove $w
+
+       trace add variable linenum write [cb _goto_cb]
+       bind $w.ent <Return> [cb _goto]
+       bind $w.ent <Escape> [cb hide]
+
+       bind $w <Destroy> [list delete_this $this]
+       return $this
+}
+
+method show {} {
+       if {![visible $this]} {
+               grid $w
+       }
+       focus -force $w.ent
+}
+
+method hide {} {
+       if {[visible $this]} {
+               $w.ent delete 0 end
+               focus $ctext
+               grid remove $w
+       }
+}
+
+method visible {} {
+       return [winfo ismapped $w]
+}
+
+method editor {} {
+       return $w.ent
+}
+
+method _validate {P} {
+       # only accept numbers as input
+       string is integer $P
+}
+
+method _goto_cb {name ix op} {
+       after idle [cb _goto 1]
+}
+
+method _goto {{nohide {0}}} {
+       if {$linenum ne {}} {
+               $ctext see $linenum.0
+               if {!$nohide} {
+                       hide $this
+               }
+       }
+}
+
+}
index 7fdbf87bcdcf3e6d5a928dd4ac8a323509ea7953..ef3486f083c74f7f5e5fe9af861818ed0d64e89c 100644 (file)
@@ -35,6 +35,8 @@ constructor new {i_w i_text args} {
        grid remove $w
 
        trace add variable searchstring write [cb _incrsearch_cb]
+       bind $w.ent <Return> [cb find_next]
+       bind $w.ent <Shift-Return> [cb find_prev]
        
        bind $w <Destroy> [list delete_this $this]
        return $this
@@ -196,4 +198,4 @@ method scrolled {} {
        }
 }
 
-}
\ No newline at end of file
+}
index 595bbf5dee97e34eeab46b742225fce2f95ab052..0f5837d48e7040e9d2b07513666b608353736645 100644 (file)
@@ -18,28 +18,23 @@ specialized so-called "po file editors" (e.g. emacs po-mode, KBabel,
 poedit, GTranslator --- any of them would work well).  Please install
 them.
 
-You would then need to clone the git-gui internationalization project
-repository, so that you can work on it:
+You would then need to clone the git-gui project repository and create
+a feature branch to begin working:
 
-       $ git clone mob@repo.or.cz:/srv/git/git-gui/git-gui-i18n.git/
-       $ cd git-gui-i18n
-       $ git checkout --track -b mob origin/mob
-       $ git config remote.origin.push mob
+       $ git clone git://repo.or.cz/git-gui.git
+       $ cd git-gui.git
+       $ git checkout -b my-translation
 
-The "git checkout" command creates a 'mob' branch from upstream's
-corresponding branch and makes it your current branch.  You will be
-working on this branch.
-
-The "git config" command records in your repository configuration file
-that you would push "mob" branch to the upstream when you say "git
-push".
+The "git checkout" command creates a new branch to keep your work
+isolated and to make it simple to post your patch series when
+completed.  You will be working on this branch.
 
 
 2. Starting a new language.
 
-In the git-gui-i18n directory is a po/ subdirectory.  It has a
-handful files whose names end with ".po".  Is there a file that has
-messages in your language?
+In the git-gui directory is a po/ subdirectory.  It has a handful of
+files whose names end with ".po".  Is there a file that has messages
+in your language?
 
 If you do not know what your language should be named, you need to find
 it.  This currently follows ISO 639-1 two letter codes:
@@ -149,15 +144,18 @@ There is a trick to test your translation without first installing:
        $ make
        $ LANG=af ./git-gui.sh
 
-When you are satisfied with your translation, commit your changes, and
-push it back to the 'mob' branch:
+When you are satisfied with your translation, commit your changes then submit
+your patch series to the maintainer and the Git mailing list:
 
        $ edit po/af.po
        ... be sure to update Last-Translator: and
        ... PO-Revision-Date: lines.
        $ git add po/af.po
-       $ git commit -m 'Started Afrikaans translation.'
-       $ git push
+       $ git commit -s -m 'git-gui: added Afrikaans translation.'
+       $ git send-email --to 'git@vger.kernel.org' \
+          --cc 'Pat Thoyts <patthoyts@users.sourceforge.net>' \
+          --subject 'git-gui: Afrikaans translation' \
+          master..
 
 
 3. Updating your translation.
@@ -169,6 +167,7 @@ itself was updated and there are new messages that need translation.
 
 In any case, make sure you are up-to-date before starting your work:
 
+       $ git checkout master
        $ git pull
 
 In the former case, you will edit po/af.po (again, replace "af" with
index 8bd3c5d75feea3dfe310adbfab705d8045aa8367..24cc4e3675e05e9bd3bf7ea17335a67a3aa5166b 100644 (file)
@@ -1714,7 +1714,7 @@ msgstr ""
 
 #: lib/index.tcl:30
 msgid "Continue"
-msgstr "Forstätt"
+msgstr "Fortsätt"
 
 #: lib/index.tcl:33
 msgid "Unlock Index"
index 7aeb96952f668baef2fd18383806e13a20415f19..f612cb847aca981e16cb6006fa08d870497c6263 100755 (executable)
@@ -117,7 +117,7 @@ case "${1:-.}${2:-.}${3:-.}" in
 
                # If we do not have enough common material, it is not
                # worth trying two-file merge using common subsections.
-               expr "$sz0" \< "$sz1" \* 2 >/dev/null || : >$orig
+               expr $sz0 \< $sz1 \* 2 >/dev/null || : >$orig
                ;;
        *)
                echo "Auto-merging $4"
index b6d463f0d057361ab5909209d24fc8fd383a3dbb..085e213a126e5864befc8e3436832ae3c587a624 100755 (executable)
@@ -342,64 +342,42 @@ merge_keep_temporaries="$(git config --bool mergetool.keepTemporaries || echo fa
 
 last_status=0
 rollup_status=0
-rerere=false
-
-files_to_merge() {
-    if test "$rerere" = true
-    then
-       git rerere remaining
-    else
-       git ls-files -u | sed -e 's/^[^ ]*      //' | sort -u
-    fi
-}
-
+files=
 
 if test $# -eq 0 ; then
     cd_to_toplevel
 
     if test -e "$GIT_DIR/MERGE_RR"
     then
-       rerere=true
-    fi
-
-    files=$(files_to_merge)
-    if test -z "$files" ; then
-       echo "No files need merging"
-       exit 0
+       files=$(git rerere remaining)
+    else
+       files=$(git ls-files -u | sed -e 's/^[^ ]*      //' | sort -u)
     fi
+else
+    files=$(git ls-files -u -- "$@" | sed -e 's/^[^    ]*      //' | sort -u)
+fi
 
-    # Save original stdin
-    exec 3<&0
+if test -z "$files" ; then
+    echo "No files need merging"
+    exit 0
+fi
 
-    printf "Merging:\n"
-    printf "$files\n"
+printf "Merging:\n"
+printf "$files\n"
 
-    files_to_merge |
-    while IFS= read i
-    do
-       if test $last_status -ne 0; then
-           prompt_after_failed_merge <&3 || exit 1
-       fi
-       printf "\n"
-       merge_file "$i" <&3
-       last_status=$?
-       if test $last_status -ne 0; then
-           rollup_status=1
-       fi
-    done
-else
-    while test $# -gt 0; do
-       if test $last_status -ne 0; then
-           prompt_after_failed_merge || exit 1
-       fi
-       printf "\n"
-       merge_file "$1"
-       last_status=$?
-       if test $last_status -ne 0; then
-           rollup_status=1
-       fi
-       shift
-    done
-fi
+IFS='
+'
+for i in $files
+do
+    if test $last_status -ne 0; then
+       prompt_after_failed_merge || exit 1
+    fi
+    printf "\n"
+    merge_file "$i"
+    last_status=$?
+    if test $last_status -ne 0; then
+       rollup_status=1
+    fi
+done
 
 exit $rollup_status
index 63da37bcc2730358140ae4b862040ed8fb0ed77e..9868a0bfb478707b361f664a252870b3d1939138 100755 (executable)
@@ -11,7 +11,7 @@ OPTIONS_SPEC=
 . git-sh-setup
 . git-sh-i18n
 set_reflog_action "pull${1+ $*}"
-require_work_tree
+require_work_tree_exists
 cd_to_toplevel
 
 
@@ -120,7 +120,7 @@ do
        --d|--dr|--dry|--dry-|--dry-r|--dry-ru|--dry-run)
                dry_run=--dry-run
                ;;
-       -h|--h|--he|--hel|--help|--help-|--help-a|--help-al|--help-all)
+       -h|--help-all)
                usage
                ;;
        *)
index 94f36c254c53366ba53d256c0bd50a1de07cdb85..804001bb4e29873ef08d950a40cf1f1745e2aa27 100644 (file)
@@ -161,6 +161,19 @@ do_with_author () {
        )
 }
 
+git_sequence_editor () {
+       if test -z "$GIT_SEQUENCE_EDITOR"
+       then
+               GIT_SEQUENCE_EDITOR="$(git config sequence.editor)"
+               if [ -z "$GIT_SEQUENCE_EDITOR" ]
+               then
+                       GIT_SEQUENCE_EDITOR="$(git var GIT_EDITOR)" || return $?
+               fi
+       fi
+
+       eval "$GIT_SEQUENCE_EDITOR" '"$@"'
+}
+
 pick_one () {
        ff=--ff
        case "$1" in -n) sha1=$2; ff= ;; *) sha1=$1 ;; esac
@@ -832,7 +845,7 @@ has_action "$todo" ||
        die_abort "Nothing to do"
 
 cp "$todo" "$todo".backup
-git_editor "$todo" ||
+git_sequence_editor "$todo" ||
        die_abort "Could not execute editor"
 
 has_action "$todo" ||
index 6759702c573f51ac00bf1b5c8abccf252cb4ff1d..00ca7b99fef35e21d24af844e6dbaa92cab5834f 100755 (executable)
@@ -63,7 +63,7 @@ skip!              skip current patch and continue
 "
 . git-sh-setup
 set_reflog_action rebase
-require_work_tree
+require_work_tree_exists
 cd_to_toplevel
 
 LF='
index 98ab33aae7e35c2d288bc34ddb2bbc71f8a16cdd..d491db92c9379cf0eea63182dd704feb6582b6e7 100755 (executable)
@@ -225,7 +225,6 @@ my %config_settings = (
     "cccmd" => \$cc_cmd,
     "aliasfiletype" => \$aliasfiletype,
     "bcc" => \@bcclist,
-    "aliasesfile" => \@alias_files,
     "suppresscc" => \@suppress_cc,
     "envelopesender" => \$envelope_sender,
     "multiedit" => \$multiedit,
@@ -234,6 +233,10 @@ my %config_settings = (
     "assume8bitencoding" => \$auto_8bit_encoding,
 );
 
+my %config_path_settings = (
+    "aliasesfile" => \@alias_files,
+);
+
 # Help users prepare for 1.7.0
 sub chain_reply_to {
        if (defined $chain_reply_to &&
@@ -275,7 +278,9 @@ $SIG{INT}  = \&signal_handler;
 # Begin by accumulating all the variables (defined above), that we will end up
 # needing, first, from the command line:
 
-my $rc = GetOptions("sender|from=s" => \$sender,
+my $help;
+my $rc = GetOptions("h" => \$help,
+                   "sender|from=s" => \$sender,
                     "in-reply-to=s" => \$initial_reply_to,
                    "subject=s" => \$initial_subject,
                    "to=s" => \@initial_to,
@@ -313,6 +318,7 @@ my $rc = GetOptions("sender|from=s" => \$sender,
                    "force" => \$force,
         );
 
+usage() if $help;
 unless ($rc) {
     usage();
 }
@@ -330,6 +336,19 @@ sub read_config {
                $$target = Git::config_bool(@repo, "$prefix.$setting") unless (defined $$target);
        }
 
+       foreach my $setting (keys %config_path_settings) {
+               my $target = $config_path_settings{$setting};
+               if (ref($target) eq "ARRAY") {
+                       unless (@$target) {
+                               my @values = Git::config_path(@repo, "$prefix.$setting");
+                               @$target = @values if (@values && defined $values[0]);
+                       }
+               }
+               else {
+                       $$target = Git::config_path(@repo, "$prefix.$setting") unless (defined $$target);
+               }
+       }
+
        foreach my $setting (keys %config_settings) {
                my $target = $config_settings{$setting};
                next if $setting eq "to" and defined $no_to;
@@ -1079,7 +1098,7 @@ X-Mailer: git-send-email $gitversion
                                        $smtp_encryption = '';
                                        # Send EHLO again to receive fresh
                                        # supported commands
-                                       $smtp->hello();
+                                       $smtp->hello($smtp_domain);
                                } else {
                                        die "Server does not support STARTTLS! ".$smtp->message;
                                }
@@ -1095,6 +1114,12 @@ X-Mailer: git-send-email $gitversion
                }
 
                if (defined $smtp_authuser) {
+                       # Workaround AUTH PLAIN/LOGIN interaction defect
+                       # with Authen::SASL::Cyrus
+                       eval {
+                               require Authen::SASL;
+                               Authen::SASL->import(qw(Perl));
+                       };
 
                        if (!defined $smtp_authpass) {
 
index 8e427dab31c27bd78d9eac1ad15bc1f5dcc044c4..1fba6c2de0b78ddb5aa1495c9c519c26b48eebc2 100644 (file)
@@ -90,7 +90,7 @@ $LONG_USAGE"
        fi
 
        case "$1" in
-               -h|--h|--he|--hel|--help)
+               -h)
                echo "$LONG_USAGE"
                exit
        esac
index 814d0d914eaf994462e61559b0692c958f97513b..3adab9363563e80e4ceb2c7fb5c3ab91ac1ef505 100755 (executable)
@@ -104,9 +104,9 @@ module_name()
        re=$(printf '%s\n' "$1" | sed -e 's/[].[^$\\*]/\\&/g')
        name=$( git config -f .gitmodules --get-regexp '^submodule\..*\.path$' |
                sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' )
-       test -z "$name" &&
-       die "$(eval_gettext "No submodule mapping found in .gitmodules for path '\$path'")"
-       echo "$name"
+       test -z "$name" &&
+       die "$(eval_gettext "No submodule mapping found in .gitmodules for path '\$path'")"
+       echo "$name"
 }
 
 #
@@ -128,13 +128,49 @@ module_clone()
                quiet=-q
        fi
 
-       if test -n "$reference"
+       gitdir=
+       gitdir_base=
+       name=$(module_name "$path" 2>/dev/null)
+       base_path=$(dirname "$path")
+
+       gitdir=$(git rev-parse --git-dir)
+       gitdir_base="$gitdir/modules/$base_path"
+       gitdir="$gitdir/modules/$path"
+
+       case $gitdir in
+       /*)
+               a="$(cd_to_toplevel && pwd)/"
+               b=$gitdir
+               while [ "$b" ] && [ "${a%%/*}" = "${b%%/*}" ]
+               do
+                       a=${a#*/} b=${b#*/};
+               done
+
+               rel="$a$name"
+               rel=`echo $rel | sed -e 's|[^/]*|..|g'`
+               rel_gitdir="$rel/$b"
+               ;;
+       *)
+               rel=`echo $name | sed -e 's|[^/]*|..|g'`
+               rel_gitdir="$rel/$gitdir"
+               ;;
+       esac
+
+       if test -d "$gitdir"
        then
-               git-clone $quiet "$reference" -n "$url" "$path"
+               mkdir -p "$path"
+               echo "gitdir: $rel_gitdir" >"$path/.git"
+               rm -f "$gitdir/index"
        else
-               git-clone $quiet -n "$url" "$path"
-       fi ||
-       die "$(eval_gettext "Clone of '\$url' into submodule path '\$path' failed")"
+               mkdir -p "$gitdir_base"
+               if test -n "$reference"
+               then
+                       git-clone $quiet "$reference" -n "$url" "$path" --separate-git-dir "$gitdir"
+               else
+                       git-clone $quiet -n "$url" "$path" --separate-git-dir "$gitdir"
+               fi ||
+               die "$(eval_gettext "Clone of '\$url' into submodule path '\$path' failed")"
+       fi
 }
 
 #
@@ -426,6 +462,9 @@ cmd_update()
                --recursive)
                        recursive=1
                        ;;
+               --checkout)
+                       update="checkout"
+                       ;;
                --)
                        shift
                        break
@@ -458,7 +497,19 @@ cmd_update()
                fi
                name=$(module_name "$path") || exit
                url=$(git config submodule."$name".url)
-               update_module=$(git config submodule."$name".update)
+               if ! test -z "$update"
+               then
+                       update_module=$update
+               else
+                       update_module=$(git config submodule."$name".update)
+               fi
+
+               if test "$update_module" = "none"
+               then
+                       echo "Skipping submodule '$path'"
+                       continue
+               fi
+
                if test -z "$url"
                then
                        # Only mention uninitialized submodules when its
@@ -480,11 +531,6 @@ Maybe you want to use 'update --init'?")"
                        die "$(eval_gettext "Unable to find current revision in submodule path '\$path'")"
                fi
 
-               if ! test -z "$update"
-               then
-                       update_module=$update
-               fi
-
                if test "$subsha1" != "$sha1"
                then
                        subforce=$force
index 351e743a902568c8b6f68ffaf4030f087ba8568d..e30df22d89d97642f0b99c3a35ee0b2c5c062bb3 100755 (executable)
@@ -22,14 +22,13 @@ $Git::SVN::default_ref_id = $ENV{GIT_SVN_ID} || 'git-svn';
 $Git::SVN::Ra::_log_window_size = 100;
 $Git::SVN::_minimize_url = 'unset';
 
-if (! exists $ENV{SVN_SSH}) {
-       if (exists $ENV{GIT_SSH}) {
-               $ENV{SVN_SSH} = $ENV{GIT_SSH};
-               if ($^O eq 'msys') {
-                       $ENV{SVN_SSH} =~ s/\\/\\\\/g;
-                       $ENV{SVN_SSH} =~ s/(.*)/"$1"/;
-               }
-       }
+if (! exists $ENV{SVN_SSH} && exists $ENV{GIT_SSH}) {
+       $ENV{SVN_SSH} = $ENV{GIT_SSH};
+}
+
+if (exists $ENV{SVN_SSH} && $^O eq 'msys') {
+       $ENV{SVN_SSH} =~ s/\\/\\\\/g;
+       $ENV{SVN_SSH} =~ s/(.*)/"$1"/;
 }
 
 $Git::SVN::Log::TZ = $ENV{TZ};
@@ -87,14 +86,15 @@ my ($_stdin, $_help, $_edit,
        $_version, $_fetch_all, $_no_rebase, $_fetch_parent,
        $_merge, $_strategy, $_dry_run, $_local,
        $_prefix, $_no_checkout, $_url, $_verbose,
-       $_git_format, $_commit_url, $_tag, $_merge_info);
+       $_git_format, $_commit_url, $_tag, $_merge_info, $_interactive);
 $Git::SVN::_follow_parent = 1;
 $SVN::Git::Fetcher::_placeholder_filename = ".gitignore";
 $_q ||= 0;
 my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username,
                     'config-dir=s' => \$Git::SVN::Ra::config_dir,
                     'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache,
-                    'ignore-paths=s' => \$SVN::Git::Fetcher::_ignore_regex );
+                    'ignore-paths=s' => \$SVN::Git::Fetcher::_ignore_regex,
+                    'ignore-refs=s' => \$Git::SVN::Ra::_ignore_refs_regex );
 my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent,
                'authors-file|A=s' => \$_authors,
                'authors-prog=s' => \$_authors_prog,
@@ -163,6 +163,7 @@ my %cmd = (
                          'revision|r=i' => \$_revision,
                          'no-rebase' => \$_no_rebase,
                          'mergeinfo=s' => \$_merge_info,
+                         'interactive|i' => \$_interactive,
                        %cmt_opts, %fc_opts } ],
        branch => [ \&cmd_branch,
                    'Create a branch in the SVN repository',
@@ -256,6 +257,27 @@ my %cmd = (
                {} ],
 );
 
+use Term::ReadLine;
+package FakeTerm;
+sub new {
+       my ($class, $reason) = @_;
+       return bless \$reason, shift;
+}
+sub readline {
+       my $self = shift;
+       die "Cannot use readline on FakeTerm: $$self";
+}
+package main;
+
+my $term = eval {
+       $ENV{"GIT_SVN_NOTTY"}
+               ? new Term::ReadLine 'git-svn', \*STDIN, \*STDOUT
+               : new Term::ReadLine 'git-svn';
+};
+if ($@) {
+       $term = new FakeTerm "$@: going non-interactive";
+}
+
 my $cmd;
 for (my $i = 0; $i < @ARGV; $i++) {
        if (defined $cmd{$ARGV[$i]}) {
@@ -299,7 +321,7 @@ read_git_config(\%opts);
 if ($cmd && ($cmd eq 'log' || $cmd eq 'blame')) {
        Getopt::Long::Configure('pass_through');
 }
-my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version,
+my $rv = GetOptions(%opts, 'h|H' => \$_help, 'version|V' => \$_version,
                     'minimize-connections' => \$Git::SVN::Migration::_minimize,
                     'id|i=s' => \$Git::SVN::default_ref_id,
                     'svn-remote|remote|R=s' => sub {
@@ -366,6 +388,36 @@ sub version {
        exit 0;
 }
 
+sub ask {
+       my ($prompt, %arg) = @_;
+       my $valid_re = $arg{valid_re};
+       my $default = $arg{default};
+       my $resp;
+       my $i = 0;
+
+       if ( !( defined($term->IN)
+            && defined( fileno($term->IN) )
+            && defined( $term->OUT )
+            && defined( fileno($term->OUT) ) ) ){
+               return defined($default) ? $default : undef;
+       }
+
+       while ($i++ < 10) {
+               $resp = $term->readline($prompt);
+               if (!defined $resp) { # EOF
+                       print "\n";
+                       return defined $default ? $default : undef;
+               }
+               if ($resp eq '' and defined $default) {
+                       return $default;
+               }
+               if (!defined $valid_re or $resp =~ /$valid_re/) {
+                       return $resp;
+               }
+       }
+       return undef;
+}
+
 sub do_git_init_db {
        unless (-d $ENV{GIT_DIR}) {
                my @init_db = ('init');
@@ -388,9 +440,12 @@ sub do_git_init_db {
                command_noisy('config', "$pfx.$i", $icv{$i});
                $set = $i;
        }
-       my $ignore_regex = \$SVN::Git::Fetcher::_ignore_regex;
-       command_noisy('config', "$pfx.ignore-paths", $$ignore_regex)
-               if defined $$ignore_regex;
+       my $ignore_paths_regex = \$SVN::Git::Fetcher::_ignore_regex;
+       command_noisy('config', "$pfx.ignore-paths", $$ignore_paths_regex)
+               if defined $$ignore_paths_regex;
+       my $ignore_refs_regex = \$Git::SVN::Ra::_ignore_refs_regex;
+       command_noisy('config', "$pfx.ignore-refs", $$ignore_refs_regex)
+               if defined $$ignore_refs_regex;
 
        if (defined $SVN::Git::Fetcher::_preserve_empty_dirs) {
                my $fname = \$SVN::Git::Fetcher::_placeholder_filename;
@@ -629,7 +684,7 @@ sub populate_merge_info {
                                fatal "merge commit $d has ancestor $parent, but that change "
                      ."does not have git-svn metadata!";
                        }
-                       unless ($branchurl =~ /^$rooturl(.*)/) {
+                       unless ($branchurl =~ /^\Q$rooturl\E(.*)/) {
                                fatal "commit $parent git-svn metadata changed mid-run!";
                        }
                        my $branchpath = $1;
@@ -746,6 +801,27 @@ sub cmd_dcommit {
                     "If these changes depend on each other, re-running ",
                     "without --no-rebase may be required."
        }
+
+       if (defined $_interactive){
+               my $ask_default = "y";
+               foreach my $d (@$linear_refs){
+                       my ($fh, $ctx) = command_output_pipe(qw(show --summary), "$d");
+                       while (<$fh>){
+                               print $_;
+                       }
+                       command_close_pipe($fh, $ctx);
+                       $_ = ask("Commit this patch to SVN? ([y]es (default)|[n]o|[q]uit|[a]ll): ",
+                                valid_re => qr/^(?:yes|y|no|n|quit|q|all|a)/i,
+                                default => $ask_default);
+                       die "Commit this patch reply required" unless defined $_;
+                       if (/^[nq]/i) {
+                               exit(0);
+                       } elsif (/^a/i) {
+                               last;
+                       }
+               }
+       }
+
        my $expect_url = $url;
 
        my $push_merge_info = eval {
@@ -791,7 +867,7 @@ sub cmd_dcommit {
                                                         ."has uuid $uuid!";
                                        }
 
-                                       unless ($branchurl =~ /^$rooturl(.*)/) {
+                                       unless ($branchurl =~ /^\Q$rooturl\E(.*)/) {
                                                # This branch is very strange indeed.
                                                fatal "merge parent $parent for $d is on branch "
                                                         ."$branchurl, which is not under the "
@@ -2119,6 +2195,8 @@ sub read_all_remotes {
                        $r->{$1}->{url} = $2;
                } elsif (m!^(.+)\.pushurl=\s*(.*)\s*$!) {
                        $r->{$1}->{pushurl} = $2;
+               } elsif (m!^(.+)\.ignore-refs=\s*(.*)\s*$!) {
+                       $r->{$1}->{ignore_refs_regex} = $2;
                } elsif (m!^(.+)\.(branches|tags)=$svn_refspec$!) {
                        my ($remote, $t, $local_ref, $remote_ref) =
                                                             ($1, $2, $3, $4);
@@ -2155,6 +2233,16 @@ sub read_all_remotes {
                }
        } keys %$r;
 
+       foreach my $remote (keys %$r) {
+               foreach ( grep { defined $_ }
+                         map { $r->{$remote}->{$_} } qw(branches tags) ) {
+                       foreach my $rs ( @$_ ) {
+                               $rs->{ignore_refs_regex} =
+                                   $r->{$remote}->{ignore_refs_regex};
+                       }
+               }
+       }
+
        $r;
 }
 
@@ -5310,7 +5398,7 @@ sub apply_diff {
 }
 
 package Git::SVN::Ra;
-use vars qw/@ISA $config_dir $_log_window_size/;
+use vars qw/@ISA $config_dir $_ignore_refs_regex $_log_window_size/;
 use strict;
 use warnings;
 my ($ra_invalid, $can_do_switch, %ignored_err, $RA);
@@ -5768,6 +5856,17 @@ sub get_dir_globbed {
        @finalents;
 }
 
+# return value: 0 -- don't ignore, 1 -- ignore
+sub is_ref_ignored {
+       my ($g, $p) = @_;
+       my $refname = $g->{ref}->full_path($p);
+       return 1 if defined($g->{ignore_refs_regex}) &&
+                   $refname =~ m!$g->{ignore_refs_regex}!;
+       return 0 unless defined($_ignore_refs_regex);
+       return 1 if $refname =~ m!$_ignore_refs_regex!o;
+       return 0;
+}
+
 sub match_globs {
        my ($self, $exists, $paths, $globs, $r) = @_;
 
@@ -5804,6 +5903,7 @@ sub match_globs {
                        next unless /$g->{path}->{regex}/;
                        my $p = $1;
                        my $pathname = $g->{path}->full_path($p);
+                       next if is_ref_ignored($g, $p);
                        next if $exists->{$pathname};
                        next if ($self->check_path($pathname, $r) !=
                                 $SVN::Node::dir);
index e9de241dd004a9f5d6d6d8e6a300a1fa5949a46d..1e827264b4cab04a801804b8d066f9d2cbb85edc 100755 (executable)
@@ -156,7 +156,7 @@ firefox|iceweasel|seamonkey|iceape)
        ;;
 google-chrome|chrome|chromium|chromium-browser)
        # No need to specify newTab. It's default in chromium
-       eval "$browser_path" "$@" &
+       "$browser_path" "$@" &
        ;;
 konqueror)
        case "$(basename "$browser_path")" in
@@ -164,10 +164,10 @@ konqueror)
                # It's simpler to use kfmclient to open a new tab in konqueror.
                browser_path="$(echo "$browser_path" | sed -e 's/konqueror$/kfmclient/')"
                type "$browser_path" > /dev/null 2>&1 || die "No '$browser_path' found."
-               eval "$browser_path" newTab "$@"
+               "$browser_path" newTab "$@" &
                ;;
        kfmclient)
-               eval "$browser_path" newTab "$@"
+               "$browser_path" newTab "$@" &
                ;;
        *)
                "$browser_path" "$@" &
@@ -175,7 +175,7 @@ konqueror)
        esac
        ;;
 w3m|elinks|links|lynx|open)
-       eval "$browser_path" "$@"
+       "$browser_path" "$@"
        ;;
 start)
        exec "$browser_path" '"web-browse"' "$@"
@@ -185,7 +185,7 @@ opera|dillo)
        ;;
 *)
        if test -n "$browser_cmd"; then
-               ( eval $browser_cmd "$@" )
+               ( eval "$browser_cmd \"\$@\"" )
        fi
        ;;
 esac
index 91c8462b7dae171247c40db7f7b5b36d2c0633a1..c562c622847b9559fd40ae867f0d3cb229fa784b 100644 (file)
@@ -199,7 +199,11 @@ rm -rf $RPM_BUILD_ROOT
 
 %files -n gitweb
 %defattr(-,root,root)
+%doc gitweb/README gitweb/INSTALL Documentation/*gitweb*.txt
 %{_datadir}/gitweb
+%{!?_without_docs: %{_mandir}/man1/*gitweb*.1*}
+%{!?_without_docs: %{_mandir}/man5/*gitweb*.5*}
+%{!?_without_docs: %doc Documentation/*gitweb*.html }
 
 %files -n perl-Git -f perl-files
 %defattr(-,root,root)
@@ -208,6 +212,9 @@ rm -rf $RPM_BUILD_ROOT
 # No files for you!
 
 %changelog
+* Sun Sep 18 2011 Jakub Narebski <jnareb@gmail.com>
+- Add gitweb manpages to 'gitweb' subpackage
+
 * Wed Jun 30 2010 Junio C Hamano <gitster@pobox.com>
 - Add 'gitweb' subpackage.
 
index a383e6c08d5752df1ff42fa25019273dccdaebc8..007a1bfdf37d231470f69d9d0cffa46e80127f34 100644 (file)
@@ -54,7 +54,7 @@ def valid_git_ref (ref_name):
     # The following is a reimplementation of the git check-ref-format
     # command.  The rules were derived from the git check-ref-format(1)
     # manual page.  This code should be replaced by a call to
-    # check_ref_format() in the git library, when such is available.
+    # check_refname_format() in the git library, when such is available.
     if ref_name.endswith('/') or \
        ref_name.startswith('.') or \
        ref_name.count('/.') or \
index f5efe7454ca42decbe7b113388bc6233e2311c1e..6d4540679731bdfd33af61635c59b798b19a8888 100644 (file)
@@ -130,6 +130,8 @@ You can specify the following configuration variables when building GIT:
    Points to an .html file which is included on the gitweb project
    overview page ('projects_list' view), if it exists.  Relative to
    gitweb.cgi script.  [Default: indextext.html]
+ * GITWEB_SITE_HTML_HEAD_STRING
+   html snippet to include in the <head> section of each page. [No default]
  * GITWEB_SITE_HEADER
    Filename of html text to include at top of each page.  Relative to
    gitweb.cgi script.  [No default]
@@ -229,7 +231,7 @@ Gitweb config file
 ------------------
 
 See also "Runtime gitweb configuration" section in README file
-for gitweb (in gitweb/README).
+for gitweb (in gitweb/README), and gitweb.conf(5) manpage.
 
 - You can configure gitweb further using the per-instance gitweb configuration file;
   by default this is a file named gitweb_config.perl in the same place as
@@ -287,97 +289,19 @@ adding the following lines to your $GITWEB_CONFIG:
 Gitweb repositories
 -------------------
 
-- By default all git repositories under projectroot are visible and
-  available to gitweb. The list of projects is generated by default by
-  scanning the projectroot directory for git repositories (for object
-  databases to be more exact).
-
-  You can provide a pre-generated list of [visible] repositories,
-  together with information about their owners (the project ownership
-  defaults to the owner of the repository directory otherwise), by setting
-  the GITWEB_LIST build configuration variable (or the $projects_list
-  variable in the gitweb config file) to point to a plain file.
-
-  Each line of the projects list file should consist of the url-encoded path
-  to the project repository database (relative to projectroot), followed
-  by the url-encoded project owner on the same line (separated by a space).
-  Spaces in both project path and project owner have to be encoded as either
-  '%20' or '+'.
-
-  Other characters that have to be url-encoded, i.e. replaced by '%'
-  followed by two-digit character number in octal, are: other whitespace
-  characters (because they are field separator in a record), plus sign '+'
-  (because it can be used as replacement for spaces), and percent sign '%'
-  (which is used for encoding / escaping).
-
-  You can generate the projects list index file using the project_index
-  action (the 'TXT' link on projects list page) directly from gitweb.
-
-- By default, even if a project is not visible on projects list page, you
-  can view it nevertheless by hand-crafting a gitweb URL. You can set the
-  GITWEB_STRICT_EXPORT build configuration variable (or the $strict_export
-  variable in the gitweb config file) to only allow viewing of
-  repositories also shown on the overview page.
-
-- Alternatively, you can configure gitweb to only list and allow
-  viewing of the explicitly exported repositories, via the
-  GITWEB_EXPORT_OK build configuration variable (or the $export_ok
-  variable in gitweb config file). If it evaluates to true, gitweb
-  shows repositories only if this file exists in its object database
-  (if directory has the magic file named $export_ok).
-
-- Finally, it is possible to specify an arbitrary perl subroutine that
-  will be called for each project to determine if it can be exported.
-  The subroutine receives an absolute path to the project as its only
-  parameter.
-
-  For example, if you use mod_perl to run the script, and have dumb
-  http protocol authentication configured for your repositories, you
-  can use the following hook to allow access only if the user is
-  authorized to read the files:
-
-    $export_auth_hook = sub {
-        use Apache2::SubRequest ();
-        use Apache2::Const -compile => qw(HTTP_OK);
-        my $path = "$_[0]/HEAD";
-        my $r    = Apache2::RequestUtil->request;
-        my $sub  = $r->lookup_file($path);
-        return $sub->filename eq $path
-            && $sub->status == Apache2::Const::HTTP_OK;
-    };
-
-
-Generating projects list using gitweb
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-We assume that GITWEB_CONFIG has its default Makefile value, namely
-gitweb_config.perl. Put the following in gitweb_make_index.perl file:
-
-       $GITWEB_CONFIG = "gitweb_config.perl";
-       do $GITWEB_CONFIG if -e $GITWEB_CONFIG;
-
-       $projects_list = $projectroot;
-
-Then create the following script to get list of project in the format
-suitable for GITWEB_LIST build configuration variable (or
-$projects_list variable in gitweb config):
-
-       #!/bin/sh
-
-       export GITWEB_CONFIG="gitweb_make_index.perl"
-       export GATEWAY_INTERFACE="CGI/1.1"
-       export HTTP_ACCEPT="*/*"
-       export REQUEST_METHOD="GET"
-       export QUERY_STRING="a=project_index"
-
-       perl -- /var/www/cgi-bin/gitweb.cgi
+By default gitweb shows all git repositories under single common repository
+root on a local filesystem; see description of GITWEB_PROJECTROOT build-time
+configuration variable above (and also of GITWEB_LIST).
+
+More advanced usage, like limiting access or visibility of repositories and
+managing multiple roots are described on gitweb manpage.
 
 
 Example web server configuration
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-See also "Webserver configuration" section in README file for gitweb
-(in gitweb/README).
+See also "Webserver configuration" and "Advanced web server setup" sections
+in gitweb(1) manpage.
 
 
 - Apache2, gitweb installed as CGI script,
index 1c85b5fda8bc994e0ecd249e11e8f2331098bea9..cd194d057f9231ed77f57f05b22a226cd1210f6d 100644 (file)
@@ -34,6 +34,7 @@ GITWEB_CSS = static/gitweb.css
 GITWEB_LOGO = static/git-logo.png
 GITWEB_FAVICON = static/git-favicon.png
 GITWEB_JS = static/gitweb.js
+GITWEB_SITE_HTML_HEAD_STRING =
 GITWEB_SITE_HEADER =
 GITWEB_SITE_FOOTER =
 HIGHLIGHT_BIN = highlight
@@ -144,6 +145,7 @@ GITWEB_REPLACE = \
        -e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \
        -e 's|++GITWEB_FAVICON++|$(GITWEB_FAVICON)|g' \
        -e 's|++GITWEB_JS++|$(GITWEB_JS)|g' \
+       -e 's|++GITWEB_SITE_HTML_HEAD_STRING++|$(GITWEB_SITE_HTML_HEAD_STRING)|g' \
        -e 's|++GITWEB_SITE_HEADER++|$(GITWEB_SITE_HEADER)|g' \
        -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \
        -e 's|++HIGHLIGHT_BIN++|$(HIGHLIGHT_BIN)|g'
@@ -185,7 +187,9 @@ install: all
 ### Cleaning rules
 
 clean:
-       $(RM) gitweb.cgi static/gitweb.min.js static/gitweb.min.css GITWEB-BUILD-OPTIONS
+       $(RM) gitweb.cgi static/gitweb.js \
+               static/gitweb.min.js static/gitweb.min.css \
+               GITWEB-BUILD-OPTIONS
 
 .PHONY: all clean install test test-installed .FORCE-GIT-VERSION-FILE FORCE
 
index a9988200d6b67066b837e7298e0e1529057a4bca..6da4778b73e3ef1d098fce7655bf4ca5bb2443e5 100644 (file)
@@ -7,9 +7,18 @@ The one working on:
 From the git version 1.4.0 gitweb is bundled with git.
 
 
+Build time gitweb configuration
+-------------------------------
+There are many configuration variables which affect building gitweb (among
+others creating gitweb.cgi out of gitweb.perl by replacing placeholders such
+as `++GIT_BINDIR++` by their build-time values).
+
+Building and installing gitweb is described in gitweb's INSTALL file
+(in 'gitweb/INSTALL').
+
+
 Runtime gitweb configuration
 ----------------------------
-
 Gitweb obtains configuration data from the following sources in the
 following order:
 
@@ -41,400 +50,22 @@ Ultimate description on how to reconfigure the default features setting
 in your `GITWEB_CONFIG` or per-project in `project.git/config` can be found
 as comments inside 'gitweb.cgi'.
 
-See also the "Gitweb config file" (with an example of config file), and
-the "Gitweb repositories" sections in INSTALL file for gitweb.
-
-
-The gitweb config file is a fragment of perl code. You can set variables
-using "our $variable = value"; text from "#" character until the end
-of a line is ignored. See perlsyn(1) man page for details.
-
-Below is the list of variables which you might want to set in gitweb config.
-See the top of 'gitweb.cgi' for the full list of variables and their
-descriptions.
-
-Gitweb config file variables
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You can set, among others, the following variables in gitweb config files
-(with the exception of $projectroot and $projects_list this list does
-not include variables usually directly set during build):
- * $GIT
-   Core git executable to use.  By default set to "$GIT_BINDIR/git", which
-   in turn is by default set to "$(bindir)/git".  If you use git from binary
-   package, set this to "/usr/bin/git".  This can just be "git" if your
-   webserver has a sensible PATH.  If you have multiple git versions
-   installed it can be used to choose which one to use.
- * $version
-   Gitweb version, set automatically when creating gitweb.cgi from
-   gitweb.perl. You might want to modify it if you are running modified
-   gitweb.
- * $projectroot
-   Absolute filesystem path which will be prepended to project path;
-   the path to repository is $projectroot/$project.  Set to
-   $GITWEB_PROJECTROOT during installation.  This variable have to be
-   set correctly for gitweb to find repositories.
- * $projects_list
-   Source of projects list, either directory to scan, or text file
-   with list of repositories (in the "<URI-encoded repository path> SP
-   <URI-encoded repository owner>" line format; actually there can be
-   any sequence of whitespace in place of space (SP)).  Set to
-   $GITWEB_LIST during installation.  If empty, $projectroot is used
-   to scan for repositories.
- * $my_url, $my_uri
-   Full URL and absolute URL of gitweb script;
-   in earlier versions of gitweb you might have need to set those
-   variables, now there should be no need to do it.  See
-   $per_request_config if you need to set them still.
- * $base_url
-   Base URL for relative URLs in pages generated by gitweb,
-   (e.g. $logo, $favicon, @stylesheets if they are relative URLs),
-   needed and used only for URLs with nonempty PATH_INFO via
-   <base href="$base_url">.  Usually gitweb sets its value correctly,
-   and there is no need to set this variable, e.g. to $my_uri or "/".
-   See $per_request_config if you need to set it anyway.
- * $home_link
-   Target of the home link on top of all pages (the first part of view
-   "breadcrumbs").  By default set to absolute URI of a page ($my_uri).
- * @stylesheets
-   List of URIs of stylesheets (relative to base URI of a page). You
-   might specify more than one stylesheet, for example use gitweb.css
-   as base, with site specific modifications in separate stylesheet
-   to make it easier to upgrade gitweb. You can add 'site' stylesheet
-   for example by using
-      push @stylesheets, "gitweb-site.css";
-   in the gitweb config file.
- * $logo_url, $logo_label
-   URI and label (title) of GIT logo link (or your site logo, if you choose
-   to use different logo image). By default they point to git homepage;
-   in the past they pointed to git documentation at www.kernel.org.
- * $projects_list_description_width
-   The width (in characters) of the projects list "Description" column.
-   Longer descriptions will be cut (trying to cut at word boundary);
-   full description is available as 'title' attribute (usually shown on
-   mouseover).  By default set to 25, which might be too small if you
-   use long project descriptions.
- * $projects_list_group_categories
-   Enables the grouping of projects by category on the project list page.
-   The category of a project is determined by the $GIT_DIR/category
-   file or the 'gitweb.category' variable in its repository configuration.
-   Disabled by default.
- * $project_list_default_category
-   Default category for projects for which none is specified.  If set
-   to the empty string, such projects will remain uncategorized and
-   listed at the top, above categorized projects.
- * @git_base_url_list
-   List of git base URLs used for URL to where fetch project from, shown
-   in project summary page.  Full URL is "$git_base_url/$project".
-   You can setup multiple base URLs (for example one for  git:// protocol
-   access, and one for http:// "dumb" protocol access).  Note that per
-   repository configuration in 'cloneurl' file, or as values of gitweb.url
-   project config.
- * $default_blob_plain_mimetype
-   Default mimetype for blob_plain (raw) view, if mimetype checking
-   doesn't result in some other type; by default 'text/plain'.
- * $default_text_plain_charset
-   Default charset for text files. If not set, web server configuration
-   would be used.
- * $mimetypes_file
-   File to use for (filename extension based) guessing of MIME types before
-   trying /etc/mime.types. Path, if relative, is taken currently as
-   relative to the current git repository.
- * $fallback_encoding
-   Gitweb assumes this charset if line contains non-UTF-8 characters.
-   Fallback decoding is used without error checking, so it can be even
-   'utf-8'. Value must be valid encoding; see Encoding::Supported(3pm) man
-   page for a list.   By default 'latin1', aka. 'iso-8859-1'.
- * @diff_opts
-   Rename detection options for git-diff and git-diff-tree. By default
-   ('-M'); set it to ('-C') or ('-C', '-C') to also detect copies, or
-   set it to () if you don't want to have renames detection.
- * $prevent_xss
-   If true, some gitweb features are disabled to prevent content in
-   repositories from launching cross-site scripting (XSS) attacks.  Set this
-   to true if you don't trust the content of your repositories. The default
-   is false.
- * $maxload
-   Used to set the maximum load that we will still respond to gitweb queries.
-   If server load exceed this value then return "503 Service Unavailable" error.
-   Server load is taken to be 0 if gitweb cannot determine its value.  Set it to
-   undefined value to turn it off.  The default is 300.
- * $highlight_bin
-   Path to the highlight executable to use (must be the one from
-   http://www.andre-simon.de due to assumptions about parameters and output).
-   Useful if highlight is not installed on your webserver's PATH.
-   [Default: highlight]
- * $per_request_config
-   If set to code reference, it would be run once per each request.  You can
-   set parts of configuration that change per session, e.g. by setting it to
-     sub { $ENV{GL_USER} = $cgi->remote_user || "gitweb"; }
-   Otherwise it is treated as boolean value: if true gitweb would process
-   config file once per request, if false it would process config file only
-   once.  Note: $my_url, $my_uri, and $base_url are overwritten with
-   their default values before every request, so if you want to change
-   them, be sure to set this variable to true or a code reference effecting
-   the desired changes.  The default is true.
-
-Projects list file format
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Instead of having gitweb find repositories by scanning filesystem starting
-from $projectroot (or $projects_list, if it points to directory), you can
-provide list of projects by setting $projects_list to a text file with list
-of projects (and some additional info).  This file uses the following
-format:
-
-One record (for project / repository) per line, whitespace separated fields;
-does not support (at least for now) lines continuation (newline escaping).
-Leading and trailing whitespace are ignored, any run of whitespace can be
-used as field separator (rules for Perl's "split(' ', $line)").  Keyed by
-the first field, which is project name, i.e. path to repository GIT_DIR
-relative to $projectroot.  Fields use modified URI encoding, defined in
-RFC 3986, section 2.1 (Percent-Encoding), or rather "Query string encoding"
-(see http://en.wikipedia.org/wiki/Query_string#URL_encoding), the difference
-being that SP (' ') can be encoded as '+' (and therefore '+' has to be also
-percent-encoded).  Reserved characters are: '%' (used for encoding), '+'
-(can be used to encode SPACE), all whitespace characters as defined in Perl,
-including SP, TAB and LF, (used to separate fields in a record).
-
-Currently list of fields is
- * <repository path>  - path to repository GIT_DIR, relative to $projectroot
- * <repository owner> - displayed as repository owner, preferably full name,
-                        or email, or both
-
-You can additionally use $projects_list file to limit which repositories
-are visible, and together with $strict_export to limit access to
-repositories (see "Gitweb repositories" section in gitweb/INSTALL).
-
-
-Per-repository gitweb configuration
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You can also configure individual repositories shown in gitweb by creating
-file in the GIT_DIR of git repository, or by setting some repo configuration
-variable (in GIT_DIR/config).
-
-You can use the following files in repository:
- * README.html
-   A .html file (HTML fragment) which is included on the gitweb project
-   summary page inside <div> block element. You can use it for longer
-   description of a project, to provide links (for example to project's
-   homepage), etc. This is recognized only if XSS prevention is off
-   ($prevent_xss is false); a way to include a readme safely when XSS
-   prevention is on may be worked out in the future.
- * description (or gitweb.description)
-   Short (shortened by default to 25 characters in the projects list page)
-   single line description of a project (of a repository). Plain text file;
-   HTML will be escaped. By default set to
-     Unnamed repository; edit this file to name it for gitweb.
-   from the template during repository creation. You can use the
-   gitweb.description repo configuration variable, but the file takes
-   precedence.
- * category (or gitweb.category)
-   Singe line category of a project, used to group projects if
-   $projects_list_group_categories is enabled. By default (file and
-   configuration variable absent), uncategorized projects are put in
-   the $project_list_default_category category. You can use the
-   gitweb.category repo configuration variable, but the file takes
-   precedence.
- * cloneurl (or multiple-valued gitweb.url)
-   File with repository URL (used for clone and fetch), one per line.
-   Displayed in the project summary page. You can use multiple-valued
-   gitweb.url repository configuration variable for that, but the file
-   takes precedence.
- * gitweb.owner
-   You can use the gitweb.owner repository configuration variable to set
-   repository's owner. It is displayed in the project list and summary
-   page. If it's not set, filesystem directory's owner is used
-   (via GECOS field / real name field from getpwiud(3)).
- * various gitweb.* config variables (in config)
-   Read description of %feature hash for detailed list, and some
-   descriptions.
-
-
-Webserver configuration
------------------------
-
-If you want to have one URL for both gitweb and your http://
-repositories, you can configure apache like this:
-
-<VirtualHost *:80>
-    ServerName         git.example.org
-    DocumentRoot       /pub/git
-    SetEnv                     GITWEB_CONFIG   /etc/gitweb.conf
-
-    # turning on mod rewrite
-    RewriteEngine on
-
-    # make the front page an internal rewrite to the gitweb script
-    RewriteRule ^/$  /cgi-bin/gitweb.cgi
-
-    # make access for "dumb clients" work
-    RewriteRule ^/(.*\.git/(?!/?(HEAD|info|objects|refs)).*)?$ /cgi-bin/gitweb.cgi%{REQUEST_URI}  [L,PT]
-</VirtualHost>
-
-The above configuration expects your public repositories to live under
-/pub/git and will serve them as http://git.domain.org/dir-under-pub-git,
-both as cloneable GIT URL and as browseable gitweb interface.
-If you then start your git-daemon with --base-path=/pub/git --export-all
-then you can even use the git:// URL with exactly the same path.
-
-Setting the environment variable GITWEB_CONFIG will tell gitweb to use
-the named file (i.e. in this example /etc/gitweb.conf) as a
-configuration for gitweb.  Perl variables defined in here will
-override the defaults given at the head of the gitweb.perl (or
-gitweb.cgi).  Look at the comments in that file for information on
-which variables and what they mean.
-
-If you use the rewrite rules from the example you'll likely also need
-something like the following in your gitweb.conf (or gitweb_config.perl) file:
-
-  @stylesheets = ("/some/absolute/path/gitweb.css");
-  $my_uri = "/";
-  $home_link = "/";
-
-
-Webserver configuration with multiple projects' root
-----------------------------------------------------
-
-If you want to use gitweb with several project roots you can edit your apache
-virtual host and gitweb.conf configuration files like this :
-
-virtual host configuration :
-
-<VirtualHost *:80>
-    ServerName                 git.example.org
-    DocumentRoot               /pub/git
-    SetEnv                             GITWEB_CONFIG   /etc/gitweb.conf
-
-    # turning on mod rewrite
-    RewriteEngine on
-
-    # make the front page an internal rewrite to the gitweb script
-    RewriteRule ^/$            /cgi-bin/gitweb.cgi [QSA,L,PT]
-
-    # look for a public_git folder in unix users' home
-    # http://git.example.org/~<user>/
-    RewriteRule ^/\~([^\/]+)(/|/gitweb.cgi)?$  /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT]
-
-    # http://git.example.org/+<user>/
-    #RewriteRule ^/\+([^\/]+)(/|/gitweb.cgi)?$ /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT]
-
-    # http://git.example.org/user/<user>/
-    #RewriteRule ^/user/([^\/]+)/(gitweb.cgi)?$        /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/home/$1/public_git/,L,PT]
-
-    # defined list of project roots
-    RewriteRule ^/scm(/|/gitweb.cgi)?$         /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/pub/scm/,L,PT]
-    RewriteRule ^/var(/|/gitweb.cgi)?$         /cgi-bin/gitweb.cgi [QSA,E=GITWEB_PROJECTROOT:/var/git/,L,PT]
-
-    # make access for "dumb clients" work
-    RewriteRule ^/(.*\.git/(?!/?(HEAD|info|objects|refs)).*)?$ /cgi-bin/gitweb.cgi%{REQUEST_URI}  [L,PT]
-</VirtualHost>
-
-gitweb.conf configuration :
-
-$projectroot = $ENV{'GITWEB_PROJECTROOT'} || "/pub/git";
-
-These configurations enable two things. First, each unix user (<user>) of the
-server will be able to browse through gitweb git repositories found in
-~/public_git/ with the following url : http://git.example.org/~<user>/
-
-If you do not want this feature on your server just remove the second rewrite rule.
-
-If you already use mod_userdir in your virtual host or you don't want to use
-the '~' as first character just comment or remove the second rewrite rule and
-uncomment one of the following according to what you want.
-
-Second, repositories found in /pub/scm/ and /var/git/ will be accesible
-through http://git.example.org/scm/ and http://git.example.org/var/.
-You can add as many project roots as you want by adding rewrite rules like the
-third and the fourth.
-
-
-PATH_INFO usage
------------------------
-If you enable PATH_INFO usage in gitweb by putting
-
-   $feature{'pathinfo'}{'default'} = [1];
-
-in your gitweb.conf, it is possible to set up your server so that it
-consumes and produces URLs in the form
-
-http://git.example.com/project.git/shortlog/sometag
-
-by using a configuration such as the following, that assumes that
-/var/www/gitweb is the DocumentRoot of your webserver, and that it
-contains the gitweb.cgi script and complementary static files
-(stylesheet, favicon):
-
-<VirtualHost *:80>
-       ServerAlias git.example.com
-
-       DocumentRoot /var/www/gitweb
-
-       <Directory /var/www/gitweb>
-               Options ExecCGI
-               AddHandler cgi-script cgi
-
-               DirectoryIndex gitweb.cgi
-
-               RewriteEngine On
-               RewriteCond %{REQUEST_FILENAME} !-f
-               RewriteCond %{REQUEST_FILENAME} !-d
-               RewriteRule ^.* /gitweb.cgi/$0 [L,PT]
-       </Directory>
-</VirtualHost>
-
-The rewrite rule guarantees that existing static files will be properly
-served, whereas any other URL will be passed to gitweb as PATH_INFO
-parameter.
-
-Notice that in this case you don't need special settings for
-@stylesheets, $my_uri and $home_link, but you lose "dumb client" access
-to your project .git dirs. A possible workaround for the latter is the
-following: in your project root dir (e.g. /pub/git) have the projects
-named without a .git extension (e.g. /pub/git/project instead of
-/pub/git/project.git) and configure Apache as follows:
-
-<VirtualHost *:80>
-       ServerAlias git.example.com
-
-       DocumentRoot /var/www/gitweb
-
-       AliasMatch ^(/.*?)(\.git)(/.*)?$ /pub/git$1$3
-       <Directory /var/www/gitweb>
-               Options ExecCGI
-               AddHandler cgi-script cgi
-
-               DirectoryIndex gitweb.cgi
-
-               RewriteEngine On
-               RewriteCond %{REQUEST_FILENAME} !-f
-               RewriteCond %{REQUEST_FILENAME} !-d
-               RewriteRule ^.* /gitweb.cgi/$0 [L,PT]
-       </Directory>
-</VirtualHost>
-
-The additional AliasMatch makes it so that
-
-http://git.example.com/project.git
-
-will give raw access to the project's git dir (so that the project can
-be cloned), while
-
-http://git.example.com/project
-
-will provide human-friendly gitweb access.
-
-This solution is not 100% bulletproof, in the sense that if some project
-has a named ref (branch, tag) starting with 'git/', then paths such as
+See also gitweb.conf(5) manpage.
 
-http://git.example.com/project/command/abranch..git/abranch
 
-will fail with a 404 error.
+Web server configuration
+------------------------
+Gitweb can be run as CGI script, as legacy mod_perl application (using
+ModPerl::Registry), and as FastCGI script.  You can find some simple examples
+in "Example web server configuration" section in INSTALL file for gitweb (in
+gitweb/INSTALL).
 
+See "Webserver configuration" and "Advanced web server setup" sections in
+gitweb(1) manpage.
 
 
+AUTHORS
+-------
 Originally written by:
   Kay Sievers <kay.sievers@vrfy.org>
 
index 70a576a626ac67516af7b980c64ec33da6b7c19b..4f0c3bd90c7f90dad1674f50999da534c33c0261 100755 (executable)
@@ -85,6 +85,8 @@ our $home_link_str = "++GITWEB_HOME_LINK_STR++";
 our $site_name = "++GITWEB_SITENAME++"
                  || ($ENV{'SERVER_NAME'} || "Untitled") . " Git";
 
+# html snippet to include in the <head> section of each page
+our $site_html_head_string = "++GITWEB_SITE_HTML_HEAD_STRING++";
 # filename of html text to include at top of each page
 our $site_header = "++GITWEB_SITE_HEADER++";
 # html text to include at home page
@@ -1517,6 +1519,17 @@ sub esc_path {
        return $str;
 }
 
+# Sanitize for use in XHTML + application/xml+xhtm (valid XML 1.0)
+sub sanitize {
+       my $str = shift;
+
+       return undef unless defined $str;
+
+       $str = to_utf8($str);
+       $str =~ s|([[:cntrl:]])|($1 =~ /[\t\n\r]/ ? $1 : quot_cec($1))|eg;
+       return $str;
+}
+
 # Make control characters "printable", using character escape codes (CEC)
 sub quot_cec {
        my $cntrl = shift;
@@ -2875,7 +2888,7 @@ sub filter_forks_from_projects_list {
                $path =~ s/\.git$//;      # forks of 'repo.git' are in 'repo/' directory
                next if ($path =~ m!/$!); # skip non-bare repositories, e.g. 'repo/.git'
                next unless ($path);      # skip '.git' repository: tests, git-instaweb
-               next unless (-d $path);   # containing directory exists
+               next unless (-d "$projectroot/$path"); # containing directory exists
                $pr->{'forks'} = [];      # there can be 0 or more forks of project
 
                # add to trie
@@ -3868,6 +3881,11 @@ EOF
                print "<base href=\"".esc_url($base_url)."\" />\n";
        }
        print_header_links($status);
+
+       if (defined $site_html_head_string) {
+               print to_utf8($site_html_head_string);
+       }
+
        print "</head>\n" .
              "<body>\n";
 
@@ -6484,7 +6502,8 @@ sub git_blob {
                        $nr++;
                        $line = untabify($line);
                        printf qq!<div class="pre"><a id="l%i" href="%s#l%i" class="linenr">%4i</a> %s</div>\n!,
-                              $nr, esc_attr(href(-replay => 1)), $nr, $nr, $syntax ? to_utf8($line) : esc_html($line, -nbsp=>1);
+                              $nr, esc_attr(href(-replay => 1)), $nr, $nr,
+                              $syntax ? sanitize($line) : esc_html($line, -nbsp=>1);
                }
        }
        close $fd
index 93dd2bdd91b2b4b851f4ddc3b806e58bde792d56..fa2596f77c645b87e79aceb242d6074d6b2764ea 100644 (file)
@@ -16,7 +16,7 @@
  * and other reasons to not add 'js=1' param at the end of link
  * @constant
  */
-var jsExceptionsRe = /[;?]js=[01]$/;
+var jsExceptionsRe = /[;?]js=[01](#.*)?$/;
 
 /**
  * Add '?js=1' or ';js=1' to the end of every link in the document
@@ -33,9 +33,9 @@ function fixLinks() {
        var allLinks = document.getElementsByTagName("a") || document.links;
        for (var i = 0, len = allLinks.length; i < len; i++) {
                var link = allLinks[i];
-               if (!jsExceptionsRe.test(link)) { // =~ /[;?]js=[01]$/;
-                       link.href +=
-                               (link.href.indexOf('?') === -1 ? '?' : ';') + 'js=1';
+               if (!jsExceptionsRe.test(link)) {
+                       link.href = link.href.replace(/(#|$)/,
+                               (link.href.indexOf('?') === -1 ? '?' : ';') + 'js=1$1');
                }
        }
 }
diff --git a/hex.c b/hex.c
index bb402fbaa2a04ef0849789fdd814dcbb8773fff5..9ebc050637532765b775cbd8e3b92951a67ea333 100644 (file)
--- a/hex.c
+++ b/hex.c
@@ -39,7 +39,15 @@ int get_sha1_hex(const char *hex, unsigned char *sha1)
 {
        int i;
        for (i = 0; i < 20; i++) {
-               unsigned int val = (hexval(hex[0]) << 4) | hexval(hex[1]);
+               unsigned int val;
+               /*
+                * hex[1]=='\0' is caught when val is checked below,
+                * but if hex[0] is NUL we have to avoid reading
+                * past the end of the string:
+                */
+               if (!hex[0])
+                       return -1;
+               val = (hexval(hex[0]) << 4) | hexval(hex[1]);
                if (val & ~0xff)
                        return -1;
                *sha1++ = val;
index 8c4c5d2224a2493a648e6a34257bc150f2712dd0..69299b7bd2a956266bf581df9c23589a97fca805 100644 (file)
@@ -67,7 +67,7 @@ int main(int argc, const char **argv)
 
        git_config(git_default_config, NULL);
 
-       http_init(NULL);
+       http_init(NULL, url);
        walker = get_http_walker(url);
        walker->get_tree = get_tree;
        walker->get_history = get_history;
index 376331a76fcde24e6828eba0b88fec26d93fe56f..edd553b7f69ed92fde301966e605e7562703718a 100644 (file)
@@ -1606,10 +1606,10 @@ static void fetch_symref(const char *path, char **symref, unsigned char *sha1)
        strbuf_release(&buffer);
 }
 
-static int verify_merge_base(unsigned char *head_sha1, unsigned char *branch_sha1)
+static int verify_merge_base(unsigned char *head_sha1, struct ref *remote)
 {
-       struct commit *head = lookup_commit(head_sha1);
-       struct commit *branch = lookup_commit(branch_sha1);
+       struct commit *head = lookup_commit_or_die(head_sha1, "HEAD");
+       struct commit *branch = lookup_commit_or_die(remote->old_sha1, remote->name);
        struct commit_list *merge_bases = get_merge_bases(head, branch, 1);
 
        return (merge_bases && !merge_bases->next && merge_bases->item == branch);
@@ -1680,7 +1680,7 @@ static int delete_remote_branch(const char *pattern, int force)
                        return error("Remote branch %s resolves to object %s\nwhich does not exist locally, perhaps you need to fetch?", remote_ref->name, sha1_to_hex(remote_ref->old_sha1));
 
                /* Remote branch must be an ancestor of remote HEAD */
-               if (!verify_merge_base(head_sha1, remote_ref->old_sha1)) {
+               if (!verify_merge_base(head_sha1, remote_ref)) {
                        return error("The branch '%s' is not an ancestor "
                                     "of your current HEAD.\n"
                                     "If you are sure you want to delete it,"
@@ -1747,7 +1747,6 @@ int main(int argc, char **argv)
        int i;
        int new_refs;
        struct ref *ref, *local_refs;
-       struct remote *remote;
 
        git_extract_argv0_path(argv[0]);
 
@@ -1821,14 +1820,7 @@ int main(int argc, char **argv)
 
        memset(remote_dir_exists, -1, 256);
 
-       /*
-        * Create a minimum remote by hand to give to http_init(),
-        * primarily to allow it to look at the URL.
-        */
-       remote = xcalloc(sizeof(*remote), 1);
-       ALLOC_GROW(remote->url, remote->url_nr + 1, remote->url_alloc);
-       remote->url[remote->url_nr++] = repo->url;
-       http_init(remote);
+       http_init(NULL, repo->url);
 
 #ifdef USE_CURL_MULTI
        is_running_queue = 0;
@@ -1877,8 +1869,8 @@ int main(int argc, char **argv)
        }
 
        /* match them up */
-       if (match_refs(local_refs, &remote_refs,
-                      nr_refspec, (const char **) refspec, push_all)) {
+       if (match_push_refs(local_refs, &remote_refs,
+                           nr_refspec, (const char **) refspec, push_all)) {
                rc = -1;
                goto cleanup;
        }
diff --git a/http.c b/http.c
index a59cac45d7a6c159e740cab9b20bb63ab91f7c48..44fcc4d178fcedaa87f1917608dd32a65c24c98a 100644 (file)
--- a/http.c
+++ b/http.c
@@ -4,7 +4,6 @@
 #include "run-command.h"
 #include "url.h"
 
-int data_received;
 int active_requests;
 int http_is_verbose;
 size_t http_post_buffer = 16 * LARGE_PACKET_MAX;
@@ -42,7 +41,7 @@ static long curl_low_speed_time = -1;
 static int curl_ftp_no_epsv;
 static const char *curl_http_proxy;
 static const char *curl_cookie_file;
-static char *user_name, *user_pass;
+static char *user_name, *user_pass, *description;
 static const char *user_agent;
 
 #if LIBCURL_VERSION_NUM >= 0x071700
@@ -99,13 +98,11 @@ size_t fwrite_buffer(char *ptr, size_t eltsize, size_t nmemb, void *buffer_)
        struct strbuf *buffer = buffer_;
 
        strbuf_add(buffer, ptr, size);
-       data_received++;
        return size;
 }
 
 size_t fwrite_null(char *ptr, size_t eltsize, size_t nmemb, void *strbuf)
 {
-       data_received++;
        return eltsize * nmemb;
 }
 
@@ -139,6 +136,27 @@ static void process_curl_messages(void)
 }
 #endif
 
+static char *git_getpass_with_description(const char *what, const char *desc)
+{
+       struct strbuf prompt = STRBUF_INIT;
+       char *r;
+
+       if (desc)
+               strbuf_addf(&prompt, "%s for '%s': ", what, desc);
+       else
+               strbuf_addf(&prompt, "%s: ", what);
+       /*
+        * NEEDSWORK: for usernames, we should do something less magical that
+        * actually echoes the characters. However, we need to read from
+        * /dev/tty and not stdio, which is not portable (but getpass will do
+        * it for us). http.c uses the same workaround.
+        */
+       r = git_getpass(prompt.buf);
+
+       strbuf_release(&prompt);
+       return xstrdup(r);
+}
+
 static int http_options(const char *var, const char *value, void *cb)
 {
        if (!strcmp("http.sslverify", var)) {
@@ -214,7 +232,7 @@ static void init_curl_http_auth(CURL *result)
        if (user_name) {
                struct strbuf up = STRBUF_INIT;
                if (!user_pass)
-                       user_pass = xstrdup(git_getpass("Password: "));
+                       user_pass = xstrdup(git_getpass_with_description("Password", description));
                strbuf_addf(&up, "%s:%s", user_name, user_pass);
                curl_easy_setopt(result, CURLOPT_USERPWD,
                                 strbuf_detach(&up, NULL));
@@ -229,7 +247,7 @@ static int has_cert_password(void)
                return 0;
        /* Only prompt the user once. */
        ssl_cert_password_required = -1;
-       ssl_cert_password = git_getpass("Certificate Password: ");
+       ssl_cert_password = git_getpass_with_description("Certificate Password", description);
        if (ssl_cert_password != NULL) {
                ssl_cert_password = xstrdup(ssl_cert_password);
                return 1;
@@ -258,8 +276,6 @@ static CURL *get_curl_handle(void)
        curl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
 #endif
 
-       init_curl_http_auth(result);
-
        if (ssl_cert != NULL)
                curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert);
        if (has_cert_password())
@@ -307,8 +323,7 @@ static CURL *get_curl_handle(void)
 
 static void http_auth_init(const char *url)
 {
-       char *at, *colon, *cp, *slash, *decoded;
-       int len;
+       const char *at, *colon, *cp, *slash, *host;
 
        cp = strstr(url, "://");
        if (!cp)
@@ -324,34 +339,22 @@ static void http_auth_init(const char *url)
        at = strchr(cp, '@');
        colon = strchr(cp, ':');
        slash = strchrnul(cp, '/');
-       if (!at || slash <= at)
-               return; /* No credentials */
-       if (!colon || at <= colon) {
+       if (!at || slash <= at) {
+               /* No credentials, but we may have to ask for some later */
+               host = cp;
+       }
+       else if (!colon || at <= colon) {
                /* Only username */
-               len = at - cp;
-               user_name = xmalloc(len + 1);
-               memcpy(user_name, cp, len);
-               user_name[len] = '\0';
-               decoded = url_decode(user_name);
-               free(user_name);
-               user_name = decoded;
+               user_name = url_decode_mem(cp, at - cp);
                user_pass = NULL;
+               host = at + 1;
        } else {
-               len = colon - cp;
-               user_name = xmalloc(len + 1);
-               memcpy(user_name, cp, len);
-               user_name[len] = '\0';
-               decoded = url_decode(user_name);
-               free(user_name);
-               user_name = decoded;
-               len = at - (colon + 1);
-               user_pass = xmalloc(len + 1);
-               memcpy(user_pass, colon + 1, len);
-               user_pass[len] = '\0';
-               decoded = url_decode(user_pass);
-               free(user_pass);
-               user_pass = decoded;
+               user_name = url_decode_mem(cp, colon - cp);
+               user_pass = url_decode_mem(colon + 1, at - (colon + 1));
+               host = at + 1;
        }
+
+       description = url_decode_mem(host, slash - host);
 }
 
 static void set_from_env(const char **var, const char *envname)
@@ -361,7 +364,7 @@ static void set_from_env(const char **var, const char *envname)
                *var = val;
 }
 
-void http_init(struct remote *remote)
+void http_init(struct remote *remote, const char *url)
 {
        char *low_speed_limit;
        char *low_speed_time;
@@ -425,11 +428,11 @@ void http_init(struct remote *remote)
        if (getenv("GIT_CURL_FTP_NO_EPSV"))
                curl_ftp_no_epsv = 1;
 
-       if (remote && remote->url && remote->url[0]) {
-               http_auth_init(remote->url[0]);
+       if (url) {
+               http_auth_init(url);
                if (!ssl_cert_password_required &&
                    getenv("GIT_SSL_CERT_PASSWORD_PROTECTED") &&
-                   !prefixcmp(remote->url[0], "https://"))
+                   !prefixcmp(url, "https://"))
                        ssl_cert_password_required = 1;
        }
 
@@ -530,7 +533,6 @@ struct active_request_slot *get_active_slot(void)
 
        active_requests++;
        slot->in_use = 1;
-       slot->local = NULL;
        slot->results = NULL;
        slot->finished = NULL;
        slot->callback_data = NULL;
@@ -634,8 +636,6 @@ void step_active_slots(void)
 void run_active_slot(struct active_request_slot *slot)
 {
 #ifdef USE_CURL_MULTI
-       long last_pos = 0;
-       long current_pos;
        fd_set readfds;
        fd_set writefds;
        fd_set excfds;
@@ -645,25 +645,33 @@ void run_active_slot(struct active_request_slot *slot)
 
        slot->finished = &finished;
        while (!finished) {
-               data_received = 0;
                step_active_slots();
 
-               if (!data_received && slot->local != NULL) {
-                       current_pos = ftell(slot->local);
-                       if (current_pos > last_pos)
-                               data_received++;
-                       last_pos = current_pos;
-               }
+               if (slot->in_use) {
+#if LIBCURL_VERSION_NUM >= 0x070f04
+                       long curl_timeout;
+                       curl_multi_timeout(curlm, &curl_timeout);
+                       if (curl_timeout == 0) {
+                               continue;
+                       } else if (curl_timeout == -1) {
+                               select_timeout.tv_sec  = 0;
+                               select_timeout.tv_usec = 50000;
+                       } else {
+                               select_timeout.tv_sec  =  curl_timeout / 1000;
+                               select_timeout.tv_usec = (curl_timeout % 1000) * 1000;
+                       }
+#else
+                       select_timeout.tv_sec  = 0;
+                       select_timeout.tv_usec = 50000;
+#endif
 
-               if (slot->in_use && !data_received) {
-                       max_fd = 0;
+                       max_fd = -1;
                        FD_ZERO(&readfds);
                        FD_ZERO(&writefds);
                        FD_ZERO(&excfds);
-                       select_timeout.tv_sec = 0;
-                       select_timeout.tv_usec = 50000;
-                       select(max_fd, &readfds, &writefds,
-                              &excfds, &select_timeout);
+                       curl_multi_fdset(curlm, &readfds, &writefds, &excfds, &max_fd);
+
+                       select(max_fd+1, &readfds, &writefds, &excfds, &select_timeout);
                }
        }
 #else
@@ -741,14 +749,6 @@ static inline int needs_quote(int ch)
        return 1;
 }
 
-static inline int hex(int v)
-{
-       if (v < 10)
-               return '0' + v;
-       else
-               return 'A' + v - 10;
-}
-
 static char *quote_ref_url(const char *base, const char *ref)
 {
        struct strbuf buf = STRBUF_INIT;
@@ -816,7 +816,6 @@ static int http_request(const char *url, void *result, int target, int options)
                                headers = curl_slist_append(headers, buf.buf);
                                strbuf_reset(&buf);
                        }
-                       slot->local = result;
                } else
                        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION,
                                         fwrite_buffer);
@@ -838,7 +837,7 @@ static int http_request(const char *url, void *result, int target, int options)
                else if (missing_target(&results))
                        ret = HTTP_MISSING_TARGET;
                else if (results.http_code == 401) {
-                       if (user_name) {
+                       if (user_name && user_pass) {
                                ret = HTTP_NOAUTH;
                        } else {
                                /*
@@ -847,31 +846,41 @@ static int http_request(const char *url, void *result, int target, int options)
                                 * but that is non-portable.  Using git_getpass() can at least be stubbed
                                 * on other platforms with a different implementation if/when necessary.
                                 */
-                               user_name = xstrdup(git_getpass("Username: "));
+                               if (!user_name)
+                                       user_name = xstrdup(git_getpass_with_description("Username", description));
                                init_curl_http_auth(slot->curl);
                                ret = HTTP_REAUTH;
                        }
-               } else
+               } else {
+                       if (!curl_errorstr[0])
+                               strlcpy(curl_errorstr,
+                                       curl_easy_strerror(results.curl_result),
+                                       sizeof(curl_errorstr));
                        ret = HTTP_ERROR;
+               }
        } else {
                error("Unable to start HTTP request for %s", url);
                ret = HTTP_START_FAILED;
        }
 
-       slot->local = NULL;
        curl_slist_free_all(headers);
        strbuf_release(&buf);
 
        return ret;
 }
 
+static int http_request_reauth(const char *url, void *result, int target,
+                              int options)
+{
+       int ret = http_request(url, result, target, options);
+       if (ret != HTTP_REAUTH)
+               return ret;
+       return http_request(url, result, target, options);
+}
+
 int http_get_strbuf(const char *url, struct strbuf *result, int options)
 {
-       int http_ret = http_request(url, result, HTTP_REQUEST_STRBUF, options);
-       if (http_ret == HTTP_REAUTH) {
-               http_ret = http_request(url, result, HTTP_REQUEST_STRBUF, options);
-       }
-       return http_ret;
+       return http_request_reauth(url, result, HTTP_REQUEST_STRBUF, options);
 }
 
 /*
@@ -894,7 +903,7 @@ static int http_get_file(const char *url, const char *filename, int options)
                goto cleanup;
        }
 
-       ret = http_request(url, result, HTTP_REQUEST_FILE, options);
+       ret = http_request_reauth(url, result, HTTP_REQUEST_FILE, options);
        fclose(result);
 
        if ((ret == HTTP_OK) && move_temp_to_file(tmpfile.buf, filename))
@@ -908,7 +917,7 @@ int http_error(const char *url, int ret)
 {
        /* http_request has already handled HTTP_START_FAILED. */
        if (ret != HTTP_START_FAILED)
-               error("%s while accessing %s\n", curl_errorstr, url);
+               error("%s while accessing %s", curl_errorstr, url);
 
        return ret;
 }
@@ -1048,7 +1057,6 @@ void release_http_pack_request(struct http_pack_request *preq)
        if (preq->packfile != NULL) {
                fclose(preq->packfile);
                preq->packfile = NULL;
-               preq->slot->local = NULL;
        }
        if (preq->range_header != NULL) {
                curl_slist_free_all(preq->range_header);
@@ -1070,7 +1078,6 @@ int finish_http_pack_request(struct http_pack_request *preq)
 
        fclose(preq->packfile);
        preq->packfile = NULL;
-       preq->slot->local = NULL;
 
        lst = preq->lst;
        while (*lst != p)
@@ -1139,7 +1146,6 @@ struct http_pack_request *new_http_pack_request(
        }
 
        preq->slot = get_active_slot();
-       preq->slot->local = preq->packfile;
        curl_easy_setopt(preq->slot->curl, CURLOPT_FILE, preq->packfile);
        curl_easy_setopt(preq->slot->curl, CURLOPT_WRITEFUNCTION, fwrite);
        curl_easy_setopt(preq->slot->curl, CURLOPT_URL, preq->url);
@@ -1196,7 +1202,6 @@ static size_t fwrite_sha1_file(char *ptr, size_t eltsize, size_t nmemb,
                git_SHA1_Update(&freq->c, expn,
                                sizeof(expn) - freq->stream.avail_out);
        } while (freq->stream.avail_in && freq->zret == Z_OK);
-       data_received++;
        return size;
 }
 
diff --git a/http.h b/http.h
index 0bf8592dc45209c9be5b8ddac3a53f6fc11e29be..ee1606942a9716b45da937873949220ab7f8092b 100644 (file)
--- a/http.h
+++ b/http.h
@@ -49,7 +49,6 @@ struct slot_results {
 
 struct active_request_slot {
        CURL *curl;
-       FILE *local;
        int in_use;
        CURLcode curl_result;
        long http_code;
@@ -86,10 +85,9 @@ extern void add_fill_function(void *data, int (*fill)(void *));
 extern void step_active_slots(void);
 #endif
 
-extern void http_init(struct remote *remote);
+extern void http_init(struct remote *remote, const char *url);
 extern void http_cleanup(void);
 
-extern int data_received;
 extern int active_requests;
 extern int http_is_verbose;
 extern size_t http_post_buffer;
diff --git a/ident.c b/ident.c
index 35a6f264737e700ddd45714a0e966422a0d756c7..f619619b82379c2d205c7d7ea101049e373ab90a 100644 (file)
--- a/ident.c
+++ b/ident.c
@@ -50,6 +50,54 @@ static void copy_gecos(const struct passwd *w, char *name, size_t sz)
 
 }
 
+static int add_mailname_host(char *buf, size_t len)
+{
+       FILE *mailname;
+
+       mailname = fopen("/etc/mailname", "r");
+       if (!mailname) {
+               if (errno != ENOENT)
+                       warning("cannot open /etc/mailname: %s",
+                               strerror(errno));
+               return -1;
+       }
+       if (!fgets(buf, len, mailname)) {
+               if (ferror(mailname))
+                       warning("cannot read /etc/mailname: %s",
+                               strerror(errno));
+               fclose(mailname);
+               return -1;
+       }
+       /* success! */
+       fclose(mailname);
+       return 0;
+}
+
+static void add_domainname(char *buf, size_t len)
+{
+       struct hostent *he;
+       size_t namelen;
+       const char *domainname;
+
+       if (gethostname(buf, len)) {
+               warning("cannot get host name: %s", strerror(errno));
+               strlcpy(buf, "(none)", len);
+               return;
+       }
+       namelen = strlen(buf);
+       if (memchr(buf, '.', namelen))
+               return;
+
+       he = gethostbyname(buf);
+       buf[namelen++] = '.';
+       buf += namelen;
+       len -= namelen;
+       if (he && (domainname = strchr(he->h_name, '.')))
+               strlcpy(buf, domainname + 1, len);
+       else
+               strlcpy(buf, "(none)", len);
+}
+
 static void copy_email(const struct passwd *pw)
 {
        /*
@@ -61,35 +109,29 @@ static void copy_email(const struct passwd *pw)
                die("Your sysadmin must hate you!");
        memcpy(git_default_email, pw->pw_name, len);
        git_default_email[len++] = '@';
-       gethostname(git_default_email + len, sizeof(git_default_email) - len);
-       if (!strchr(git_default_email+len, '.')) {
-               struct hostent *he = gethostbyname(git_default_email + len);
-               char *domainname;
-
-               len = strlen(git_default_email);
-               git_default_email[len++] = '.';
-               if (he && (domainname = strchr(he->h_name, '.')))
-                       strlcpy(git_default_email + len, domainname + 1,
-                               sizeof(git_default_email) - len);
-               else
-                       strlcpy(git_default_email + len, "(none)",
-                               sizeof(git_default_email) - len);
-       }
+
+       if (!add_mailname_host(git_default_email + len,
+                               sizeof(git_default_email) - len))
+               return; /* read from "/etc/mailname" (Debian) */
+       add_domainname(git_default_email + len,
+                       sizeof(git_default_email) - len);
 }
 
-static void setup_ident(void)
+static void setup_ident(const char **name, const char **emailp)
 {
        struct passwd *pw = NULL;
 
        /* Get the name ("gecos") */
-       if (!git_default_name[0]) {
+       if (!*name && !git_default_name[0]) {
                pw = getpwuid(getuid());
                if (!pw)
                        die("You don't exist. Go away!");
                copy_gecos(pw, git_default_name, sizeof(git_default_name));
        }
+       if (!*name)
+               *name = git_default_name;
 
-       if (!git_default_email[0]) {
+       if (!*emailp && !git_default_email[0]) {
                const char *email = getenv("EMAIL");
 
                if (email && email[0]) {
@@ -104,6 +146,8 @@ static void setup_ident(void)
                        copy_email(pw);
                }
        }
+       if (!*emailp)
+               *emailp = git_default_email;
 
        /* And set the default date */
        if (!git_default_date[0])
@@ -199,11 +243,7 @@ const char *fmt_ident(const char *name, const char *email,
        int warn_on_no_name = (flag & IDENT_WARN_ON_NO_NAME);
        int name_addr_only = (flag & IDENT_NO_DATE);
 
-       setup_ident();
-       if (!name)
-               name = git_default_name;
-       if (!email)
-               email = git_default_email;
+       setup_ident(&name, &email);
 
        if (!*name) {
                struct passwd *pw;
diff --git a/kwset.c b/kwset.c
index 956ae729501ed09b947eb33e161551beed2a1d95..51b2ab6c7e61413851a958f7a4009a0194744b68 100644 (file)
--- a/kwset.c
+++ b/kwset.c
@@ -674,7 +674,7 @@ cwexec (kwset_t kws, char const *text, size_t len, struct kwsmatch *kwsmatch)
      copy of the preceding main search loops. */
   if (lim - mch > kwset->maxd)
     lim = mch + kwset->maxd;
-  lmch = 0;
+  lmch = NULL;
   d = 1;
   while (lim - end >= d)
     {
index 39d80c01753527e45716ec762beeb891dfc7d28d..3dd4a960190a1b0016b26dec9187692111b73e3f 100644 (file)
@@ -71,7 +71,8 @@ static void process_tree(struct rev_info *revs,
        struct tree_desc desc;
        struct name_entry entry;
        struct name_path me;
-       int match = revs->diffopt.pathspec.nr == 0 ? 2 : 0;
+       enum interesting match = revs->diffopt.pathspec.nr == 0 ?
+               all_entries_interesting: entry_not_interesting;
        int baselen = base->len;
 
        if (!revs->tree_objects)
@@ -97,12 +98,12 @@ static void process_tree(struct rev_info *revs,
        init_tree_desc(&desc, tree->buffer, tree->size);
 
        while (tree_entry(&desc, &entry)) {
-               if (match != 2) {
+               if (match != all_entries_interesting) {
                        match = tree_entry_interesting(&entry, base, 0,
                                                       &revs->diffopt.pathspec);
-                       if (match < 0)
+                       if (match == all_entries_not_interesting)
                                break;
-                       if (match == 0)
+                       if (match == entry_not_interesting)
                                continue;
                }
 
index 24c295ea1dc91180b9685299f48812593162bdfe..e7694a3a4ca2e6de7fe8f3b5458eb2b65591373a 100644 (file)
@@ -165,6 +165,14 @@ static void show_parents(struct commit *commit, int abbrev)
        }
 }
 
+static void show_children(struct rev_info *opt, struct commit *commit, int abbrev)
+{
+       struct commit_list *p = lookup_decoration(&opt->children, &commit->object);
+       for ( ; p; p = p->next) {
+               printf(" %s", find_unique_abbrev(p->item->object.sha1, abbrev));
+       }
+}
+
 void show_decorations(struct rev_info *opt, struct commit *commit)
 {
        const char *prefix;
@@ -414,6 +422,8 @@ void show_log(struct rev_info *opt)
                fputs(find_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
                if (opt->print_parents)
                        show_parents(commit, abbrev_commit);
+               if (opt->children.name)
+                       show_children(opt, commit, abbrev_commit);
                show_decorations(opt, commit);
                if (opt->graph && !graph_is_commit_finished(opt->graph)) {
                        putchar('\n');
@@ -473,6 +483,8 @@ void show_log(struct rev_info *opt)
                      stdout);
                if (opt->print_parents)
                        show_parents(commit, abbrev_commit);
+               if (opt->children.name)
+                       show_children(opt, commit, abbrev_commit);
                if (parent)
                        printf(" (from %s)",
                               find_unique_abbrev(parent->object.sha1,
index 02fcfde0b0b786af5229559586cf8ff5758429bc..8c3196c7d76ff90e90a9fb4ff569aff6a429861a 100644 (file)
--- a/mailmap.c
+++ b/mailmap.c
@@ -70,8 +70,7 @@ static void add_mapping(struct string_list *map,
        } else {
                /* create mailmap entry */
                struct string_list_item *item = string_list_insert_at_index(map, index, old_email);
-               item->util = xmalloc(sizeof(struct mailmap_entry));
-               memset(item->util, 0, sizeof(struct mailmap_entry));
+               item->util = xcalloc(1, sizeof(struct mailmap_entry));
                ((struct mailmap_entry *)item->util)->namemap.strdup_strings = 1;
        }
        me = (struct mailmap_entry *)map->items[index].util;
@@ -88,7 +87,7 @@ static void add_mapping(struct string_list *map,
                        me->email = xstrdup(new_email);
                }
        } else {
-               struct mailmap_info *mi = xmalloc(sizeof(struct mailmap_info));
+               struct mailmap_info *mi = xcalloc(1, sizeof(struct mailmap_info));
                debug_mm("mailmap: adding (complex) entry for %s at index %d\n", old_email, index);
                if (new_name)
                        mi->name = xstrdup(new_name);
index 3efc04e04fd754de85fbab1dbafa7bffb2fa2854..cc664c39b66b0bb499dec13a22880a6096423fa6 100644 (file)
@@ -390,7 +390,7 @@ static void record_df_conflict_files(struct merge_options *o,
                                     struct string_list *entries)
 {
        /* If there is a D/F conflict and the file for such a conflict
-        * currently exist in the working copy, we want to allow it to be
+        * currently exist in the working tree, we want to allow it to be
         * removed to make room for the corresponding directory if needed.
         * The files underneath the directories of such D/F conflicts will
         * be processed before the corresponding file involved in the D/F
@@ -946,8 +946,10 @@ static struct merge_file_info merge_file_1(struct merge_options *o,
                        free(result_buf.ptr);
                        result.clean = (merge_status == 0);
                } else if (S_ISGITLINK(a->mode)) {
-                       result.clean = merge_submodule(result.sha, one->path, one->sha1,
-                                                      a->sha1, b->sha1);
+                       result.clean = merge_submodule(result.sha,
+                                                      one->path, one->sha1,
+                                                      a->sha1, b->sha1,
+                                                      !o->call_depth);
                } else if (S_ISLNK(a->mode)) {
                        hashcpy(result.sha, a->sha1);
 
index 27b3dd48b840139acd65ea742e04221c6400a485..b6319d206e2333e42469486604d4635e846e5faa 100644 (file)
@@ -16,5 +16,10 @@ merge_cmd () {
 }
 
 translate_merge_tool_path() {
-       echo bcompare
+       if type bcomp >/dev/null 2>/dev/null
+       then
+               echo bcomp
+       else
+               echo bcompare
+       fi
 }
index c6b6a3fe4cd94e48893b172c17b6e7df3bfa36f8..d8d25c23e99dddd9bd0bf83d73f2ae136d7307b5 100644 (file)
@@ -57,12 +57,10 @@ static void hash_index_entry_directories(struct index_state *istate, struct cach
                if (*ptr == '/') {
                        ++ptr;
                        hash = hash_name(ce->name, ptr - ce->name);
-                       if (!lookup_hash(hash, &istate->name_hash)) {
-                               pos = insert_hash(hash, ce, &istate->name_hash);
-                               if (pos) {
-                                       ce->next = *pos;
-                                       *pos = ce;
-                               }
+                       pos = insert_hash(hash, ce, &istate->name_hash);
+                       if (pos) {
+                               ce->dir_next = *pos;
+                               *pos = ce;
                        }
                }
        }
@@ -76,7 +74,7 @@ static void hash_index_entry(struct index_state *istate, struct cache_entry *ce)
        if (ce->ce_flags & CE_HASHED)
                return;
        ce->ce_flags |= CE_HASHED;
-       ce->next = NULL;
+       ce->next = ce->dir_next = NULL;
        hash = hash_name(ce->name, ce_namelen(ce));
        pos = insert_hash(hash, ce, &istate->name_hash);
        if (pos) {
@@ -166,7 +164,10 @@ struct cache_entry *index_name_exists(struct index_state *istate, const char *na
                        if (same_name(ce, name, namelen, icase))
                                return ce;
                }
-               ce = ce->next;
+               if (icase && name[namelen - 1] == '/')
+                       ce = ce->dir_next;
+               else
+                       ce = ce->next;
        }
 
        /*
index e1aaf43b438d0cfb6b7b0c723060a662a915bcea..ce10aacf2978024eda0234482964577e9235f2d2 100644 (file)
@@ -21,14 +21,6 @@ void init_notes_merge_options(struct notes_merge_options *o)
        o->verbosity = NOTES_MERGE_VERBOSITY_DEFAULT;
 }
 
-#define OUTPUT(o, v, ...) \
-       do { \
-               if ((o)->verbosity >= (v)) { \
-                       printf(__VA_ARGS__); \
-                       puts(""); \
-               } \
-       } while (0)
-
 static int path_to_sha1(const char *path, unsigned char *sha1)
 {
        char hex_sha1[40];
@@ -392,21 +384,26 @@ static int merge_one_change_manual(struct notes_merge_options *o,
 
        strbuf_addf(&(o->commit_msg), "\t%s\n", sha1_to_hex(p->obj));
 
-       OUTPUT(o, 2, "Auto-merging notes for %s", sha1_to_hex(p->obj));
+       if (o->verbosity >= 2)
+               printf("Auto-merging notes for %s\n", sha1_to_hex(p->obj));
        check_notes_merge_worktree(o);
        if (is_null_sha1(p->local)) {
                /* D/F conflict, checkout p->remote */
                assert(!is_null_sha1(p->remote));
-               OUTPUT(o, 1, "CONFLICT (delete/modify): Notes for object %s "
-                      "deleted in %s and modified in %s. Version from %s "
-                      "left in tree.", sha1_to_hex(p->obj), lref, rref, rref);
+               if (o->verbosity >= 1)
+                       printf("CONFLICT (delete/modify): Notes for object %s "
+                               "deleted in %s and modified in %s. Version from %s "
+                               "left in tree.\n",
+                               sha1_to_hex(p->obj), lref, rref, rref);
                write_note_to_worktree(p->obj, p->remote);
        } else if (is_null_sha1(p->remote)) {
                /* D/F conflict, checkout p->local */
                assert(!is_null_sha1(p->local));
-               OUTPUT(o, 1, "CONFLICT (delete/modify): Notes for object %s "
-                      "deleted in %s and modified in %s. Version from %s "
-                      "left in tree.", sha1_to_hex(p->obj), rref, lref, lref);
+               if (o->verbosity >= 1)
+                       printf("CONFLICT (delete/modify): Notes for object %s "
+                               "deleted in %s and modified in %s. Version from %s "
+                               "left in tree.\n",
+                               sha1_to_hex(p->obj), rref, lref, lref);
                write_note_to_worktree(p->obj, p->local);
        } else {
                /* "regular" conflict, checkout result of ll_merge() */
@@ -415,8 +412,9 @@ static int merge_one_change_manual(struct notes_merge_options *o,
                        reason = "add/add";
                assert(!is_null_sha1(p->local));
                assert(!is_null_sha1(p->remote));
-               OUTPUT(o, 1, "CONFLICT (%s): Merge conflict in notes for "
-                      "object %s", reason, sha1_to_hex(p->obj));
+               if (o->verbosity >= 1)
+                       printf("CONFLICT (%s): Merge conflict in notes for "
+                               "object %s\n", reason, sha1_to_hex(p->obj));
                ll_merge_in_worktree(o, p);
        }
 
@@ -438,24 +436,30 @@ static int merge_one_change(struct notes_merge_options *o,
        case NOTES_MERGE_RESOLVE_MANUAL:
                return merge_one_change_manual(o, p, t);
        case NOTES_MERGE_RESOLVE_OURS:
-               OUTPUT(o, 2, "Using local notes for %s", sha1_to_hex(p->obj));
+               if (o->verbosity >= 2)
+                       printf("Using local notes for %s\n",
+                                               sha1_to_hex(p->obj));
                /* nothing to do */
                return 0;
        case NOTES_MERGE_RESOLVE_THEIRS:
-               OUTPUT(o, 2, "Using remote notes for %s", sha1_to_hex(p->obj));
+               if (o->verbosity >= 2)
+                       printf("Using remote notes for %s\n",
+                                               sha1_to_hex(p->obj));
                if (add_note(t, p->obj, p->remote, combine_notes_overwrite))
                        die("BUG: combine_notes_overwrite failed");
                return 0;
        case NOTES_MERGE_RESOLVE_UNION:
-               OUTPUT(o, 2, "Concatenating local and remote notes for %s",
-                      sha1_to_hex(p->obj));
+               if (o->verbosity >= 2)
+                       printf("Concatenating local and remote notes for %s\n",
+                                                       sha1_to_hex(p->obj));
                if (add_note(t, p->obj, p->remote, combine_notes_concatenate))
                        die("failed to concatenate notes "
                            "(combine_notes_concatenate)");
                return 0;
        case NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ:
-               OUTPUT(o, 2, "Concatenating unique lines in local and remote "
-                      "notes for %s", sha1_to_hex(p->obj));
+               if (o->verbosity >= 2)
+                       printf("Concatenating unique lines in local and remote "
+                               "notes for %s\n", sha1_to_hex(p->obj));
                if (add_note(t, p->obj, p->remote, combine_notes_cat_sort_uniq))
                        die("failed to concatenate notes "
                            "(combine_notes_cat_sort_uniq)");
@@ -518,8 +522,9 @@ static int merge_from_diffs(struct notes_merge_options *o,
        conflicts = merge_changes(o, changes, &num_changes, t);
        free(changes);
 
-       OUTPUT(o, 4, "Merge result: %i unmerged notes and a %s notes tree",
-              conflicts, t->dirty ? "dirty" : "clean");
+       if (o->verbosity >= 4)
+               printf("Merge result: %i unmerged notes and a %s notes tree\n",
+                       conflicts, t->dirty ? "dirty" : "clean");
 
        return conflicts ? -1 : 1;
 }
@@ -570,7 +575,8 @@ int notes_merge(struct notes_merge_options *o,
        /* Dereference o->local_ref into local_sha1 */
        if (!resolve_ref(o->local_ref, local_sha1, 0, NULL))
                die("Failed to resolve local notes ref '%s'", o->local_ref);
-       else if (!check_ref_format(o->local_ref) && is_null_sha1(local_sha1))
+       else if (!check_refname_format(o->local_ref, 0) &&
+               is_null_sha1(local_sha1))
                local = NULL; /* local_sha1 == null_sha1 indicates unborn ref */
        else if (!(local = lookup_commit_reference(local_sha1)))
                die("Could not parse local commit %s (%s)",
@@ -583,7 +589,7 @@ int notes_merge(struct notes_merge_options *o,
                 * Failed to get remote_sha1. If o->remote_ref looks like an
                 * unborn ref, perform the merge using an empty notes tree.
                 */
-               if (!check_ref_format(o->remote_ref)) {
+               if (!check_refname_format(o->remote_ref, 0)) {
                        hashclr(remote_sha1);
                        remote = NULL;
                } else {
@@ -616,33 +622,40 @@ int notes_merge(struct notes_merge_options *o,
        if (!bases) {
                base_sha1 = null_sha1;
                base_tree_sha1 = EMPTY_TREE_SHA1_BIN;
-               OUTPUT(o, 4, "No merge base found; doing history-less merge");
+               if (o->verbosity >= 4)
+                       printf("No merge base found; doing history-less merge\n");
        } else if (!bases->next) {
                base_sha1 = bases->item->object.sha1;
                base_tree_sha1 = bases->item->tree->object.sha1;
-               OUTPUT(o, 4, "One merge base found (%.7s)",
-                      sha1_to_hex(base_sha1));
+               if (o->verbosity >= 4)
+                       printf("One merge base found (%.7s)\n",
+                               sha1_to_hex(base_sha1));
        } else {
                /* TODO: How to handle multiple merge-bases? */
                base_sha1 = bases->item->object.sha1;
                base_tree_sha1 = bases->item->tree->object.sha1;
-               OUTPUT(o, 3, "Multiple merge bases found. Using the first "
-                      "(%.7s)", sha1_to_hex(base_sha1));
+               if (o->verbosity >= 3)
+                       printf("Multiple merge bases found. Using the first "
+                               "(%.7s)\n", sha1_to_hex(base_sha1));
        }
 
-       OUTPUT(o, 4, "Merging remote commit %.7s into local commit %.7s with "
-              "merge-base %.7s", sha1_to_hex(remote->object.sha1),
-              sha1_to_hex(local->object.sha1), sha1_to_hex(base_sha1));
+       if (o->verbosity >= 4)
+               printf("Merging remote commit %.7s into local commit %.7s with "
+                       "merge-base %.7s\n", sha1_to_hex(remote->object.sha1),
+                       sha1_to_hex(local->object.sha1),
+                       sha1_to_hex(base_sha1));
 
        if (!hashcmp(remote->object.sha1, base_sha1)) {
                /* Already merged; result == local commit */
-               OUTPUT(o, 2, "Already up-to-date!");
+               if (o->verbosity >= 2)
+                       printf("Already up-to-date!\n");
                hashcpy(result_sha1, local->object.sha1);
                goto found_result;
        }
        if (!hashcmp(local->object.sha1, base_sha1)) {
                /* Fast-forward; result == remote commit */
-               OUTPUT(o, 2, "Fast-forward");
+               if (o->verbosity >= 2)
+                       printf("Fast-forward\n");
                hashcpy(result_sha1, remote->object.sha1);
                goto found_result;
        }
@@ -680,12 +693,13 @@ int notes_merge_commit(struct notes_merge_options *o,
         * Finally store the new commit object SHA1 into 'result_sha1'.
         */
        struct dir_struct dir;
-       const char *path = git_path(NOTES_MERGE_WORKTREE "/");
+       char *path = xstrdup(git_path(NOTES_MERGE_WORKTREE "/"));
        int path_len = strlen(path), i;
        const char *msg = strstr(partial_commit->buffer, "\n\n");
 
-       OUTPUT(o, 3, "Committing notes in notes merge worktree at %.*s",
-              path_len - 1, path);
+       if (o->verbosity >= 3)
+               printf("Committing notes in notes merge worktree at %.*s\n",
+                       path_len - 1, path);
 
        if (!msg || msg[2] == '\0')
                die("partial notes commit has empty message");
@@ -700,7 +714,9 @@ int notes_merge_commit(struct notes_merge_options *o,
                unsigned char obj_sha1[20], blob_sha1[20];
 
                if (ent->len - path_len != 40 || get_sha1_hex(relpath, obj_sha1)) {
-                       OUTPUT(o, 3, "Skipping non-SHA1 entry '%s'", ent->name);
+                       if (o->verbosity >= 3)
+                               printf("Skipping non-SHA1 entry '%s'\n",
+                                                               ent->name);
                        continue;
                }
 
@@ -712,14 +728,17 @@ int notes_merge_commit(struct notes_merge_options *o,
                if (add_note(partial_tree, obj_sha1, blob_sha1, NULL))
                        die("Failed to add resolved note '%s' to notes tree",
                            ent->name);
-               OUTPUT(o, 4, "Added resolved note for object %s: %s",
-                      sha1_to_hex(obj_sha1), sha1_to_hex(blob_sha1));
+               if (o->verbosity >= 4)
+                       printf("Added resolved note for object %s: %s\n",
+                               sha1_to_hex(obj_sha1), sha1_to_hex(blob_sha1));
        }
 
        create_notes_commit(partial_tree, partial_commit->parents, msg,
                            result_sha1);
-       OUTPUT(o, 4, "Finalized notes merge commit: %s",
-              sha1_to_hex(result_sha1));
+       if (o->verbosity >= 4)
+               printf("Finalized notes merge commit: %s\n",
+                       sha1_to_hex(result_sha1));
+       free(path);
        return 0;
 }
 
@@ -730,7 +749,8 @@ int notes_merge_abort(struct notes_merge_options *o)
        int ret;
 
        strbuf_addstr(&buf, git_path(NOTES_MERGE_WORKTREE));
-       OUTPUT(o, 3, "Removing notes merge worktree at %s", buf.buf);
+       if (o->verbosity >= 3)
+               printf("Removing notes merge worktree at %s\n", buf.buf);
        ret = remove_dir_recursively(&buf, 0);
        strbuf_release(&buf);
        return ret;
index 0c19b6e5a5677bd14989175abddc119381fac4ef..63a595c45c961fce54cca95ab6e09e3336f0bb8e 100644 (file)
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "pack.h"
 #include "pack-revindex.h"
+#include "progress.h"
 
 struct idx_entry {
        off_t                offset;
@@ -42,7 +43,10 @@ int check_pack_crc(struct packed_git *p, struct pack_window **w_curs,
 }
 
 static int verify_packfile(struct packed_git *p,
-               struct pack_window **w_curs)
+                          struct pack_window **w_curs,
+                          verify_fn fn,
+                          struct progress *progress, uint32_t base_count)
+
 {
        off_t index_size = p->index_size;
        const unsigned char *index_base = p->index_data;
@@ -113,20 +117,25 @@ static int verify_packfile(struct packed_git *p,
                                            p->pack_name, (uintmax_t)offset);
                }
                data = unpack_entry(p, entries[i].offset, &type, &size);
-               if (!data) {
+               if (!data)
                        err = error("cannot unpack %s from %s at offset %"PRIuMAX"",
                                    sha1_to_hex(entries[i].sha1), p->pack_name,
                                    (uintmax_t)entries[i].offset);
-                       break;
-               }
-               if (check_sha1_signature(entries[i].sha1, data, size, typename(type))) {
+               else if (check_sha1_signature(entries[i].sha1, data, size, typename(type)))
                        err = error("packed %s from %s is corrupt",
                                    sha1_to_hex(entries[i].sha1), p->pack_name);
-                       free(data);
-                       break;
+               else if (fn) {
+                       int eaten = 0;
+                       fn(entries[i].sha1, type, size, data, &eaten);
+                       if (eaten)
+                               data = NULL;
                }
+               if (((base_count + i) & 1023) == 0)
+                       display_progress(progress, base_count + i);
                free(data);
+
        }
+       display_progress(progress, base_count + i);
        free(entries);
 
        return err;
@@ -155,7 +164,8 @@ int verify_pack_index(struct packed_git *p)
        return err;
 }
 
-int verify_pack(struct packed_git *p)
+int verify_pack(struct packed_git *p, verify_fn fn,
+               struct progress *progress, uint32_t base_count)
 {
        int err = 0;
        struct pack_window *w_curs = NULL;
@@ -164,7 +174,7 @@ int verify_pack(struct packed_git *p)
        if (!p->index_data)
                return -1;
 
-       err |= verify_packfile(p, &w_curs);
+       err |= verify_packfile(p, &w_curs, fn, progress, base_count);
        unuse_pack(&w_curs);
 
        return err;
index 1290570260d184b9c94fdab62650d6e474b365b4..23bbd00e3e542e844fce08156c5c1073e6437d43 100644 (file)
@@ -72,7 +72,7 @@ static void try_remove_empty_parents(char *name)
        for (i = 0; i < 2; i++) { /* refs/{heads,tags,...}/ */
                while (*p && *p != '/')
                        p++;
-               /* tolerate duplicate slashes; see check_ref_format() */
+               /* tolerate duplicate slashes; see check_refname_format() */
                while (*p == '/')
                        p++;
        }
diff --git a/pack.h b/pack.h
index aca4739319071ffeb201fb5bcaf55811ddf5b40f..a8d9b9f2fcc41bf56007cd48dcfcb94a7e00d3ca 100644 (file)
--- a/pack.h
+++ b/pack.h
@@ -71,10 +71,14 @@ struct pack_idx_entry {
        off_t offset;
 };
 
+
+struct progress;
+typedef int (*verify_fn)(const unsigned char*, enum object_type, unsigned long, void*, int*);
+
 extern const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, const struct pack_idx_option *, unsigned char *sha1);
 extern int check_pack_crc(struct packed_git *p, struct pack_window **w_curs, off_t offset, off_t len, unsigned int nr);
 extern int verify_pack_index(struct packed_git *);
-extern int verify_pack(struct packed_git *);
+extern int verify_pack(struct packed_git *, verify_fn fn, struct progress *, uint32_t);
 extern void fixup_pack_header_footer(int, unsigned char *, const char *, uint32_t, unsigned char *, off_t);
 extern char *index_pack_lockfile(int fd);
 extern int encode_in_pack_object_header(enum object_type, uintmax_t, unsigned char *);
index 6db0921fc1fde3b5fbf829577bdd019ae95462e0..0de5fb168a5c1b86c50be0d0c3f2d7228e7159a1 100644 (file)
@@ -123,3 +123,8 @@ int parse_opt_string_list(const struct option *opt, const char *arg, int unset)
        string_list_append(v, xstrdup(arg));
        return 0;
 }
+
+int parse_opt_noop_cb(const struct option *opt, const char *arg, int unset)
+{
+       return 0;
+}
index 503ab5d500c8ca2fe30f50601717979e752b9254..f0098eb8ea7eed27d8a67952481f043ce7c75d4a 100644 (file)
@@ -83,7 +83,7 @@ static int get_value(struct parse_opt_ctx_t *p,
                        *(int *)opt->value &= ~opt->defval;
                return 0;
 
-       case OPTION_BOOLEAN:
+       case OPTION_COUNTUP:
                *(int *)opt->value = unset ? 0 : *(int *)opt->value + 1;
                return 0;
 
@@ -319,7 +319,7 @@ static void parse_options_check(const struct option *opts)
                        err |= optbug(opts, "uses feature "
                                        "not supported for dashless options");
                switch (opts->type) {
-               case OPTION_BOOLEAN:
+               case OPTION_COUNTUP:
                case OPTION_BIT:
                case OPTION_NEGBIT:
                case OPTION_SET_INT:
index 59e0b524bdcbe1c061f49b8e5f3c6365a9f2eba4..2e811dc7da8e6adc7ebce1a9929e8c345424fe4a 100644 (file)
@@ -10,7 +10,7 @@ enum parse_opt_type {
        /* options with no arguments */
        OPTION_BIT,
        OPTION_NEGBIT,
-       OPTION_BOOLEAN, /* _INCR would have been a better name */
+       OPTION_COUNTUP,
        OPTION_SET_INT,
        OPTION_SET_PTR,
        /* options with arguments (usually) */
@@ -21,6 +21,9 @@ enum parse_opt_type {
        OPTION_FILENAME
 };
 
+/* Deprecated synonym */
+#define OPTION_BOOLEAN OPTION_COUNTUP
+
 enum parse_opt_flags {
        PARSE_OPT_KEEP_DASHDASH = 1,
        PARSE_OPT_STOP_AT_NON_OPTION = 2,
@@ -122,10 +125,11 @@ struct option {
                                      PARSE_OPT_NOARG, NULL, (b) }
 #define OPT_NEGBIT(s, l, v, h, b)   { OPTION_NEGBIT, (s), (l), (v), NULL, \
                                      (h), PARSE_OPT_NOARG, NULL, (b) }
-#define OPT_BOOLEAN(s, l, v, h)     { OPTION_BOOLEAN, (s), (l), (v), NULL, \
+#define OPT_COUNTUP(s, l, v, h)     { OPTION_COUNTUP, (s), (l), (v), NULL, \
                                      (h), PARSE_OPT_NOARG }
 #define OPT_SET_INT(s, l, v, h, i)  { OPTION_SET_INT, (s), (l), (v), NULL, \
                                      (h), PARSE_OPT_NOARG, NULL, (i) }
+#define OPT_BOOL(s, l, v, h)        OPT_SET_INT(s, l, v, h, 1)
 #define OPT_SET_PTR(s, l, v, h, p)  { OPTION_SET_PTR, (s), (l), (v), NULL, \
                                      (h), PARSE_OPT_NOARG, NULL, (p) }
 #define OPT_INTEGER(s, l, v, h)     { OPTION_INTEGER, (s), (l), (v), "n", (h) }
@@ -149,6 +153,13 @@ struct option {
        { OPTION_CALLBACK, (s), (l), (v), "when", (h), PARSE_OPT_OPTARG, \
                parse_opt_color_flag_cb, (intptr_t)"always" }
 
+#define OPT_NOOP_NOARG(s, l) \
+       { OPTION_CALLBACK, (s), (l), NULL, NULL, \
+         "no-op (backward compatibility)", \
+         PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, parse_opt_noop_cb }
+
+/* Deprecated synonym */
+#define OPT_BOOLEAN OPT_COUNTUP
 
 /* parse_options() will filter out the processed options and leave the
  * non-option arguments in argv[].
@@ -210,6 +221,7 @@ extern int parse_opt_verbosity_cb(const struct option *, const char *, int);
 extern int parse_opt_with_commit(const struct option *, const char *, int);
 extern int parse_opt_tertiary(const struct option *, const char *, int);
 extern int parse_opt_string_list(const struct option *, const char *, int);
+extern int parse_opt_noop_cb(const struct option *, const char *, int);
 
 #define OPT__VERBOSE(var, h)  OPT_BOOLEAN('v', "verbose", (var), (h))
 #define OPT__QUIET(var, h)    OPT_BOOLEAN('q', "quiet",   (var), (h))
diff --git a/path.c b/path.c
index 6f3f5d56c0ed76f50d1aa37646d18ae280f1edbb..b6f71d1086981dc41bdbbc8954eccd9e9b719f98 100644 (file)
--- a/path.c
+++ b/path.c
@@ -283,7 +283,7 @@ return_null:
  * links.  User relative paths are also returned as they are given,
  * except DWIM suffixing.
  */
-char *enter_repo(char *path, int strict)
+const char *enter_repo(const char *path, int strict)
 {
        static char used_path[PATH_MAX];
        static char validated_path[PATH_MAX];
@@ -295,16 +295,19 @@ char *enter_repo(char *path, int strict)
                static const char *suffix[] = {
                        ".git/.git", "/.git", ".git", "", NULL,
                };
+               const char *gitfile;
                int len = strlen(path);
                int i;
-               while ((1 < len) && (path[len-1] == '/')) {
-                       path[len-1] = 0;
+               while ((1 < len) && (path[len-1] == '/'))
                        len--;
-               }
+
                if (PATH_MAX <= len)
                        return NULL;
-               if (path[0] == '~') {
-                       char *newpath = expand_user_path(path);
+               strncpy(used_path, path, len); used_path[len] = 0 ;
+               strcpy(validated_path, used_path);
+
+               if (used_path[0] == '~') {
+                       char *newpath = expand_user_path(used_path);
                        if (!newpath || (PATH_MAX - 10 < strlen(newpath))) {
                                free(newpath);
                                return NULL;
@@ -316,24 +319,23 @@ char *enter_repo(char *path, int strict)
                         * anyway.
                         */
                        strcpy(used_path, newpath); free(newpath);
-                       strcpy(validated_path, path);
-                       path = used_path;
                }
                else if (PATH_MAX - 10 < len)
                        return NULL;
-               else {
-                       path = strcpy(used_path, path);
-                       strcpy(validated_path, path);
-               }
-               len = strlen(path);
+               len = strlen(used_path);
                for (i = 0; suffix[i]; i++) {
-                       strcpy(path + len, suffix[i]);
-                       if (!access(path, F_OK)) {
+                       strcpy(used_path + len, suffix[i]);
+                       if (!access(used_path, F_OK)) {
                                strcat(validated_path, suffix[i]);
                                break;
                        }
                }
-               if (!suffix[i] || chdir(path))
+               if (!suffix[i])
+                       return NULL;
+               gitfile = read_gitfile(used_path) ;
+               if (gitfile)
+                       strcpy(used_path, gitfile);
+               if (chdir(used_path))
                        return NULL;
                path = validated_path;
        }
index 98b24772c7ebe838d513d8e24f3c8acff7839cb9..9235e73163204593379cdf6367133b5b503423c2 100644 (file)
@@ -1,5 +1,6 @@
 perl.mak
 perl.mak.old
+MYMETA.yml
 blib
 blibdirs
 pm_to_blib
index a86ab709c25b5e110aa2708941cea560f82c1fa8..f7ce511bbbfbff5a479200f2814ad87b96f16791 100644 (file)
@@ -570,30 +570,10 @@ does. In scalar context requires the variable to be set only one time
 (exception is thrown otherwise), in array context returns allows the
 variable to be set multiple times and returns all the values.
 
-This currently wraps command('config') so it is not so fast.
-
 =cut
 
 sub config {
-       my ($self, $var) = _maybe_self(@_);
-
-       try {
-               my @cmd = ('config');
-               unshift @cmd, $self if $self;
-               if (wantarray) {
-                       return command(@cmd, '--get-all', $var);
-               } else {
-                       return command_oneline(@cmd, '--get', $var);
-               }
-       } catch Git::Error::Command with {
-               my $E = shift;
-               if ($E->value() == 1) {
-                       # Key not found.
-                       return;
-               } else {
-                       throw $E;
-               }
-       };
+       return _config_common({}, @_);
 }
 
 
@@ -603,30 +583,33 @@ Retrieve the bool configuration C<VARIABLE>. The return value
 is usable as a boolean in perl (and C<undef> if it's not defined,
 of course).
 
-This currently wraps command('config') so it is not so fast.
-
 =cut
 
 sub config_bool {
-       my ($self, $var) = _maybe_self(@_);
+       my $val = scalar _config_common({'kind' => '--bool'}, @_);
 
-       try {
-               my @cmd = ('config', '--bool', '--get', $var);
-               unshift @cmd, $self if $self;
-               my $val = command_oneline(@cmd);
-               return undef unless defined $val;
+       # Do not rewrite this as return (defined $val && $val eq 'true')
+       # as some callers do care what kind of falsehood they receive.
+       if (!defined $val) {
+               return undef;
+       } else {
                return $val eq 'true';
-       } catch Git::Error::Command with {
-               my $E = shift;
-               if ($E->value() == 1) {
-                       # Key not found.
-                       return undef;
-               } else {
-                       throw $E;
-               }
-       };
+       }
 }
 
+
+=item config_path ( VARIABLE )
+
+Retrieve the path configuration C<VARIABLE>. The return value
+is an expanded path or C<undef> if it's not defined.
+
+=cut
+
+sub config_path {
+       return _config_common({'kind' => '--path'}, @_);
+}
+
+
 =item config_int ( VARIABLE )
 
 Retrieve the integer configuration C<VARIABLE>. The return value
@@ -635,22 +618,31 @@ or 'g' in the config file will cause the value to be multiplied
 by 1024, 1048576 (1024^2), or 1073741824 (1024^3) prior to output.
 It would return C<undef> if configuration variable is not defined,
 
-This currently wraps command('config') so it is not so fast.
-
 =cut
 
 sub config_int {
+       return scalar _config_common({'kind' => '--int'}, @_);
+}
+
+# Common subroutine to implement bulk of what the config* family of methods
+# do. This curently wraps command('config') so it is not so fast.
+sub _config_common {
+       my ($opts) = shift @_;
        my ($self, $var) = _maybe_self(@_);
 
        try {
-               my @cmd = ('config', '--int', '--get', $var);
+               my @cmd = ('config', $opts->{'kind'} ? $opts->{'kind'} : ());
                unshift @cmd, $self if $self;
-               return command_oneline(@cmd);
+               if (wantarray) {
+                       return command(@cmd, '--get-all', $var);
+               } else {
+                       return command_oneline(@cmd, '--get', $var);
+               }
        } catch Git::Error::Command with {
                my $E = shift;
                if ($E->value() == 1) {
                        # Key not found.
-                       return undef;
+                       return;
                } else {
                        throw $E;
                }
index f45eb54e4c99b8d67e4aa85f9a6218ea7a560592..230fe1cc82e3a3bf7c0fef604350868d917acef4 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -1094,7 +1094,6 @@ void format_commit_message(const struct commit *commit,
 {
        struct format_commit_context context;
        static const char utf8[] = "UTF-8";
-       const char *enc;
        const char *output_enc = pretty_ctx->output_encoding;
 
        memset(&context, 0, sizeof(context));
@@ -1103,10 +1102,13 @@ void format_commit_message(const struct commit *commit,
        context.wrap_start = sb->len;
        context.message = commit->buffer;
        if (output_enc) {
-               enc = get_header(commit, "encoding");
-               enc = enc ? enc : utf8;
-               if (strcmp(enc, output_enc))
+               char *enc = get_header(commit, "encoding");
+               if (strcmp(enc ? enc : utf8, output_enc)) {
                        context.message = logmsg_reencode(commit, output_enc);
+                       if (!context.message)
+                               context.message = commit->buffer;
+               }
+               free(enc);
        }
 
        strbuf_expand(sb, format, format_commit_item, &context);
diff --git a/quote.c b/quote.c
index 532fd3b7b7a0c7b6766a7af742b0c7362f307221..911229fdf3caffe29a87aea76f38dc50863797d4 100644 (file)
--- a/quote.c
+++ b/quote.c
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "quote.h"
+#include "argv-array.h"
 
 int quote_path_fully = 1;
 
@@ -120,7 +121,9 @@ char *sq_dequote(char *arg)
        return sq_dequote_step(arg, NULL);
 }
 
-int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc)
+static int sq_dequote_to_argv_internal(char *arg,
+                                      const char ***argv, int *nr, int *alloc,
+                                      struct argv_array *array)
 {
        char *next = arg;
 
@@ -130,13 +133,27 @@ int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc)
                char *dequoted = sq_dequote_step(next, &next);
                if (!dequoted)
                        return -1;
-               ALLOC_GROW(*argv, *nr + 1, *alloc);
-               (*argv)[(*nr)++] = dequoted;
+               if (argv) {
+                       ALLOC_GROW(*argv, *nr + 1, *alloc);
+                       (*argv)[(*nr)++] = dequoted;
+               }
+               if (array)
+                       argv_array_push(array, dequoted);
        } while (next);
 
        return 0;
 }
 
+int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc)
+{
+       return sq_dequote_to_argv_internal(arg, argv, nr, alloc, NULL);
+}
+
+int sq_dequote_to_argv_array(char *arg, struct argv_array *array)
+{
+       return sq_dequote_to_argv_internal(arg, NULL, NULL, NULL, array);
+}
+
 /* 1 means: quote as octal
  * 0 means: quote as octal if (quote_path_fully)
  * -1 means: never quote
diff --git a/quote.h b/quote.h
index 024e21d80ceaa37ec1f893acc907a62092560d21..133155a48b4baa56cd70c07af8a477771f373105 100644 (file)
--- a/quote.h
+++ b/quote.h
@@ -40,12 +40,19 @@ extern char *sq_dequote(char *);
 
 /*
  * Same as the above, but can be used to unwrap many arguments in the
- * same string separated by space. "next" is changed to point to the
- * next argument that should be passed as first parameter. When there
- * is no more argument to be dequoted, "next" is updated to point to NULL.
+ * same string separated by space. Like sq_quote, it works in place,
+ * modifying arg and appending pointers into it to argv.
  */
 extern int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc);
 
+/*
+ * Same as above, but store the unquoted strings in an argv_array. We will
+ * still modify arg in place, but unlike sq_dequote_to_argv, the argv_array
+ * will duplicate and take ownership of the strings.
+ */
+struct argv_array;
+extern int sq_dequote_to_argv_array(char *arg, struct argv_array *);
+
 extern int unquote_c_style(struct strbuf *, const char *quoted, const char **endp);
 extern size_t quote_c_style(const char *name, struct strbuf *, FILE *, int no_dq);
 extern void quote_two_c_style(struct strbuf *, const char *, const char *, int);
index 3fc6b1d320faad3a528db016a0d800ff1bdde4f0..bf7970661f26664e461ed5d6fd7a1982f7e8fadd 100644 (file)
@@ -7,11 +7,25 @@
 #include "revision.h"
 #include "reachable.h"
 #include "cache-tree.h"
+#include "progress.h"
+
+struct connectivity_progress {
+       struct progress *progress;
+       unsigned long count;
+};
+
+static void update_progress(struct connectivity_progress *cp)
+{
+       cp->count++;
+       if ((cp->count & 1023) == 0)
+               display_progress(cp->progress, cp->count);
+}
 
 static void process_blob(struct blob *blob,
                         struct object_array *p,
                         struct name_path *path,
-                        const char *name)
+                        const char *name,
+                        struct connectivity_progress *cp)
 {
        struct object *obj = &blob->object;
 
@@ -20,6 +34,7 @@ static void process_blob(struct blob *blob,
        if (obj->flags & SEEN)
                return;
        obj->flags |= SEEN;
+       update_progress(cp);
        /* Nothing to do, really .. The blob lookup was the important part */
 }
 
@@ -34,7 +49,8 @@ static void process_gitlink(const unsigned char *sha1,
 static void process_tree(struct tree *tree,
                         struct object_array *p,
                         struct name_path *path,
-                        const char *name)
+                        const char *name,
+                        struct connectivity_progress *cp)
 {
        struct object *obj = &tree->object;
        struct tree_desc desc;
@@ -46,6 +62,7 @@ static void process_tree(struct tree *tree,
        if (obj->flags & SEEN)
                return;
        obj->flags |= SEEN;
+       update_progress(cp);
        if (parse_tree(tree) < 0)
                die("bad tree object %s", sha1_to_hex(obj->sha1));
        add_object(obj, p, path, name);
@@ -57,23 +74,25 @@ static void process_tree(struct tree *tree,
 
        while (tree_entry(&desc, &entry)) {
                if (S_ISDIR(entry.mode))
-                       process_tree(lookup_tree(entry.sha1), p, &me, entry.path);
+                       process_tree(lookup_tree(entry.sha1), p, &me, entry.path, cp);
                else if (S_ISGITLINK(entry.mode))
                        process_gitlink(entry.sha1, p, &me, entry.path);
                else
-                       process_blob(lookup_blob(entry.sha1), p, &me, entry.path);
+                       process_blob(lookup_blob(entry.sha1), p, &me, entry.path, cp);
        }
        free(tree->buffer);
        tree->buffer = NULL;
 }
 
-static void process_tag(struct tag *tag, struct object_array *p, const char *name)
+static void process_tag(struct tag *tag, struct object_array *p,
+                       const char *name, struct connectivity_progress *cp)
 {
        struct object *obj = &tag->object;
 
        if (obj->flags & SEEN)
                return;
        obj->flags |= SEEN;
+       update_progress(cp);
 
        if (parse_tag(tag) < 0)
                die("bad tag object %s", sha1_to_hex(obj->sha1));
@@ -81,15 +100,18 @@ static void process_tag(struct tag *tag, struct object_array *p, const char *nam
                add_object(tag->tagged, p, NULL, name);
 }
 
-static void walk_commit_list(struct rev_info *revs)
+static void walk_commit_list(struct rev_info *revs,
+                            struct connectivity_progress *cp)
 {
        int i;
        struct commit *commit;
        struct object_array objects = OBJECT_ARRAY_INIT;
 
        /* Walk all commits, process their trees */
-       while ((commit = get_revision(revs)) != NULL)
-               process_tree(commit->tree, &objects, NULL, "");
+       while ((commit = get_revision(revs)) != NULL) {
+               process_tree(commit->tree, &objects, NULL, "", cp);
+               update_progress(cp);
+       }
 
        /* Then walk all the pending objects, recursively processing them too */
        for (i = 0; i < revs->pending.nr; i++) {
@@ -97,15 +119,15 @@ static void walk_commit_list(struct rev_info *revs)
                struct object *obj = pending->item;
                const char *name = pending->name;
                if (obj->type == OBJ_TAG) {
-                       process_tag((struct tag *) obj, &objects, name);
+                       process_tag((struct tag *) obj, &objects, name, cp);
                        continue;
                }
                if (obj->type == OBJ_TREE) {
-                       process_tree((struct tree *)obj, &objects, NULL, name);
+                       process_tree((struct tree *)obj, &objects, NULL, name, cp);
                        continue;
                }
                if (obj->type == OBJ_BLOB) {
-                       process_blob((struct blob *)obj, &objects, NULL, name);
+                       process_blob((struct blob *)obj, &objects, NULL, name, cp);
                        continue;
                }
                die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name);
@@ -191,8 +213,11 @@ static void add_cache_refs(struct rev_info *revs)
                add_cache_tree(active_cache_tree, revs);
 }
 
-void mark_reachable_objects(struct rev_info *revs, int mark_reflog)
+void mark_reachable_objects(struct rev_info *revs, int mark_reflog,
+                           struct progress *progress)
 {
+       struct connectivity_progress cp;
+
        /*
         * Set up revision parsing, and mark us as being interested
         * in all object types, not just commits.
@@ -211,11 +236,15 @@ void mark_reachable_objects(struct rev_info *revs, int mark_reflog)
        if (mark_reflog)
                for_each_reflog(add_one_reflog, revs);
 
+       cp.progress = progress;
+       cp.count = 0;
+
        /*
         * Set up the revision walk - this will move all commits
         * from the pending list to the commit walking list.
         */
        if (prepare_revision_walk(revs))
                die("revision walk setup failed");
-       walk_commit_list(revs);
+       walk_commit_list(revs, &cp);
+       display_progress(cp.progress, cp.count);
 }
index 40751810b64f8bbf9c0a633472a0ef27d23ed1a5..5d082adfecc47c40074212df2ddedc00d8a5ecb1 100644 (file)
@@ -1,6 +1,7 @@
 #ifndef REACHEABLE_H
 #define REACHEABLE_H
 
-extern void mark_reachable_objects(struct rev_info *revs, int mark_reflog);
+struct progress;
+extern void mark_reachable_objects(struct rev_info *revs, int mark_reflog, struct progress *);
 
 #endif
index 01a0e2505121f10544ee03948e545d07c24f366e..5790a91044e4fdf5b2eec515051a66c110e0daa4 100644 (file)
@@ -1249,9 +1249,9 @@ static void convert_from_disk(struct ondisk_cache_entry *ondisk, struct cache_en
 
 static inline size_t estimate_cache_size(size_t ondisk_size, unsigned int entries)
 {
-       long per_entry;
-
-       per_entry = sizeof(struct cache_entry) - sizeof(struct ondisk_cache_entry);
+       size_t fix_size_mem = offsetof(struct cache_entry, name);
+       size_t fix_size_dsk = offsetof(struct ondisk_cache_entry, name);
+       long per_entry = (fix_size_mem - fix_size_dsk + 7) & ~7;
 
        /*
         * Alignment can cause differences. This should be "alignof", but
diff --git a/refs.c b/refs.c
index a615043b34cd6d0507d8a30f7bd69445ec9f2456..e7843eb6b70b7de6c1ed1992db7adee1d99b27bc 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -4,18 +4,21 @@
 #include "tag.h"
 #include "dir.h"
 
-/* ISSYMREF=01 and ISPACKED=02 are public interfaces */
-#define REF_KNOWS_PEELED 04
-#define REF_BROKEN 010
+/* ISSYMREF=0x01, ISPACKED=0x02 and ISBROKEN=0x04 are public interfaces */
+#define REF_KNOWS_PEELED 0x10
 
-struct ref_list {
-       struct ref_list *next;
+struct ref_entry {
        unsigned char flag; /* ISSYMREF? ISPACKED? */
        unsigned char sha1[20];
        unsigned char peeled[20];
        char name[FLEX_ARRAY];
 };
 
+struct ref_array {
+       int nr, alloc;
+       struct ref_entry **refs;
+};
+
 static const char *parse_ref_line(char *line, unsigned char *sha1)
 {
        /*
@@ -44,149 +47,176 @@ static const char *parse_ref_line(char *line, unsigned char *sha1)
        return line;
 }
 
-static struct ref_list *add_ref(const char *name, const unsigned char *sha1,
-                               int flag, struct ref_list *list,
-                               struct ref_list **new_entry)
+static void add_ref(const char *name, const unsigned char *sha1,
+                   int flag, int check_name, struct ref_array *refs,
+                   struct ref_entry **new_entry)
 {
        int len;
-       struct ref_list *entry;
+       struct ref_entry *entry;
 
        /* Allocate it and add it in.. */
        len = strlen(name) + 1;
-       entry = xmalloc(sizeof(struct ref_list) + len);
+       entry = xmalloc(sizeof(struct ref_entry) + len);
        hashcpy(entry->sha1, sha1);
        hashclr(entry->peeled);
+       if (check_name &&
+           check_refname_format(name, REFNAME_ALLOW_ONELEVEL|REFNAME_DOT_COMPONENT))
+               die("Reference has invalid format: '%s'", name);
        memcpy(entry->name, name, len);
        entry->flag = flag;
-       entry->next = list;
        if (new_entry)
                *new_entry = entry;
-       return entry;
+       ALLOC_GROW(refs->refs, refs->nr + 1, refs->alloc);
+       refs->refs[refs->nr++] = entry;
 }
 
-/* merge sort the ref list */
-static struct ref_list *sort_ref_list(struct ref_list *list)
+static int ref_entry_cmp(const void *a, const void *b)
 {
-       int psize, qsize, last_merge_count, cmp;
-       struct ref_list *p, *q, *l, *e;
-       struct ref_list *new_list = list;
-       int k = 1;
-       int merge_count = 0;
+       struct ref_entry *one = *(struct ref_entry **)a;
+       struct ref_entry *two = *(struct ref_entry **)b;
+       return strcmp(one->name, two->name);
+}
 
-       if (!list)
-               return list;
+static void sort_ref_array(struct ref_array *array)
+{
+       int i = 0, j = 1;
 
-       do {
-               last_merge_count = merge_count;
-               merge_count = 0;
+       /* Nothing to sort unless there are at least two entries */
+       if (array->nr < 2)
+               return;
 
-               psize = 0;
+       qsort(array->refs, array->nr, sizeof(*array->refs), ref_entry_cmp);
 
-               p = new_list;
-               q = new_list;
-               new_list = NULL;
-               l = NULL;
+       /* Remove any duplicates from the ref_array */
+       for (; j < array->nr; j++) {
+               struct ref_entry *a = array->refs[i];
+               struct ref_entry *b = array->refs[j];
+               if (!strcmp(a->name, b->name)) {
+                       if (hashcmp(a->sha1, b->sha1))
+                               die("Duplicated ref, and SHA1s don't match: %s",
+                                   a->name);
+                       warning("Duplicated ref: %s", a->name);
+                       free(b);
+                       continue;
+               }
+               i++;
+               array->refs[i] = array->refs[j];
+       }
+       array->nr = i + 1;
+}
 
-               while (p) {
-                       merge_count++;
+static struct ref_entry *search_ref_array(struct ref_array *array, const char *name)
+{
+       struct ref_entry *e, **r;
+       int len;
 
-                       while (psize < k && q->next) {
-                               q = q->next;
-                               psize++;
-                       }
-                       qsize = k;
-
-                       while ((psize > 0) || (qsize > 0 && q)) {
-                               if (qsize == 0 || !q) {
-                                       e = p;
-                                       p = p->next;
-                                       psize--;
-                               } else if (psize == 0) {
-                                       e = q;
-                                       q = q->next;
-                                       qsize--;
-                               } else {
-                                       cmp = strcmp(q->name, p->name);
-                                       if (cmp < 0) {
-                                               e = q;
-                                               q = q->next;
-                                               qsize--;
-                                       } else if (cmp > 0) {
-                                               e = p;
-                                               p = p->next;
-                                               psize--;
-                                       } else {
-                                               if (hashcmp(q->sha1, p->sha1))
-                                                       die("Duplicated ref, and SHA1s don't match: %s",
-                                                           q->name);
-                                               warning("Duplicated ref: %s", q->name);
-                                               e = q;
-                                               q = q->next;
-                                               qsize--;
-                                               free(e);
-                                               e = p;
-                                               p = p->next;
-                                               psize--;
-                                       }
-                               }
+       if (name == NULL)
+               return NULL;
 
-                               e->next = NULL;
+       if (!array->nr)
+               return NULL;
 
-                               if (l)
-                                       l->next = e;
-                               if (!new_list)
-                                       new_list = e;
-                               l = e;
-                       }
+       len = strlen(name) + 1;
+       e = xmalloc(sizeof(struct ref_entry) + len);
+       memcpy(e->name, name, len);
+
+       r = bsearch(&e, array->refs, array->nr, sizeof(*array->refs), ref_entry_cmp);
 
-                       p = q;
-               };
+       free(e);
 
-               k = k * 2;
-       } while ((last_merge_count != merge_count) || (last_merge_count != 1));
+       if (r == NULL)
+               return NULL;
 
-       return new_list;
+       return *r;
 }
 
 /*
  * Future: need to be in "struct repository"
  * when doing a full libification.
  */
-static struct cached_refs {
+static struct ref_cache {
+       struct ref_cache *next;
        char did_loose;
        char did_packed;
-       struct ref_list *loose;
-       struct ref_list *packed;
-} cached_refs, submodule_refs;
-static struct ref_list *current_ref;
+       struct ref_array loose;
+       struct ref_array packed;
+       /* The submodule name, or "" for the main repo. */
+       char name[FLEX_ARRAY];
+} *ref_cache;
 
-static struct ref_list *extra_refs;
+static struct ref_entry *current_ref;
 
-static void free_ref_list(struct ref_list *list)
+static struct ref_array extra_refs;
+
+static void free_ref_array(struct ref_array *array)
 {
-       struct ref_list *next;
-       for ( ; list; list = next) {
-               next = list->next;
-               free(list);
-       }
+       int i;
+       for (i = 0; i < array->nr; i++)
+               free(array->refs[i]);
+       free(array->refs);
+       array->nr = array->alloc = 0;
+       array->refs = NULL;
+}
+
+static void clear_packed_ref_cache(struct ref_cache *refs)
+{
+       if (refs->did_packed)
+               free_ref_array(&refs->packed);
+       refs->did_packed = 0;
 }
 
-static void invalidate_cached_refs(void)
+static void clear_loose_ref_cache(struct ref_cache *refs)
 {
-       struct cached_refs *ca = &cached_refs;
+       if (refs->did_loose)
+               free_ref_array(&refs->loose);
+       refs->did_loose = 0;
+}
 
-       if (ca->did_loose && ca->loose)
-               free_ref_list(ca->loose);
-       if (ca->did_packed && ca->packed)
-               free_ref_list(ca->packed);
-       ca->loose = ca->packed = NULL;
-       ca->did_loose = ca->did_packed = 0;
+static struct ref_cache *create_ref_cache(const char *submodule)
+{
+       int len;
+       struct ref_cache *refs;
+       if (!submodule)
+               submodule = "";
+       len = strlen(submodule) + 1;
+       refs = xcalloc(1, sizeof(struct ref_cache) + len);
+       memcpy(refs->name, submodule, len);
+       return refs;
+}
+
+/*
+ * Return a pointer to a ref_cache for the specified submodule. For
+ * the main repository, use submodule==NULL. The returned structure
+ * will be allocated and initialized but not necessarily populated; it
+ * should not be freed.
+ */
+static struct ref_cache *get_ref_cache(const char *submodule)
+{
+       struct ref_cache *refs = ref_cache;
+       if (!submodule)
+               submodule = "";
+       while (refs) {
+               if (!strcmp(submodule, refs->name))
+                       return refs;
+               refs = refs->next;
+       }
+
+       refs = create_ref_cache(submodule);
+       refs->next = ref_cache;
+       ref_cache = refs;
+       return refs;
+}
+
+void invalidate_ref_cache(const char *submodule)
+{
+       struct ref_cache *refs = get_ref_cache(submodule);
+       clear_packed_ref_cache(refs);
+       clear_loose_ref_cache(refs);
 }
 
-static void read_packed_refs(FILE *f, struct cached_refs *cached_refs)
+static void read_packed_refs(FILE *f, struct ref_array *array)
 {
-       struct ref_list *list = NULL;
-       struct ref_list *last = NULL;
+       struct ref_entry *last = NULL;
        char refline[PATH_MAX];
        int flag = REF_ISPACKED;
 
@@ -205,7 +235,7 @@ static void read_packed_refs(FILE *f, struct cached_refs *cached_refs)
 
                name = parse_ref_line(refline, sha1);
                if (name) {
-                       list = add_ref(name, sha1, flag, list, &last);
+                       add_ref(name, sha1, flag, 1, array, &last);
                        continue;
                }
                if (last &&
@@ -215,48 +245,43 @@ static void read_packed_refs(FILE *f, struct cached_refs *cached_refs)
                    !get_sha1_hex(refline + 1, sha1))
                        hashcpy(last->peeled, sha1);
        }
-       cached_refs->packed = sort_ref_list(list);
+       sort_ref_array(array);
 }
 
 void add_extra_ref(const char *name, const unsigned char *sha1, int flag)
 {
-       extra_refs = add_ref(name, sha1, flag, extra_refs, NULL);
+       add_ref(name, sha1, flag, 0, &extra_refs, NULL);
 }
 
 void clear_extra_refs(void)
 {
-       free_ref_list(extra_refs);
-       extra_refs = NULL;
+       free_ref_array(&extra_refs);
 }
 
-static struct ref_list *get_packed_refs(const char *submodule)
+static struct ref_array *get_packed_refs(const char *submodule)
 {
-       const char *packed_refs_file;
-       struct cached_refs *refs;
+       struct ref_cache *refs = get_ref_cache(submodule);
 
-       if (submodule) {
-               packed_refs_file = git_path_submodule(submodule, "packed-refs");
-               refs = &submodule_refs;
-               free_ref_list(refs->packed);
-       } else {
-               packed_refs_file = git_path("packed-refs");
-               refs = &cached_refs;
-       }
+       if (!refs->did_packed) {
+               const char *packed_refs_file;
+               FILE *f;
 
-       if (!refs->did_packed || submodule) {
-               FILE *f = fopen(packed_refs_file, "r");
-               refs->packed = NULL;
+               if (submodule)
+                       packed_refs_file = git_path_submodule(submodule, "packed-refs");
+               else
+                       packed_refs_file = git_path("packed-refs");
+               f = fopen(packed_refs_file, "r");
                if (f) {
-                       read_packed_refs(f, refs);
+                       read_packed_refs(f, &refs->packed);
                        fclose(f);
                }
                refs->did_packed = 1;
        }
-       return refs->packed;
+       return &refs->packed;
 }
 
-static struct ref_list *get_ref_dir(const char *submodule, const char *base,
-                                   struct ref_list *list)
+static void get_ref_dir(const char *submodule, const char *base,
+                       struct ref_array *array)
 {
        DIR *dir;
        const char *path;
@@ -299,7 +324,7 @@ static struct ref_list *get_ref_dir(const char *submodule, const char *base,
                        if (stat(refdir, &st) < 0)
                                continue;
                        if (S_ISDIR(st.st_mode)) {
-                               list = get_ref_dir(submodule, ref, list);
+                               get_ref_dir(submodule, ref, array);
                                continue;
                        }
                        if (submodule) {
@@ -307,19 +332,17 @@ static struct ref_list *get_ref_dir(const char *submodule, const char *base,
                                flag = 0;
                                if (resolve_gitlink_ref(submodule, ref, sha1) < 0) {
                                        hashclr(sha1);
-                                       flag |= REF_BROKEN;
-                               }
-                       } else
-                               if (!resolve_ref(ref, sha1, 1, &flag)) {
-                                       hashclr(sha1);
-                                       flag |= REF_BROKEN;
+                                       flag |= REF_ISBROKEN;
                                }
-                       list = add_ref(ref, sha1, flag, list, NULL);
+                       } else if (!resolve_ref(ref, sha1, 1, &flag)) {
+                               hashclr(sha1);
+                               flag |= REF_ISBROKEN;
+                       }
+                       add_ref(ref, sha1, flag, 1, array, NULL);
                }
                free(ref);
                closedir(dir);
        }
-       return sort_ref_list(list);
 }
 
 struct warn_if_dangling_data {
@@ -356,49 +379,43 @@ void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)
        for_each_rawref(warn_if_dangling_symref, &data);
 }
 
-static struct ref_list *get_loose_refs(const char *submodule)
+static struct ref_array *get_loose_refs(const char *submodule)
 {
-       if (submodule) {
-               free_ref_list(submodule_refs.loose);
-               submodule_refs.loose = get_ref_dir(submodule, "refs", NULL);
-               return submodule_refs.loose;
-       }
+       struct ref_cache *refs = get_ref_cache(submodule);
 
-       if (!cached_refs.did_loose) {
-               cached_refs.loose = get_ref_dir(NULL, "refs", NULL);
-               cached_refs.did_loose = 1;
+       if (!refs->did_loose) {
+               get_ref_dir(submodule, "refs", &refs->loose);
+               sort_ref_array(&refs->loose);
+               refs->did_loose = 1;
        }
-       return cached_refs.loose;
+       return &refs->loose;
 }
 
 /* We allow "recursive" symbolic refs. Only within reason, though */
 #define MAXDEPTH 5
 #define MAXREFLEN (1024)
 
+/*
+ * Called by resolve_gitlink_ref_recursive() after it failed to read
+ * from "name", which is "module/.git/<refname>". Find <refname> in
+ * the packed-refs file for the submodule.
+ */
 static int resolve_gitlink_packed_ref(char *name, int pathlen, const char *refname, unsigned char *result)
 {
-       FILE *f;
-       struct cached_refs refs;
-       struct ref_list *ref;
-       int retval;
+       int retval = -1;
+       struct ref_entry *ref;
+       struct ref_array *array;
 
-       strcpy(name + pathlen, "packed-refs");
-       f = fopen(name, "r");
-       if (!f)
-               return -1;
-       read_packed_refs(f, &refs);
-       fclose(f);
-       ref = refs.packed;
-       retval = -1;
-       while (ref) {
-               if (!strcmp(ref->name, refname)) {
-                       retval = 0;
-                       memcpy(result, ref->sha1, 20);
-                       break;
-               }
-               ref = ref->next;
+       /* being defensive: resolve_gitlink_ref() did this for us */
+       if (pathlen < 6 || memcmp(name + pathlen - 6, "/.git/", 6))
+               die("Oops");
+       name[pathlen - 6] = '\0'; /* make it path to the submodule */
+       array = get_packed_refs(name);
+       ref = search_ref_array(array, refname);
+       if (ref != NULL) {
+               memcpy(result, ref->sha1, 20);
+               retval = 0;
        }
-       free_ref_list(refs.packed);
        return retval;
 }
 
@@ -466,17 +483,20 @@ int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *re
 }
 
 /*
- * If the "reading" argument is set, this function finds out what _object_
- * the ref points at by "reading" the ref.  The ref, if it is not symbolic,
- * has to exist, and if it is symbolic, it has to point at an existing ref,
- * because the "read" goes through the symref to the ref it points at.
- *
- * The access that is not "reading" may often be "writing", but does not
- * have to; it can be merely checking _where it leads to_. If it is a
- * prelude to "writing" to the ref, a write to a symref that points at
- * yet-to-be-born ref will create the real ref pointed by the symref.
- * reading=0 allows the caller to check where such a symref leads to.
+ * Try to read ref from the packed references.  On success, set sha1
+ * and return 0; otherwise, return -1.
  */
+static int get_packed_ref(const char *ref, unsigned char *sha1)
+{
+       struct ref_array *packed = get_packed_refs(NULL);
+       struct ref_entry *entry = search_ref_array(packed, ref);
+       if (entry) {
+               hashcpy(sha1, entry->sha1);
+               return 0;
+       }
+       return -1;
+}
+
 const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *flag)
 {
        int depth = MAXDEPTH;
@@ -487,6 +507,9 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *
        if (flag)
                *flag = 0;
 
+       if (check_refname_format(ref, REFNAME_ALLOW_ONELEVEL))
+               return NULL;
+
        for (;;) {
                char path[PATH_MAX];
                struct stat st;
@@ -497,29 +520,36 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *
                        return NULL;
 
                git_snpath(path, sizeof(path), "%s", ref);
-               /* Special case: non-existing file. */
+
                if (lstat(path, &st) < 0) {
-                       struct ref_list *list = get_packed_refs(NULL);
-                       while (list) {
-                               if (!strcmp(ref, list->name)) {
-                                       hashcpy(sha1, list->sha1);
-                                       if (flag)
-                                               *flag |= REF_ISPACKED;
-                                       return ref;
-                               }
-                               list = list->next;
+                       if (errno != ENOENT)
+                               return NULL;
+                       /*
+                        * The loose reference file does not exist;
+                        * check for a packed reference.
+                        */
+                       if (!get_packed_ref(ref, sha1)) {
+                               if (flag)
+                                       *flag |= REF_ISPACKED;
+                               return ref;
                        }
-                       if (reading || errno != ENOENT)
+                       /* The reference is not a packed reference, either. */
+                       if (reading) {
                                return NULL;
-                       hashclr(sha1);
-                       return ref;
+                       } else {
+                               hashclr(sha1);
+                               return ref;
+                       }
                }
 
                /* Follow "normalized" - ie "refs/.." symlinks by hand */
                if (S_ISLNK(st.st_mode)) {
                        len = readlink(path, buffer, sizeof(buffer)-1);
-                       if (len >= 5 && !memcmp("refs/", buffer, 5)) {
-                               buffer[len] = 0;
+                       if (len < 0)
+                               return NULL;
+                       buffer[len] = 0;
+                       if (!prefixcmp(buffer, "refs/") &&
+                                       !check_refname_format(buffer, 0)) {
                                strcpy(ref_buffer, buffer);
                                ref = ref_buffer;
                                if (flag)
@@ -543,26 +573,35 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *
                        return NULL;
                len = read_in_full(fd, buffer, sizeof(buffer)-1);
                close(fd);
+               if (len < 0)
+                       return NULL;
+               while (len && isspace(buffer[len-1]))
+                       len--;
+               buffer[len] = '\0';
 
                /*
                 * Is it a symbolic ref?
                 */
-               if (len < 4 || memcmp("ref:", buffer, 4))
+               if (prefixcmp(buffer, "ref:"))
                        break;
-               buf = buffer + 4;
-               len -= 4;
-               while (len && isspace(*buf))
-                       buf++, len--;
-               while (len && isspace(buf[len-1]))
-                       len--;
-               buf[len] = 0;
-               memcpy(ref_buffer, buf, len + 1);
-               ref = ref_buffer;
                if (flag)
                        *flag |= REF_ISSYMREF;
+               buf = buffer + 4;
+               while (isspace(*buf))
+                       buf++;
+               if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {
+                       if (flag)
+                               *flag |= REF_ISBROKEN;
+                       return NULL;
+               }
+               ref = strcpy(ref_buffer, buf);
        }
-       if (len < 40 || get_sha1_hex(buffer, sha1))
+       /* Please note that FETCH_HEAD has a second line containing other data. */
+       if (get_sha1_hex(buffer, sha1) || (buffer[40] != '\0' && !isspace(buffer[40]))) {
+               if (flag)
+                       *flag |= REF_ISBROKEN;
                return NULL;
+       }
        return ref;
 }
 
@@ -582,14 +621,14 @@ int read_ref(const char *ref, unsigned char *sha1)
 
 #define DO_FOR_EACH_INCLUDE_BROKEN 01
 static int do_one_ref(const char *base, each_ref_fn fn, int trim,
-                     int flags, void *cb_data, struct ref_list *entry)
+                     int flags, void *cb_data, struct ref_entry *entry)
 {
        if (prefixcmp(entry->name, base))
                return 0;
 
        if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) {
-               if (entry->flag & REF_BROKEN)
-                       return 0; /* ignore dangling symref */
+               if (entry->flag & REF_ISBROKEN)
+                       return 0; /* ignore broken refs e.g. dangling symref */
                if (!has_sha1_file(entry->sha1)) {
                        error("%s does not point to a valid object!", entry->name);
                        return 0;
@@ -628,18 +667,12 @@ int peel_ref(const char *ref, unsigned char *sha1)
                return -1;
 
        if ((flag & REF_ISPACKED)) {
-               struct ref_list *list = get_packed_refs(NULL);
+               struct ref_array *array = get_packed_refs(NULL);
+               struct ref_entry *r = search_ref_array(array, ref);
 
-               while (list) {
-                       if (!strcmp(list->name, ref)) {
-                               if (list->flag & REF_KNOWS_PEELED) {
-                                       hashcpy(sha1, list->peeled);
-                                       return 0;
-                               }
-                               /* older pack-refs did not leave peeled ones */
-                               break;
-                       }
-                       list = list->next;
+               if (r != NULL && r->flag & REF_KNOWS_PEELED) {
+                       hashcpy(sha1, r->peeled);
+                       return 0;
                }
        }
 
@@ -658,36 +691,39 @@ fallback:
 static int do_for_each_ref(const char *submodule, const char *base, each_ref_fn fn,
                           int trim, int flags, void *cb_data)
 {
-       int retval = 0;
-       struct ref_list *packed = get_packed_refs(submodule);
-       struct ref_list *loose = get_loose_refs(submodule);
+       int retval = 0, i, p = 0, l = 0;
+       struct ref_array *packed = get_packed_refs(submodule);
+       struct ref_array *loose = get_loose_refs(submodule);
 
-       struct ref_list *extra;
+       struct ref_array *extra = &extra_refs;
 
-       for (extra = extra_refs; extra; extra = extra->next)
-               retval = do_one_ref(base, fn, trim, flags, cb_data, extra);
+       for (i = 0; i < extra->nr; i++)
+               retval = do_one_ref(base, fn, trim, flags, cb_data, extra->refs[i]);
 
-       while (packed && loose) {
-               struct ref_list *entry;
-               int cmp = strcmp(packed->name, loose->name);
+       while (p < packed->nr && l < loose->nr) {
+               struct ref_entry *entry;
+               int cmp = strcmp(packed->refs[p]->name, loose->refs[l]->name);
                if (!cmp) {
-                       packed = packed->next;
+                       p++;
                        continue;
                }
                if (cmp > 0) {
-                       entry = loose;
-                       loose = loose->next;
+                       entry = loose->refs[l++];
                } else {
-                       entry = packed;
-                       packed = packed->next;
+                       entry = packed->refs[p++];
                }
                retval = do_one_ref(base, fn, trim, flags, cb_data, entry);
                if (retval)
                        goto end_each;
        }
 
-       for (packed = packed ? packed : loose; packed; packed = packed->next) {
-               retval = do_one_ref(base, fn, trim, flags, cb_data, packed);
+       if (l < loose->nr) {
+               p = l;
+               packed = loose;
+       }
+
+       for (; p < packed->nr; p++) {
+               retval = do_one_ref(base, fn, trim, flags, cb_data, packed->refs[p]);
                if (retval)
                        goto end_each;
        }
@@ -860,70 +896,87 @@ int for_each_rawref(each_ref_fn fn, void *cb_data)
  * - it contains a "\" (backslash)
  */
 
+/* Return true iff ch is not allowed in reference names. */
 static inline int bad_ref_char(int ch)
 {
        if (((unsigned) ch) <= ' ' || ch == 0x7f ||
            ch == '~' || ch == '^' || ch == ':' || ch == '\\')
                return 1;
        /* 2.13 Pattern Matching Notation */
-       if (ch == '?' || ch == '[') /* Unsupported */
+       if (ch == '*' || ch == '?' || ch == '[') /* Unsupported */
                return 1;
-       if (ch == '*') /* Supported at the end */
-               return 2;
        return 0;
 }
 
-int check_ref_format(const char *ref)
+/*
+ * Try to read one refname component from the front of ref.  Return
+ * the length of the component found, or -1 if the component is not
+ * legal.
+ */
+static int check_refname_component(const char *ref, int flags)
 {
-       int ch, level, bad_type, last;
-       int ret = CHECK_REF_FORMAT_OK;
-       const char *cp = ref;
-
-       level = 0;
-       while (1) {
-               while ((ch = *cp++) == '/')
-                       ; /* tolerate duplicated slashes */
-               if (!ch)
-                       /* should not end with slashes */
-                       return CHECK_REF_FORMAT_ERROR;
-
-               /* we are at the beginning of the path component */
-               if (ch == '.')
-                       return CHECK_REF_FORMAT_ERROR;
-               bad_type = bad_ref_char(ch);
-               if (bad_type) {
-                       if (bad_type == 2 && (!*cp || *cp == '/') &&
-                           ret == CHECK_REF_FORMAT_OK)
-                               ret = CHECK_REF_FORMAT_WILDCARD;
-                       else
-                               return CHECK_REF_FORMAT_ERROR;
-               }
+       const char *cp;
+       char last = '\0';
 
+       for (cp = ref; ; cp++) {
+               char ch = *cp;
+               if (ch == '\0' || ch == '/')
+                       break;
+               if (bad_ref_char(ch))
+                       return -1; /* Illegal character in refname. */
+               if (last == '.' && ch == '.')
+                       return -1; /* Refname contains "..". */
+               if (last == '@' && ch == '{')
+                       return -1; /* Refname contains "@{". */
                last = ch;
-               /* scan the rest of the path component */
-               while ((ch = *cp++) != 0) {
-                       bad_type = bad_ref_char(ch);
-                       if (bad_type)
-                               return CHECK_REF_FORMAT_ERROR;
-                       if (ch == '/')
-                               break;
-                       if (last == '.' && ch == '.')
-                               return CHECK_REF_FORMAT_ERROR;
-                       if (last == '@' && ch == '{')
-                               return CHECK_REF_FORMAT_ERROR;
-                       last = ch;
-               }
-               level++;
-               if (!ch) {
-                       if (ref <= cp - 2 && cp[-2] == '.')
-                               return CHECK_REF_FORMAT_ERROR;
-                       if (level < 2)
-                               return CHECK_REF_FORMAT_ONELEVEL;
-                       if (has_extension(ref, ".lock"))
-                               return CHECK_REF_FORMAT_ERROR;
-                       return ret;
+       }
+       if (cp == ref)
+               return -1; /* Component has zero length. */
+       if (ref[0] == '.') {
+               if (!(flags & REFNAME_DOT_COMPONENT))
+                       return -1; /* Component starts with '.'. */
+               /*
+                * Even if leading dots are allowed, don't allow "."
+                * as a component (".." is prevented by a rule above).
+                */
+               if (ref[1] == '\0')
+                       return -1; /* Component equals ".". */
+       }
+       if (cp - ref >= 5 && !memcmp(cp - 5, ".lock", 5))
+               return -1; /* Refname ends with ".lock". */
+       return cp - ref;
+}
+
+int check_refname_format(const char *ref, int flags)
+{
+       int component_len, component_count = 0;
+
+       while (1) {
+               /* We are at the start of a path component. */
+               component_len = check_refname_component(ref, flags);
+               if (component_len < 0) {
+                       if ((flags & REFNAME_REFSPEC_PATTERN) &&
+                                       ref[0] == '*' &&
+                                       (ref[1] == '\0' || ref[1] == '/')) {
+                               /* Accept one wildcard as a full refname component. */
+                               flags &= ~REFNAME_REFSPEC_PATTERN;
+                               component_len = 1;
+                       } else {
+                               return -1;
+                       }
                }
+               component_count++;
+               if (ref[component_len] == '\0')
+                       break;
+               /* Skip to next component. */
+               ref += component_len + 1;
        }
+
+       if (ref[component_len - 1] == '.')
+               return -1; /* Refname ends with '.'. */
+       if (!(flags & REFNAME_ALLOW_ONELEVEL) && component_count < 2)
+               return -1; /* Refname has only one component. */
+       return 0;
 }
 
 const char *prettify_refname(const char *name)
@@ -1003,28 +1056,116 @@ static int remove_empty_directories(const char *file)
 }
 
 static int is_refname_available(const char *ref, const char *oldref,
-                               struct ref_list *list, int quiet)
-{
-       int namlen = strlen(ref); /* e.g. 'foo/bar' */
-       while (list) {
-               /* list->name could be 'foo' or 'foo/bar/baz' */
-               if (!oldref || strcmp(oldref, list->name)) {
-                       int len = strlen(list->name);
+                               struct ref_array *array, int quiet)
+{
+       int i, namlen = strlen(ref); /* e.g. 'foo/bar' */
+       for (i = 0; i < array->nr; i++ ) {
+               struct ref_entry *entry = array->refs[i];
+               /* entry->name could be 'foo' or 'foo/bar/baz' */
+               if (!oldref || strcmp(oldref, entry->name)) {
+                       int len = strlen(entry->name);
                        int cmplen = (namlen < len) ? namlen : len;
-                       const char *lead = (namlen < len) ? list->name : ref;
-                       if (!strncmp(ref, list->name, cmplen) &&
+                       const char *lead = (namlen < len) ? entry->name : ref;
+                       if (!strncmp(ref, entry->name, cmplen) &&
                            lead[cmplen] == '/') {
                                if (!quiet)
                                        error("'%s' exists; cannot create '%s'",
-                                             list->name, ref);
+                                             entry->name, ref);
                                return 0;
                        }
                }
-               list = list->next;
        }
        return 1;
 }
 
+/*
+ * *string and *len will only be substituted, and *string returned (for
+ * later free()ing) if the string passed in is a magic short-hand form
+ * to name a branch.
+ */
+static char *substitute_branch_name(const char **string, int *len)
+{
+       struct strbuf buf = STRBUF_INIT;
+       int ret = interpret_branch_name(*string, &buf);
+
+       if (ret == *len) {
+               size_t size;
+               *string = strbuf_detach(&buf, &size);
+               *len = size;
+               return (char *)*string;
+       }
+
+       return NULL;
+}
+
+int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
+{
+       char *last_branch = substitute_branch_name(&str, &len);
+       const char **p, *r;
+       int refs_found = 0;
+
+       *ref = NULL;
+       for (p = ref_rev_parse_rules; *p; p++) {
+               char fullref[PATH_MAX];
+               unsigned char sha1_from_ref[20];
+               unsigned char *this_result;
+               int flag;
+
+               this_result = refs_found ? sha1_from_ref : sha1;
+               mksnpath(fullref, sizeof(fullref), *p, len, str);
+               r = resolve_ref(fullref, this_result, 1, &flag);
+               if (r) {
+                       if (!refs_found++)
+                               *ref = xstrdup(r);
+                       if (!warn_ambiguous_refs)
+                               break;
+               } else if ((flag & REF_ISSYMREF) && strcmp(fullref, "HEAD")) {
+                       warning("ignoring dangling symref %s.", fullref);
+               } else if ((flag & REF_ISBROKEN) && strchr(fullref, '/')) {
+                       warning("ignoring broken ref %s.", fullref);
+               }
+       }
+       free(last_branch);
+       return refs_found;
+}
+
+int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
+{
+       char *last_branch = substitute_branch_name(&str, &len);
+       const char **p;
+       int logs_found = 0;
+
+       *log = NULL;
+       for (p = ref_rev_parse_rules; *p; p++) {
+               struct stat st;
+               unsigned char hash[20];
+               char path[PATH_MAX];
+               const char *ref, *it;
+
+               mksnpath(path, sizeof(path), *p, len, str);
+               ref = resolve_ref(path, hash, 1, NULL);
+               if (!ref)
+                       continue;
+               if (!stat(git_path("logs/%s", path), &st) &&
+                   S_ISREG(st.st_mode))
+                       it = path;
+               else if (strcmp(ref, path) &&
+                        !stat(git_path("logs/%s", ref), &st) &&
+                        S_ISREG(st.st_mode))
+                       it = ref;
+               else
+                       continue;
+               if (!logs_found++) {
+                       *log = xstrdup(it);
+                       hashcpy(sha1, hash);
+               }
+               if (!warn_ambiguous_refs)
+                       break;
+       }
+       free(last_branch);
+       return logs_found;
+}
+
 static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int flags, int *type_p)
 {
        char *ref_file;
@@ -1106,7 +1247,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char
 struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1)
 {
        char refpath[PATH_MAX];
-       if (check_ref_format(ref))
+       if (check_refname_format(ref, 0))
                return NULL;
        strcpy(refpath, mkpath("refs/%s", ref));
        return lock_ref_sha1_basic(refpath, old_sha1, 0, NULL);
@@ -1114,31 +1255,22 @@ struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1)
 
 struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int flags)
 {
-       switch (check_ref_format(ref)) {
-       default:
+       if (check_refname_format(ref, REFNAME_ALLOW_ONELEVEL))
                return NULL;
-       case 0:
-       case CHECK_REF_FORMAT_ONELEVEL:
-               return lock_ref_sha1_basic(ref, old_sha1, flags, NULL);
-       }
+       return lock_ref_sha1_basic(ref, old_sha1, flags, NULL);
 }
 
 static struct lock_file packlock;
 
 static int repack_without_ref(const char *refname)
 {
-       struct ref_list *list, *packed_ref_list;
-       int fd;
-       int found = 0;
+       struct ref_array *packed;
+       struct ref_entry *ref;
+       int fd, i;
 
-       packed_ref_list = get_packed_refs(NULL);
-       for (list = packed_ref_list; list; list = list->next) {
-               if (!strcmp(refname, list->name)) {
-                       found = 1;
-                       break;
-               }
-       }
-       if (!found)
+       packed = get_packed_refs(NULL);
+       ref = search_ref_array(packed, refname);
+       if (ref == NULL)
                return 0;
        fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0);
        if (fd < 0) {
@@ -1146,17 +1278,19 @@ static int repack_without_ref(const char *refname)
                return error("cannot delete '%s' from packed refs", refname);
        }
 
-       for (list = packed_ref_list; list; list = list->next) {
+       for (i = 0; i < packed->nr; i++) {
                char line[PATH_MAX + 100];
                int len;
 
-               if (!strcmp(refname, list->name))
+               ref = packed->refs[i];
+
+               if (!strcmp(refname, ref->name))
                        continue;
                len = snprintf(line, sizeof(line), "%s %s\n",
-                              sha1_to_hex(list->sha1), list->name);
+                              sha1_to_hex(ref->sha1), ref->name);
                /* this should not happen but just being defensive */
                if (len > sizeof(line))
-                       die("too long a refname '%s'", list->name);
+                       die("too long a refname '%s'", ref->name);
                write_or_die(fd, line, len);
        }
        return commit_lock_file(&packlock);
@@ -1195,7 +1329,7 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
        ret |= repack_without_ref(refname);
 
        unlink_or_warn(git_path("logs/%s", lock->ref_name));
-       invalidate_cached_refs();
+       invalidate_ref_cache(NULL);
        unlock_ref(lock);
        return ret;
 }
@@ -1211,7 +1345,6 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
 
 int rename_ref(const char *oldref, const char *newref, const char *logmsg)
 {
-       static const char renamed_ref[] = "RENAMED-REF";
        unsigned char sha1[20], orig_sha1[20];
        int flag = 0, logmoved = 0;
        struct ref_lock *lock;
@@ -1235,13 +1368,6 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
        if (!is_refname_available(newref, oldref, get_loose_refs(NULL), 0))
                return 1;
 
-       lock = lock_ref_sha1_basic(renamed_ref, NULL, 0, NULL);
-       if (!lock)
-               return error("unable to lock %s", renamed_ref);
-       lock->force_write = 1;
-       if (write_ref_sha1(lock, orig_sha1, logmsg))
-               return error("unable to save current sha1 in %s", renamed_ref);
-
        if (log && rename(git_path("logs/%s", oldref), git_path(TMP_RENAMED_LOG)))
                return error("unable to move logfile logs/%s to "TMP_RENAMED_LOG": %s",
                        oldref, strerror(errno));
@@ -1494,7 +1620,7 @@ int write_ref_sha1(struct ref_lock *lock,
                unlock_ref(lock);
                return -1;
        }
-       invalidate_cached_refs();
+       clear_loose_ref_cache(get_ref_cache(NULL));
        if (log_ref_write(lock->ref_name, lock->old_sha1, sha1, logmsg) < 0 ||
            (strcmp(lock->ref_name, lock->orig_ref_name) &&
             log_ref_write(lock->orig_ref_name, lock->old_sha1, sha1, logmsg) < 0)) {
@@ -1851,7 +1977,7 @@ int update_ref(const char *action, const char *refname,
        return 0;
 }
 
-int ref_exists(char *refname)
+int ref_exists(const char *refname)
 {
        unsigned char sha1[20];
        return !!resolve_ref(refname, sha1, 1, NULL);
diff --git a/refs.h b/refs.h
index dfb086e93346aa3fb6f600b2ed07b5736716bf9c..3fd55369f90829706c3f4b11329fadc38380da32 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -10,8 +10,9 @@ struct ref_lock {
        int force_write;
 };
 
-#define REF_ISSYMREF 01
-#define REF_ISPACKED 02
+#define REF_ISSYMREF 0x01
+#define REF_ISPACKED 0x02
+#define REF_ISBROKEN 0x04
 
 /*
  * Calls the specified function for each ref file until it returns nonzero,
@@ -57,7 +58,7 @@ extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refn
  */
 extern void add_extra_ref(const char *refname, const unsigned char *sha1, int flags);
 extern void clear_extra_refs(void);
-extern int ref_exists(char *);
+extern int ref_exists(const char *);
 
 extern int peel_ref(const char *, unsigned char *);
 
@@ -80,6 +81,14 @@ extern void unlock_ref(struct ref_lock *lock);
 /** Writes sha1 into the ref specified by the lock. **/
 extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);
 
+/*
+ * Invalidate the reference cache for the specified submodule.  Use
+ * submodule=NULL to invalidate the cache for the main module.  This
+ * function must be called if references are changed via a mechanism
+ * other than the refs API.
+ */
+extern void invalidate_ref_cache(const char *submodule);
+
 /** Setup reflog before using. **/
 int log_ref_setup(const char *ref_name, char *logfile, int bufsize);
 
@@ -97,11 +106,22 @@ int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long, voi
  */
 extern int for_each_reflog(each_ref_fn, void *);
 
-#define CHECK_REF_FORMAT_OK 0
-#define CHECK_REF_FORMAT_ERROR (-1)
-#define CHECK_REF_FORMAT_ONELEVEL (-2)
-#define CHECK_REF_FORMAT_WILDCARD (-3)
-extern int check_ref_format(const char *target);
+#define REFNAME_ALLOW_ONELEVEL 1
+#define REFNAME_REFSPEC_PATTERN 2
+#define REFNAME_DOT_COMPONENT 4
+
+/*
+ * Return 0 iff ref has the correct format for a refname according to
+ * the rules described in Documentation/git-check-ref-format.txt.  If
+ * REFNAME_ALLOW_ONELEVEL is set in flags, then accept one-level
+ * reference names.  If REFNAME_REFSPEC_PATTERN is set in flags, then
+ * allow a "*" wildcard character in place of one of the name
+ * components.  No leading or repeated slashes are accepted.  If
+ * REFNAME_DOT_COMPONENT is set in flags, then allow refname
+ * components to start with "." (but not a whole component equal to
+ * "." or "..").
+ */
+extern int check_refname_format(const char *ref, int flags);
 
 extern const char *prettify_refname(const char *refname);
 extern char *shorten_unambiguous_ref(const char *ref, int strict);
index b8cf45a7dd439b83c80bcf7a397e1b8e34c70f67..0e720ee8bbf4cbc6a50336a1f1c93bfc63842fe3 100644 (file)
@@ -115,7 +115,7 @@ static struct discovery* discover_refs(const char *service)
        http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE);
 
        /* try again with "plain" url (no ? or & appended) */
-       if (http_ret != HTTP_OK) {
+       if (http_ret != HTTP_OK && http_ret != HTTP_NOAUTH) {
                free(refs_url);
                strbuf_reset(&buffer);
 
@@ -573,7 +573,14 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads)
 
        close(client.in);
        client.in = -1;
-       strbuf_read(&rpc->result, client.out, 0);
+       if (!err) {
+               strbuf_read(&rpc->result, client.out, 0);
+       } else {
+               char buf[4096];
+               for (;;)
+                       if (xread(client.out, buf, sizeof(buf)) <= 0)
+                               break;
+       }
 
        close(client.out);
        client.out = -1;
@@ -852,7 +859,7 @@ int main(int argc, const char **argv)
 
        url = strbuf_detach(&buf, NULL);
 
-       http_init(remote);
+       http_init(remote, url);
 
        do {
                if (strbuf_getline(&buf, stdin, '\n') == EOF) {
index b8ecfa5d9558e3824872ad6778fc344681cc8d5c..e2ef99114478c49863809ea5cef46392a5a8c0e9 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -492,23 +492,6 @@ static void read_config(void)
        alias_all_urls();
 }
 
-/*
- * We need to make sure the remote-tracking branches are well formed, but a
- * wildcard refspec in "struct refspec" must have a trailing slash. We
- * temporarily drop the trailing '/' while calling check_ref_format(),
- * and put it back.  The caller knows that a CHECK_REF_FORMAT_ONELEVEL
- * error return is Ok for a wildcard refspec.
- */
-static int verify_refname(char *name, int is_glob)
-{
-       int result;
-
-       result = check_ref_format(name);
-       if (is_glob && result == CHECK_REF_FORMAT_WILDCARD)
-               result = CHECK_REF_FORMAT_OK;
-       return result;
-}
-
 /*
  * This function frees a refspec array.
  * Warning: code paths should be checked to ensure that the src
@@ -532,13 +515,13 @@ static void free_refspecs(struct refspec *refspec, int nr_refspec)
 static struct refspec *parse_refspec_internal(int nr_refspec, const char **refspec, int fetch, int verify)
 {
        int i;
-       int st;
        struct refspec *rs = xcalloc(sizeof(*rs), nr_refspec);
 
        for (i = 0; i < nr_refspec; i++) {
                size_t llen;
                int is_glob;
                const char *lhs, *rhs;
+               int flags;
 
                is_glob = 0;
 
@@ -576,6 +559,7 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp
 
                rs[i].pattern = is_glob;
                rs[i].src = xstrndup(lhs, llen);
+               flags = REFNAME_ALLOW_ONELEVEL | (is_glob ? REFNAME_REFSPEC_PATTERN : 0);
 
                if (fetch) {
                        /*
@@ -585,26 +569,20 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp
                         */
                        if (!*rs[i].src)
                                ; /* empty is ok */
-                       else {
-                               st = verify_refname(rs[i].src, is_glob);
-                               if (st && st != CHECK_REF_FORMAT_ONELEVEL)
-                                       goto invalid;
-                       }
+                       else if (check_refname_format(rs[i].src, flags))
+                               goto invalid;
                        /*
                         * RHS
                         * - missing is ok, and is same as empty.
                         * - empty is ok; it means not to store.
                         * - otherwise it must be a valid looking ref.
                         */
-                       if (!rs[i].dst) {
+                       if (!rs[i].dst)
                                ; /* ok */
-                       } else if (!*rs[i].dst) {
+                       else if (!*rs[i].dst)
                                ; /* ok */
-                       } else {
-                               st = verify_refname(rs[i].dst, is_glob);
-                               if (st && st != CHECK_REF_FORMAT_ONELEVEL)
-                                       goto invalid;
-                       }
+                       else if (check_refname_format(rs[i].dst, flags))
+                               goto invalid;
                } else {
                        /*
                         * LHS
@@ -616,8 +594,7 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp
                        if (!*rs[i].src)
                                ; /* empty is ok */
                        else if (is_glob) {
-                               st = verify_refname(rs[i].src, is_glob);
-                               if (st && st != CHECK_REF_FORMAT_ONELEVEL)
+                               if (check_refname_format(rs[i].src, flags))
                                        goto invalid;
                        }
                        else
@@ -630,14 +607,12 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp
                         * - otherwise it must be a valid looking ref.
                         */
                        if (!rs[i].dst) {
-                               st = verify_refname(rs[i].src, is_glob);
-                               if (st && st != CHECK_REF_FORMAT_ONELEVEL)
+                               if (check_refname_format(rs[i].src, flags))
                                        goto invalid;
                        } else if (!*rs[i].dst) {
                                goto invalid;
                        } else {
-                               st = verify_refname(rs[i].dst, is_glob);
-                               if (st && st != CHECK_REF_FORMAT_ONELEVEL)
+                               if (check_refname_format(rs[i].dst, flags))
                                        goto invalid;
                        }
                }
@@ -828,59 +803,56 @@ static int match_name_with_pattern(const char *key, const char *name,
        return ret;
 }
 
-char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
-                    const char *name)
+static int query_refspecs(struct refspec *refs, int ref_count, struct refspec *query)
 {
        int i;
-       char *ret = NULL;
-       for (i = 0; i < nr_refspec; i++) {
-               struct refspec *refspec = refspecs + i;
-               if (refspec->pattern) {
-                       if (match_name_with_pattern(refspec->src, name,
-                                                   refspec->dst, &ret))
-                               return ret;
-               } else if (!strcmp(refspec->src, name))
-                       return strdup(refspec->dst);
-       }
-       return NULL;
-}
+       int find_src = !query->src;
 
-int remote_find_tracking(struct remote *remote, struct refspec *refspec)
-{
-       int find_src = refspec->src == NULL;
-       char *needle, **result;
-       int i;
+       if (find_src && !query->dst)
+               return error("query_refspecs: need either src or dst");
 
-       if (find_src) {
-               if (!refspec->dst)
-                       return error("find_tracking: need either src or dst");
-               needle = refspec->dst;
-               result = &refspec->src;
-       } else {
-               needle = refspec->src;
-               result = &refspec->dst;
-       }
+       for (i = 0; i < ref_count; i++) {
+               struct refspec *refspec = &refs[i];
+               const char *key = find_src ? refspec->dst : refspec->src;
+               const char *value = find_src ? refspec->src : refspec->dst;
+               const char *needle = find_src ? query->dst : query->src;
+               char **result = find_src ? &query->src : &query->dst;
 
-       for (i = 0; i < remote->fetch_refspec_nr; i++) {
-               struct refspec *fetch = &remote->fetch[i];
-               const char *key = find_src ? fetch->dst : fetch->src;
-               const char *value = find_src ? fetch->src : fetch->dst;
-               if (!fetch->dst)
+               if (!refspec->dst)
                        continue;
-               if (fetch->pattern) {
+               if (refspec->pattern) {
                        if (match_name_with_pattern(key, needle, value, result)) {
-                               refspec->force = fetch->force;
+                               query->force = refspec->force;
                                return 0;
                        }
                } else if (!strcmp(needle, key)) {
                        *result = xstrdup(value);
-                       refspec->force = fetch->force;
+                       query->force = refspec->force;
                        return 0;
                }
        }
        return -1;
 }
 
+char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
+                    const char *name)
+{
+       struct refspec query;
+
+       memset(&query, 0, sizeof(struct refspec));
+       query.src = (char *)name;
+
+       if (query_refspecs(refspecs, nr_refspec, &query))
+               return NULL;
+
+       return query.dst;
+}
+
+int remote_find_tracking(struct remote *remote, struct refspec *refspec)
+{
+       return query_refspecs(remote->fetch, remote->fetch_refspec_nr, refspec);
+}
+
 static struct ref *alloc_ref_with_prefix(const char *prefix, size_t prefixlen,
                const char *name)
 {
@@ -1170,12 +1142,15 @@ static struct ref **tail_ref(struct ref **head)
 }
 
 /*
- * Note. This is used only by "push"; refspec matching rules for
- * push and fetch are subtly different, so do not try to reuse it
- * without thinking.
+ * Given the set of refs the local repository has, the set of refs the
+ * remote repository has, and the refspec used for push, determine
+ * what remote refs we will update and with what value by setting
+ * peer_ref (which object is being pushed) and force (if the push is
+ * forced) in elements of "dst". The function may add new elements to
+ * dst (e.g. pushing to a new branch, done in match_explicit_refs).
  */
-int match_refs(struct ref *src, struct ref **dst,
-              int nr_refspec, const char **refspec, int flags)
+int match_push_refs(struct ref *src, struct ref **dst,
+                   int nr_refspec, const char **refspec, int flags)
 {
        struct refspec *rs;
        int send_all = flags & MATCH_REFS_ALL;
@@ -1427,8 +1402,8 @@ int get_fetch_map(const struct ref *remote_refs,
 
        for (rmp = &ref_map; *rmp; ) {
                if ((*rmp)->peer_ref) {
-                       int st = check_ref_format((*rmp)->peer_ref->name + 5);
-                       if (st && st != CHECK_REF_FORMAT_ONELEVEL) {
+                       if (check_refname_format((*rmp)->peer_ref->name + 5,
+                               REFNAME_ALLOW_ONELEVEL)) {
                                struct ref *ignore = *rmp;
                                error("* Ignoring funny ref '%s' locally",
                                      (*rmp)->peer_ref->name);
@@ -1620,7 +1595,7 @@ static int one_local_ref(const char *refname, const unsigned char *sha1, int fla
        int len;
 
        /* we already know it starts with refs/ to get here */
-       if (check_ref_format(refname + 5))
+       if (check_refname_format(refname + 5, 0))
                return 0;
 
        len = strlen(refname) + 1;
@@ -1681,36 +1656,47 @@ struct ref *guess_remote_head(const struct ref *head,
 }
 
 struct stale_heads_info {
-       struct remote *remote;
        struct string_list *ref_names;
        struct ref **stale_refs_tail;
+       struct refspec *refs;
+       int ref_count;
 };
 
 static int get_stale_heads_cb(const char *refname,
        const unsigned char *sha1, int flags, void *cb_data)
 {
        struct stale_heads_info *info = cb_data;
-       struct refspec refspec;
-       memset(&refspec, 0, sizeof(refspec));
-       refspec.dst = (char *)refname;
-       if (!remote_find_tracking(info->remote, &refspec)) {
-               if (!((flags & REF_ISSYMREF) ||
-                   string_list_has_string(info->ref_names, refspec.src))) {
-                       struct ref *ref = make_linked_ref(refname, &info->stale_refs_tail);
-                       hashcpy(ref->new_sha1, sha1);
-               }
+       struct refspec query;
+       memset(&query, 0, sizeof(struct refspec));
+       query.dst = (char *)refname;
+
+       if (query_refspecs(info->refs, info->ref_count, &query))
+               return 0; /* No matches */
+
+       /*
+        * If we did find a suitable refspec and it's not a symref and
+        * it's not in the list of refs that currently exist in that
+        * remote we consider it to be stale.
+        */
+       if (!((flags & REF_ISSYMREF) ||
+             string_list_has_string(info->ref_names, query.src))) {
+               struct ref *ref = make_linked_ref(refname, &info->stale_refs_tail);
+               hashcpy(ref->new_sha1, sha1);
        }
+
+       free(query.src);
        return 0;
 }
 
-struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map)
+struct ref *get_stale_heads(struct refspec *refs, int ref_count, struct ref *fetch_map)
 {
        struct ref *ref, *stale_refs = NULL;
        struct string_list ref_names = STRING_LIST_INIT_NODUP;
        struct stale_heads_info info;
-       info.remote = remote;
        info.ref_names = &ref_names;
        info.stale_refs_tail = &stale_refs;
+       info.refs = refs;
+       info.ref_count = ref_count;
        for (ref = fetch_map; ref; ref = ref->next)
                string_list_append(&ref_names, ref->name);
        sort_string_list(&ref_names);
index 9a30a9dba64825950f7a0448ad8b64581c0b8fae..b3955983ba5caea698a78868abcbb54451b6daa8 100644 (file)
--- a/remote.h
+++ b/remote.h
@@ -96,8 +96,8 @@ void free_refspec(int nr_refspec, struct refspec *refspec);
 char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
                     const char *name);
 
-int match_refs(struct ref *src, struct ref **dst,
-              int nr_refspec, const char **refspec, int all);
+int match_push_refs(struct ref *src, struct ref **dst,
+                   int nr_refspec, const char **refspec, int all);
 void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
        int force_update);
 
@@ -164,6 +164,6 @@ struct ref *guess_remote_head(const struct ref *head,
                              int all);
 
 /* Return refs which no longer exist on remote */
-struct ref *get_stale_heads(struct remote *remote, struct ref *fetch_map);
+struct ref *get_stale_heads(struct refspec *refs, int ref_count, struct ref *fetch_map);
 
 #endif
index 9bae329c153f33b5aa39f2dab2748dc56b2c3824..8764dde381111cfc9c8ea7eb3856223de9786ec9 100644 (file)
@@ -226,6 +226,13 @@ static struct object *get_reference(struct rev_info *revs, const char *name, con
        return object;
 }
 
+void add_pending_sha1(struct rev_info *revs, const char *name,
+                     const unsigned char *sha1, unsigned int flags)
+{
+       struct object *object = get_reference(revs, name, sha1, flags);
+       add_pending_object(revs, object, name);
+}
+
 static struct commit *handle_commit(struct rev_info *revs, struct object *object, const char *name)
 {
        unsigned long flags = object->flags;
@@ -897,7 +904,7 @@ static int handle_one_ref(const char *path, const unsigned char *sha1, int flag,
        struct object *object = get_reference(cb->all_revs, path, sha1,
                                              cb->all_flags);
        add_rev_cmdline(cb->all_revs, object, path, REV_CMD_REF, cb->all_flags);
-       add_pending_object(cb->all_revs, object, path);
+       add_pending_sha1(cb->all_revs, path, sha1, cb->all_flags);
        return 0;
 }
 
@@ -1052,10 +1059,12 @@ static void prepare_show_merge(struct rev_info *revs)
        const char **prune = NULL;
        int i, prune_num = 1; /* counting terminating NULL */
 
-       if (get_sha1("HEAD", sha1) || !(head = lookup_commit(sha1)))
+       if (get_sha1("HEAD", sha1))
                die("--merge without HEAD?");
-       if (get_sha1("MERGE_HEAD", sha1) || !(other = lookup_commit(sha1)))
+       head = lookup_commit_or_die(sha1, "HEAD");
+       if (get_sha1("MERGE_HEAD", sha1))
                die("--merge without MERGE_HEAD?");
+       other = lookup_commit_or_die(sha1, "MERGE_HEAD");
        add_pending_object(revs, &head->object, "HEAD");
        add_pending_object(revs, &other->object, "MERGE_HEAD");
        bases = get_merge_bases(head, other, 1);
@@ -2048,7 +2057,8 @@ int prepare_revision_walk(struct rev_info *revs)
                }
                e++;
        }
-       free(list);
+       if (!revs->leak_pending)
+               free(list);
 
        if (revs->no_walk)
                return 0;
index 754f31b1cda81c474f71ab56f8d82e260adee968..6aa53d1aa708918e4bbbebb042e0bf75c1629b05 100644 (file)
@@ -118,6 +118,7 @@ struct rev_info {
                        date_mode_explicit:1,
                        preserve_subject:1;
        unsigned int    disable_stdin:1;
+       unsigned int    leak_pending:1;
 
        enum date_mode date_mode;
 
@@ -214,6 +215,7 @@ extern void add_object(struct object *obj,
                       const char *name);
 
 extern void add_pending_object(struct rev_info *revs, struct object *obj, const char *name);
+extern void add_pending_sha1(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags);
 
 extern void add_head_to_pending(struct rev_info *);
 
index a2796c4caecc0fae6ea1a95b2c965b92d98f8a31..1c5104388429e50722769358d5d89948c55bb3aa 100644 (file)
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "run-command.h"
 #include "exec_cmd.h"
+#include "argv-array.h"
 
 static inline void close_pair(int fd[2])
 {
@@ -605,26 +606,23 @@ int finish_async(struct async *async)
 int run_hook(const char *index_file, const char *name, ...)
 {
        struct child_process hook;
-       const char **argv = NULL, *env[2];
+       struct argv_array argv = ARGV_ARRAY_INIT;
+       const char *p, *env[2];
        char index[PATH_MAX];
        va_list args;
        int ret;
-       size_t i = 0, alloc = 0;
 
        if (access(git_path("hooks/%s", name), X_OK) < 0)
                return 0;
 
        va_start(args, name);
-       ALLOC_GROW(argv, i + 1, alloc);
-       argv[i++] = git_path("hooks/%s", name);
-       while (argv[i-1]) {
-               ALLOC_GROW(argv, i + 1, alloc);
-               argv[i++] = va_arg(args, const char *);
-       }
+       argv_array_push(&argv, git_path("hooks/%s", name));
+       while ((p = va_arg(args, const char *)))
+               argv_array_push(&argv, p);
        va_end(args);
 
        memset(&hook, 0, sizeof(hook));
-       hook.argv = argv;
+       hook.argv = argv.argv;
        hook.no_stdin = 1;
        hook.stdout_to_stderr = 1;
        if (index_file) {
@@ -635,6 +633,6 @@ int run_hook(const char *index_file, const char *name, ...)
        }
 
        ret = run_command(&hook);
-       free(argv);
+       argv_array_clear(&argv);
        return ret;
 }
index 905d29501213f058f0d8cc88e7ba57527d506483..f435fdb4b147d2e2ce5480ddb4832228af1cb73b 100644 (file)
@@ -13,7 +13,7 @@
  *
  * With the aggressive flag, it additionally removes SEQ_OLD_DIR,
  * ignoring any errors.  Inteded to be used by the sequencer's
- * '--reset' subcommand.
+ * '--quit' subcommand.
  */
 void remove_sequencer_state(int aggressive);
 
diff --git a/setup.c b/setup.c
index 27c1d4787a2c2efd8420a225b43e36b76464ce42..61c22e6becc1e49f1e92c916a4b8badd30a9cb2f 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -236,38 +236,6 @@ const char **get_pathspec(const char *prefix, const char **pathspec)
        return pathspec;
 }
 
-const char *pathspec_prefix(const char *prefix, const char **pathspec)
-{
-       const char **p, *n, *prev;
-       unsigned long max;
-
-       if (!pathspec)
-               return prefix ? xmemdupz(prefix, strlen(prefix)) : NULL;
-
-       prev = NULL;
-       max = PATH_MAX;
-       for (p = pathspec; (n = *p) != NULL; p++) {
-               int i, len = 0;
-               for (i = 0; i < max; i++) {
-                       char c = n[i];
-                       if (prev && prev[i] != c)
-                               break;
-                       if (!c || c == '*' || c == '?')
-                               break;
-                       if (c == '/')
-                               len = i+1;
-               }
-               prev = n;
-               if (len < max) {
-                       max = len;
-                       if (!max)
-                               break;
-               }
-       }
-
-       return max ? xmemdupz(prev, max) : NULL;
-}
-
 /*
  * Test if it looks like we're at a git directory.
  * We want to see:
@@ -812,3 +780,10 @@ const char *setup_git_directory(void)
 {
        return setup_git_directory_gently(NULL);
 }
+
+const char *resolve_gitdir(const char *suspect)
+{
+       if (is_git_directory(suspect))
+               return suspect;
+       return read_gitfile(suspect);
+}
index e002056b85ce89c089dd09c1300461d60d81dffa..956422ba4a5df5f46caaf85f10e4c85321439272 100644 (file)
@@ -248,27 +248,30 @@ static int link_alt_odb_entry(const char * entry, int len, const char * relative
        const char *objdir = get_object_directory();
        struct alternate_object_database *ent;
        struct alternate_object_database *alt;
-       /* 43 = 40-byte + 2 '/' + terminating NUL */
-       int pfxlen = len;
-       int entlen = pfxlen + 43;
-       int base_len = -1;
+       int pfxlen, entlen;
+       struct strbuf pathbuf = STRBUF_INIT;
 
        if (!is_absolute_path(entry) && relative_base) {
-               /* Relative alt-odb */
-               if (base_len < 0)
-                       base_len = strlen(relative_base) + 1;
-               entlen += base_len;
-               pfxlen += base_len;
+               strbuf_addstr(&pathbuf, real_path(relative_base));
+               strbuf_addch(&pathbuf, '/');
        }
-       ent = xmalloc(sizeof(*ent) + entlen);
+       strbuf_add(&pathbuf, entry, len);
 
-       if (!is_absolute_path(entry) && relative_base) {
-               memcpy(ent->base, relative_base, base_len - 1);
-               ent->base[base_len - 1] = '/';
-               memcpy(ent->base + base_len, entry, len);
-       }
-       else
-               memcpy(ent->base, entry, pfxlen);
+       normalize_path_copy(pathbuf.buf, pathbuf.buf);
+
+       pfxlen = strlen(pathbuf.buf);
+
+       /*
+        * The trailing slash after the directory name is given by
+        * this function at the end. Remove duplicates.
+        */
+       while (pfxlen && pathbuf.buf[pfxlen-1] == '/')
+               pfxlen -= 1;
+
+       entlen = pfxlen + 43; /* '/' + 2 hex + '/' + 38 hex + NUL */
+       ent = xmalloc(sizeof(*ent) + entlen);
+       memcpy(ent->base, pathbuf.buf, pfxlen);
+       strbuf_release(&pathbuf);
 
        ent->name = ent->base + pfxlen + 1;
        ent->base[pfxlen + 3] = '/';
@@ -1264,7 +1267,8 @@ unsigned long unpack_object_header_buffer(const unsigned char *buf,
        while (c & 0x80) {
                if (len <= used || bitsizeof(long) <= shift) {
                        error("bad object header");
-                       return 0;
+                       size = used = 0;
+                       break;
                }
                c = buf[used++];
                size += (c & 0x7f) << shift;
@@ -1984,7 +1988,7 @@ off_t find_pack_entry_one(const unsigned char *sha1,
        return 0;
 }
 
-static int is_pack_valid(struct packed_git *p)
+int is_pack_valid(struct packed_git *p)
 {
        /* An already open pack is known to be valid. */
        if (p->pack_fd != -1)
@@ -2035,7 +2039,7 @@ static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e)
                         * was loaded!
                         */
                        if (!is_pack_valid(p)) {
-                               error("packfile %s cannot be accessed", p->pack_name);
+                               warning("packfile %s cannot be accessed", p->pack_name);
                                goto next;
                        }
                        e->offset = offset;
@@ -2613,7 +2617,7 @@ static int index_mem(unsigned char *sha1, void *buf, size_t size,
        if ((type == OBJ_BLOB) && path) {
                struct strbuf nbuf = STRBUF_INIT;
                if (convert_to_git(path, buf, size, &nbuf,
-                                  write_object ? safe_crlf : 0)) {
+                                  write_object ? safe_crlf : SAFE_CRLF_FALSE)) {
                        buf = strbuf_detach(&nbuf, &size);
                        re_allocated = 1;
                }
index 653b0659be8416a220268ed2bc60694140c8d472..03ffc2caaa6524a3361bc47a89e101ced2f0e987 100644 (file)
@@ -241,91 +241,6 @@ static int ambiguous_path(const char *path, int len)
        return slash;
 }
 
-/*
- * *string and *len will only be substituted, and *string returned (for
- * later free()ing) if the string passed in is a magic short-hand form
- * to name a branch.
- */
-static char *substitute_branch_name(const char **string, int *len)
-{
-       struct strbuf buf = STRBUF_INIT;
-       int ret = interpret_branch_name(*string, &buf);
-
-       if (ret == *len) {
-               size_t size;
-               *string = strbuf_detach(&buf, &size);
-               *len = size;
-               return (char *)*string;
-       }
-
-       return NULL;
-}
-
-int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
-{
-       char *last_branch = substitute_branch_name(&str, &len);
-       const char **p, *r;
-       int refs_found = 0;
-
-       *ref = NULL;
-       for (p = ref_rev_parse_rules; *p; p++) {
-               char fullref[PATH_MAX];
-               unsigned char sha1_from_ref[20];
-               unsigned char *this_result;
-               int flag;
-
-               this_result = refs_found ? sha1_from_ref : sha1;
-               mksnpath(fullref, sizeof(fullref), *p, len, str);
-               r = resolve_ref(fullref, this_result, 1, &flag);
-               if (r) {
-                       if (!refs_found++)
-                               *ref = xstrdup(r);
-                       if (!warn_ambiguous_refs)
-                               break;
-               } else if ((flag & REF_ISSYMREF) && strcmp(fullref, "HEAD"))
-                       warning("ignoring dangling symref %s.", fullref);
-       }
-       free(last_branch);
-       return refs_found;
-}
-
-int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
-{
-       char *last_branch = substitute_branch_name(&str, &len);
-       const char **p;
-       int logs_found = 0;
-
-       *log = NULL;
-       for (p = ref_rev_parse_rules; *p; p++) {
-               struct stat st;
-               unsigned char hash[20];
-               char path[PATH_MAX];
-               const char *ref, *it;
-
-               mksnpath(path, sizeof(path), *p, len, str);
-               ref = resolve_ref(path, hash, 1, NULL);
-               if (!ref)
-                       continue;
-               if (!stat(git_path("logs/%s", path), &st) &&
-                   S_ISREG(st.st_mode))
-                       it = path;
-               else if (strcmp(ref, path) &&
-                        !stat(git_path("logs/%s", ref), &st) &&
-                        S_ISREG(st.st_mode))
-                       it = ref;
-               else
-                       continue;
-               if (!logs_found++) {
-                       *log = xstrdup(it);
-                       hashcpy(sha1, hash);
-               }
-               if (!warn_ambiguous_refs)
-                       break;
-       }
-       free(last_branch);
-       return logs_found;
-}
-
 static inline int upstream_mark(const char *string, int len)
 {
        const char *suffix[] = { "@{upstream}", "@{u}" };
@@ -966,9 +881,9 @@ int strbuf_check_branch_ref(struct strbuf *sb, const char *name)
 {
        strbuf_branchname(sb, name);
        if (name[0] == '-')
-               return CHECK_REF_FORMAT_ERROR;
+               return -1;
        strbuf_splice(sb, 0, 0, "refs/heads/", 11);
-       return check_ref_format(sb->buf);
+       return check_refname_format(sb->buf, 0);
 }
 
 /*
index 4c0ac138aff7b5add73e17fc9c3c4e409fd918e2..63f9da53237d4233bede66a28e4bcf27d5b44af1 100644 (file)
@@ -48,7 +48,7 @@ int main(int argc, char **argv)
                        unsigned char sha1[20];
                        uint32_t crc;
                        uint32_t off;
-               } *entries = malloc(nr * sizeof(entries[0]));
+               } *entries = xmalloc(nr * sizeof(entries[0]));
                for (i = 0; i < nr; i++)
                        if (fread(entries[i].sha1, 20, 1, stdin) != 1)
                                die("unable to read sha1 %u/%u", i, nr);
index 9ff1b597c995780026a32a92fab78a780d60329a..3ad2cc00160fbf24e1e4904bb37ce44e8c414ce5 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -357,7 +357,6 @@ int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)
 {
        int ch;
 
-       strbuf_grow(sb, 0);
        if (feof(fp))
                return EOF;
 
index 08756387e207700861cfa6dac039334ecd53c7a1..52cdcc6a6347a786deafad34efdb9dc4dc3670ff 100644 (file)
@@ -9,6 +9,7 @@
 #include "refs.h"
 #include "string-list.h"
 #include "sha1-array.h"
+#include "argv-array.h"
 
 static struct string_list config_name_for_path;
 static struct string_list config_fetch_recurse_submodules_for_name;
@@ -390,7 +391,7 @@ static void commit_need_pushing(struct commit *commit, struct commit_list *paren
        rev.diffopt.format_callback_data = needs_pushing;
        diff_tree_combined(commit->object.sha1, parents, n, 1, &rev);
 
-       free(parents);
+       free((void *)parents);
 }
 
 int check_submodule_needs_pushing(unsigned char new_sha1[20], const char *remotes_name)
@@ -496,56 +497,26 @@ void check_for_new_submodule_commits(unsigned char new_sha1[20])
        sha1_array_append(&ref_tips_after_fetch, new_sha1);
 }
 
-struct argv_array {
-       const char **argv;
-       unsigned int argc;
-       unsigned int alloc;
-};
-
-static void init_argv(struct argv_array *array)
-{
-       array->argv = NULL;
-       array->argc = 0;
-       array->alloc = 0;
-}
-
-static void push_argv(struct argv_array *array, const char *value)
-{
-       ALLOC_GROW(array->argv, array->argc + 2, array->alloc);
-       array->argv[array->argc++] = xstrdup(value);
-       array->argv[array->argc] = NULL;
-}
-
-static void clear_argv(struct argv_array *array)
-{
-       int i;
-       for (i = 0; i < array->argc; i++)
-               free((char **)array->argv[i]);
-       free(array->argv);
-       init_argv(array);
-}
-
 static void add_sha1_to_argv(const unsigned char sha1[20], void *data)
 {
-       push_argv(data, sha1_to_hex(sha1));
+       argv_array_push(data, sha1_to_hex(sha1));
 }
 
 static void calculate_changed_submodule_paths(void)
 {
        struct rev_info rev;
        struct commit *commit;
-       struct argv_array argv;
+       struct argv_array argv = ARGV_ARRAY_INIT;
 
        /* No need to check if there are no submodules configured */
        if (!config_name_for_path.nr)
                return;
 
        init_revisions(&rev, NULL);
-       init_argv(&argv);
-       push_argv(&argv, "--"); /* argv[0] program name */
+       argv_array_push(&argv, "--"); /* argv[0] program name */
        sha1_array_for_each_unique(&ref_tips_after_fetch,
                                   add_sha1_to_argv, &argv);
-       push_argv(&argv, "--not");
+       argv_array_push(&argv, "--not");
        sha1_array_for_each_unique(&ref_tips_before_fetch,
                                   add_sha1_to_argv, &argv);
        setup_revisions(argv.argc, argv.argv, &rev, NULL);
@@ -573,7 +544,7 @@ static void calculate_changed_submodule_paths(void)
                }
        }
 
-       clear_argv(&argv);
+       argv_array_clear(&argv);
        sha1_array_clear(&ref_tips_before_fetch);
        sha1_array_clear(&ref_tips_after_fetch);
        initialized_fetch_ref_tips = 0;
@@ -823,7 +794,7 @@ static void print_commit(struct commit *commit)
 
 int merge_submodule(unsigned char result[20], const char *path,
                    const unsigned char base[20], const unsigned char a[20],
-                   const unsigned char b[20])
+                   const unsigned char b[20], int search)
 {
        struct commit *commit_base, *commit_a, *commit_b;
        int parent_count;
@@ -878,6 +849,10 @@ int merge_submodule(unsigned char result[20], const char *path,
         * user needs to confirm the resolution.
         */
 
+       /* Skip the search if makes no sense to the calling context.  */
+       if (!search)
+               return 0;
+
        /* find commit which merges them */
        parent_count = find_first_merges(&merges, path, commit_a, commit_b);
        switch (parent_count) {
index 799c22d6c6a459756983420960bc099da20336b3..80e04f3c8cfe9a49865ef54f61efaa3bc67dca5c 100644 (file)
@@ -28,7 +28,7 @@ int fetch_populated_submodules(int num_options, const char **options,
                               int quiet);
 unsigned is_submodule_modified(const char *path, int ignore_untracked);
 int merge_submodule(unsigned char result[20], const char *path, const unsigned char base[20],
-                   const unsigned char a[20], const unsigned char b[20]);
+                   const unsigned char a[20], const unsigned char b[20], int search);
 int check_submodule_needs_pushing(unsigned char new_sha1[20], const char *remotes_name);
 
 #endif
index 292753f77c4daf5f3cb55de1c28269b13455544f..21d11d6c2d65982d94f933b2a673b744cbc2e28a 100644 (file)
@@ -16,6 +16,7 @@ our \$projectroot = "$safe_pwd";
 our \$project_maxdepth = 8;
 our \$home_link_str = 'projects';
 our \$site_name = '[localhost]';
+our \$site_html_head_string = '';
 our \$site_header = '';
 our \$site_footer = '';
 our \$home_text = 'indextext.html';
diff --git a/t/lib-git-p4.sh b/t/lib-git-p4.sh
new file mode 100644 (file)
index 0000000..a870f9a
--- /dev/null
@@ -0,0 +1,74 @@
+#
+# Library code for git-p4 tests
+#
+
+. ./test-lib.sh
+
+if ! test_have_prereq PYTHON; then
+       skip_all='skipping git-p4 tests; python not available'
+       test_done
+fi
+( p4 -h && p4d -h ) >/dev/null 2>&1 || {
+       skip_all='skipping git-p4 tests; no p4 or p4d'
+       test_done
+}
+
+GITP4="$GIT_BUILD_DIR/contrib/fast-import/git-p4"
+
+# Try to pick a unique port: guess a large number, then hope
+# no more than one of each test is running.
+#
+# This does not handle the case where somebody else is running the
+# same tests and has chosen the same ports.
+testid=${this_test#t}
+git_p4_test_start=9800
+P4DPORT=$((10669 + ($testid - $git_p4_test_start)))
+
+export P4PORT=localhost:$P4DPORT
+export P4CLIENT=client
+
+db="$TRASH_DIRECTORY/db"
+cli="$TRASH_DIRECTORY/cli"
+git="$TRASH_DIRECTORY/git"
+pidfile="$TRASH_DIRECTORY/p4d.pid"
+
+start_p4d() {
+       mkdir -p "$db" "$cli" "$git" &&
+       (
+               p4d -q -r "$db" -p $P4DPORT &
+               echo $! >"$pidfile"
+       ) &&
+       for i in 1 2 3 4 5 ; do
+               p4 info >/dev/null 2>&1 && break || true &&
+               echo waiting for p4d to start &&
+               sleep 1
+       done &&
+       # complain if it never started
+       p4 info >/dev/null &&
+       (
+               cd "$cli" &&
+               p4 client -i <<-EOF
+               Client: client
+               Description: client
+               Root: $cli
+               View: //depot/... //client/...
+               EOF
+       )
+}
+
+kill_p4d() {
+       pid=$(cat "$pidfile")
+       # it had better exist for the first kill
+       kill $pid &&
+       for i in 1 2 3 4 5 ; do
+               kill $pid >/dev/null 2>&1 || break
+               sleep 1
+       done &&
+       # complain if it would not die
+       test_must_fail kill $pid >/dev/null 2>&1 &&
+       rm -rf "$db" "$cli" "$pidfile"
+}
+
+cleanup_git() {
+       rm -rf "$git"
+}
diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh
new file mode 100755 (executable)
index 0000000..05824fa
--- /dev/null
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+gpg_version=`gpg --version 2>&1`
+if test $? = 127; then
+       say "You do not seem to have gpg installed"
+else
+       # As said here: http://www.gnupg.org/documentation/faqs.html#q6.19
+       # the gpg version 1.0.6 didn't parse trust packets correctly, so for
+       # that version, creation of signed tags using the generated key fails.
+       case "$gpg_version" in
+       'gpg (GnuPG) 1.0.6'*)
+               say "Your version of gpg (1.0.6) is too buggy for testing"
+               ;;
+       *)
+               # key generation info: gpg --homedir t/lib-gpg --gen-key
+               # Type DSA and Elgamal, size 2048 bits, no expiration date.
+               # Name and email: C O Mitter <committer@example.com>
+               # No password given, to enable non-interactive operation.
+               cp -R "$TEST_DIRECTORY"/lib-gpg ./gpghome
+               chmod 0700 gpghome
+               GNUPGHOME="$(pwd)/gpghome"
+               export GNUPGHOME
+               test_set_prereq GPG
+               ;;
+       esac
+fi
+
+sanitize_pgp() {
+       perl -ne '
+               /^-----END PGP/ and $in_pgp = 0;
+               print unless $in_pgp;
+               /^-----BEGIN PGP/ and $in_pgp = 1;
+       '
+}
diff --git a/t/lib-gpg/pubring.gpg b/t/lib-gpg/pubring.gpg
new file mode 100644 (file)
index 0000000..83855fa
Binary files /dev/null and b/t/lib-gpg/pubring.gpg differ
diff --git a/t/lib-gpg/random_seed b/t/lib-gpg/random_seed
new file mode 100644 (file)
index 0000000..8fed133
Binary files /dev/null and b/t/lib-gpg/random_seed differ
diff --git a/t/lib-gpg/secring.gpg b/t/lib-gpg/secring.gpg
new file mode 100644 (file)
index 0000000..d831cd9
Binary files /dev/null and b/t/lib-gpg/secring.gpg differ
diff --git a/t/lib-gpg/trustdb.gpg b/t/lib-gpg/trustdb.gpg
new file mode 100644 (file)
index 0000000..abace96
Binary files /dev/null and b/t/lib-gpg/trustdb.gpg differ
index b8996a373a7444b54823f36159ea1a8634e4aeee..f7dc0781d5d0ec5afd7f1d0898bffa17a9b1b00e 100644 (file)
@@ -81,8 +81,7 @@ prepare_httpd() {
 
        if test -n "$LIB_HTTPD_SSL"
        then
-               HTTPD_URL=https://127.0.0.1:$LIB_HTTPD_PORT
-               AUTH_HTTPD_URL=https://user%40host:user%40host@127.0.0.1:$LIB_HTTPD_PORT
+               HTTPD_PROTO=https
 
                RANDFILE_PATH="$HTTPD_ROOT_PATH"/.rnd openssl req \
                        -config "$TEST_PATH/ssl.cnf" \
@@ -93,9 +92,12 @@ prepare_httpd() {
                export GIT_SSL_NO_VERIFY
                HTTPD_PARA="$HTTPD_PARA -DSSL"
        else
-               HTTPD_URL=http://127.0.0.1:$LIB_HTTPD_PORT
-               AUTH_HTTPD_URL=http://user%40host:user%40host@127.0.0.1:$LIB_HTTPD_PORT
+               HTTPD_PROTO=http
        fi
+       HTTPD_DEST=127.0.0.1:$LIB_HTTPD_PORT
+       HTTPD_URL=$HTTPD_PROTO://$HTTPD_DEST
+       HTTPD_URL_USER=$HTTPD_PROTO://user%40host@$HTTPD_DEST
+       HTTPD_URL_USER_PASS=$HTTPD_PROTO://user%40host:user%40host@$HTTPD_DEST
 
        if test -n "$LIB_HTTPD_DAV" -o -n "$LIB_HTTPD_SVN"
        then
index ae2f1da28fa55b92338d7dbcb6ef851b6ec40118..dbb2623d930e433111f7e125749f5f1071e9ab3c 100755 (executable)
@@ -5,20 +5,16 @@ test_description=gitattributes
 . ./test-lib.sh
 
 attr_check () {
+       path="$1" expect="$2"
 
-       path="$1"
-       expect="$2"
-
-       git check-attr test -- "$path" >actual 2>err &&
+       git $3 check-attr test -- "$path" >actual 2>err &&
        echo "$path: test: $2" >expect &&
        test_cmp expect actual &&
        test_line_count = 0 err
-
 }
 
 
 test_expect_success 'setup' '
-
        mkdir -p a/b/d a/c b &&
        (
                echo "[attr]notest !test"
@@ -27,6 +23,7 @@ test_expect_success 'setup' '
                echo "onoff test -test"
                echo "offon -test test"
                echo "no notest"
+               echo "A/e/F test=A/e/F"
        ) >.gitattributes &&
        (
                echo "g test=a/g" &&
@@ -40,29 +37,27 @@ test_expect_success 'setup' '
        (
                echo "global test=global"
        ) >"$HOME"/global-gitattributes &&
-       cat <<EOF >expect-all
-f: test: f
-a/f: test: f
-a/c/f: test: f
-a/g: test: a/g
-a/b/g: test: a/b/g
-b/g: test: unspecified
-a/b/h: test: a/b/h
-a/b/d/g: test: a/b/d/*
-onoff: test: unset
-offon: test: set
-no: notest: set
-no: test: unspecified
-a/b/d/no: notest: set
-a/b/d/no: test: a/b/d/*
-a/b/d/yes: notest: set
-a/b/d/yes: test: unspecified
-EOF
-
+       cat <<-EOF >expect-all
+       f: test: f
+       a/f: test: f
+       a/c/f: test: f
+       a/g: test: a/g
+       a/b/g: test: a/b/g
+       b/g: test: unspecified
+       a/b/h: test: a/b/h
+       a/b/d/g: test: a/b/d/*
+       onoff: test: unset
+       offon: test: set
+       no: notest: set
+       no: test: unspecified
+       a/b/d/no: notest: set
+       a/b/d/no: test: a/b/d/*
+       a/b/d/yes: notest: set
+       a/b/d/yes: test: unspecified
+       EOF
 '
 
 test_expect_success 'command line checks' '
-
        test_must_fail git check-attr &&
        test_must_fail git check-attr -- &&
        test_must_fail git check-attr test &&
@@ -72,11 +67,9 @@ test_expect_success 'command line checks' '
        echo "f" | test_must_fail git check-attr --stdin -- f &&
        echo "f" | test_must_fail git check-attr --stdin test -- f &&
        test_must_fail git check-attr "" -- f
-
 '
 
 test_expect_success 'attribute test' '
-
        attr_check f f &&
        attr_check a/f f &&
        attr_check a/c/f f &&
@@ -90,20 +83,72 @@ test_expect_success 'attribute test' '
        attr_check no unspecified &&
        attr_check a/b/d/no "a/b/d/*" &&
        attr_check a/b/d/yes unspecified
+'
+
+test_expect_success 'attribute matching is case sensitive when core.ignorecase=0' '
+
+       test_must_fail attr_check F f "-c core.ignorecase=0" &&
+       test_must_fail attr_check a/F f "-c core.ignorecase=0" &&
+       test_must_fail attr_check a/c/F f "-c core.ignorecase=0" &&
+       test_must_fail attr_check a/G a/g "-c core.ignorecase=0" &&
+       test_must_fail attr_check a/B/g a/b/g "-c core.ignorecase=0" &&
+       test_must_fail attr_check a/b/G a/b/g "-c core.ignorecase=0" &&
+       test_must_fail attr_check a/b/H a/b/h "-c core.ignorecase=0" &&
+       test_must_fail attr_check a/b/D/g "a/b/d/*" "-c core.ignorecase=0" &&
+       test_must_fail attr_check oNoFf unset "-c core.ignorecase=0" &&
+       test_must_fail attr_check oFfOn set "-c core.ignorecase=0" &&
+       attr_check NO unspecified "-c core.ignorecase=0" &&
+       test_must_fail attr_check a/b/D/NO "a/b/d/*" "-c core.ignorecase=0" &&
+       attr_check a/b/d/YES a/b/d/* "-c core.ignorecase=0" &&
+       test_must_fail attr_check a/E/f "A/e/F" "-c core.ignorecase=0"
 
 '
 
-test_expect_success 'unnormalized paths' '
+test_expect_success 'attribute matching is case insensitive when core.ignorecase=1' '
+
+       attr_check F f "-c core.ignorecase=1" &&
+       attr_check a/F f "-c core.ignorecase=1" &&
+       attr_check a/c/F f "-c core.ignorecase=1" &&
+       attr_check a/G a/g "-c core.ignorecase=1" &&
+       attr_check a/B/g a/b/g "-c core.ignorecase=1" &&
+       attr_check a/b/G a/b/g "-c core.ignorecase=1" &&
+       attr_check a/b/H a/b/h "-c core.ignorecase=1" &&
+       attr_check a/b/D/g "a/b/d/*" "-c core.ignorecase=1" &&
+       attr_check oNoFf unset "-c core.ignorecase=1" &&
+       attr_check oFfOn set "-c core.ignorecase=1" &&
+       attr_check NO unspecified "-c core.ignorecase=1" &&
+       attr_check a/b/D/NO "a/b/d/*" "-c core.ignorecase=1" &&
+       attr_check a/b/d/YES unspecified "-c core.ignorecase=1" &&
+       attr_check a/E/f "A/e/F" "-c core.ignorecase=1"
+
+'
+
+test_expect_success 'check whether FS is case-insensitive' '
+       mkdir junk &&
+       echo good >junk/CamelCase &&
+       echo bad >junk/camelcase &&
+       if test "$(cat junk/CamelCase)" != good
+       then
+               test_set_prereq CASE_INSENSITIVE_FS
+       fi
+'
+
+test_expect_success CASE_INSENSITIVE_FS 'additional case insensitivity tests' '
+       test_must_fail attr_check a/B/D/g "a/b/d/*" "-c core.ignorecase=0" &&
+       test_must_fail attr_check A/B/D/NO "a/b/d/*" "-c core.ignorecase=0" &&
+       attr_check A/b/h a/b/h "-c core.ignorecase=1" &&
+       attr_check a/B/D/g "a/b/d/*" "-c core.ignorecase=1" &&
+       attr_check A/B/D/NO "a/b/d/*" "-c core.ignorecase=1"
+'
 
+test_expect_success 'unnormalized paths' '
        attr_check ./f f &&
        attr_check ./a/g a/g &&
        attr_check a/./g a/g &&
        attr_check a/c/../b/g a/b/g
-
 '
 
 test_expect_success 'relative paths' '
-
        (cd a && attr_check ../f f) &&
        (cd a && attr_check f f) &&
        (cd a && attr_check i a/i) &&
@@ -112,7 +157,6 @@ test_expect_success 'relative paths' '
        (cd b && attr_check ../a/f f) &&
        (cd b && attr_check ../a/g a/g) &&
        (cd b && attr_check ../a/b/g a/b/g)
-
 '
 
 test_expect_success 'core.attributesfile' '
@@ -121,41 +165,43 @@ test_expect_success 'core.attributesfile' '
        attr_check global global &&
        git config core.attributesfile "~/global-gitattributes" &&
        attr_check global global &&
-       echo "global test=precedence" >> .gitattributes &&
+       echo "global test=precedence" >>.gitattributes &&
        attr_check global precedence
 '
 
 test_expect_success 'attribute test: read paths from stdin' '
-
-       grep -v notest < expect-all > expect &&
-       sed -e "s/:.*//" < expect | git check-attr --stdin test > actual &&
+       grep -v notest <expect-all >expect &&
+       sed -e "s/:.*//" <expect | git check-attr --stdin test >actual &&
        test_cmp expect actual
 '
 
 test_expect_success 'attribute test: --all option' '
+       grep -v unspecified <expect-all | sort >specified-all &&
+       sed -e "s/:.*//" <expect-all | uniq >stdin-all &&
+       git check-attr --stdin --all <stdin-all | sort >actual &&
+       test_cmp specified-all actual
+'
 
-       grep -v unspecified < expect-all | sort > expect &&
-       sed -e "s/:.*//" < expect-all | uniq |
-               git check-attr --stdin --all | sort > actual &&
-       test_cmp expect actual
+test_expect_success 'attribute test: --cached option' '
+       : >empty &&
+       git check-attr --cached --stdin --all <stdin-all | sort >actual &&
+       test_cmp empty actual &&
+       git add .gitattributes a/.gitattributes a/b/.gitattributes &&
+       git check-attr --cached --stdin --all <stdin-all | sort >actual &&
+       test_cmp specified-all actual
 '
 
 test_expect_success 'root subdir attribute test' '
-
        attr_check a/i a/i &&
        attr_check subdir/a/i unspecified
-
 '
 
 test_expect_success 'setup bare' '
-
        git clone --bare . bare.git &&
        cd bare.git
-
 '
 
 test_expect_success 'bare repository: check that .gitattribute is ignored' '
-
        (
                echo "f test=f"
                echo "a/i test=a/i"
@@ -165,11 +211,16 @@ test_expect_success 'bare repository: check that .gitattribute is ignored' '
        attr_check a/c/f unspecified &&
        attr_check a/i unspecified &&
        attr_check subdir/a/i unspecified
+'
 
+test_expect_success 'bare repository: check that --cached honors index' '
+       GIT_INDEX_FILE=../.git/index \
+       git check-attr --cached --stdin --all <../stdin-all |
+       sort >actual &&
+       test_cmp ../specified-all actual
 '
 
 test_expect_success 'bare repository: test info/attributes' '
-
        (
                echo "f test=f"
                echo "a/i test=a/i"
@@ -179,7 +230,6 @@ test_expect_success 'bare repository: test info/attributes' '
        attr_check a/c/f f &&
        attr_check a/i a/i &&
        attr_check subdir/a/i unspecified
-
 '
 
 test_done
index f87abb5a0682d7a2fc7c3947789301c4157d331d..1d29810a7a94dad15645869c2f4f015a470fa622 100755 (executable)
@@ -40,6 +40,12 @@ check_parse 2008-02 bad
 check_parse 2008-02-14 bad
 check_parse '2008-02-14 20:30:45' '2008-02-14 20:30:45 +0000'
 check_parse '2008-02-14 20:30:45 -0500' '2008-02-14 20:30:45 -0500'
+check_parse '2008-02-14 20:30:45 -0015' '2008-02-14 20:30:45 -0015'
+check_parse '2008-02-14 20:30:45 -5' '2008-02-14 20:30:45 +0000'
+check_parse '2008-02-14 20:30:45 -5:' '2008-02-14 20:30:45 +0000'
+check_parse '2008-02-14 20:30:45 -05' '2008-02-14 20:30:45 -0500'
+check_parse '2008-02-14 20:30:45 -:30' '2008-02-14 20:30:45 +0000'
+check_parse '2008-02-14 20:30:45 -05:00' '2008-02-14 20:30:45 -0500'
 check_parse '2008-02-14 20:30:45' '2008-02-14 20:30:45 -0500' EST5
 
 check_approxidate() {
index 007f39d5e1dca6eff7e01969282c4cf2dc1bb57a..a1e4616febe6c67f67ab2b4380610abc92468ba5 100755 (executable)
@@ -87,7 +87,7 @@ EOF
 test_expect_success 'long options' '
        test-parse-options --boolean --integer 1729 --boolean --string2=321 \
                --verbose --verbose --no-dry-run --abbrev=10 --file fi.le\
-               > output 2> output.err &&
+               --obsolete > output 2> output.err &&
        test ! -s output.err &&
        test_cmp expect output
 '
index 018c3546b6f05228a20dfb5ec9364d0a1575e4ff..5c0053a20bf2fbf1dd466ace5dc933e38ce3544d 100755 (executable)
@@ -234,4 +234,20 @@ test_expect_success 'read-tree --reset removes outside worktree' '
        test_cmp empty result
 '
 
+test_expect_success 'print errors when failed to update worktree' '
+       echo sub >.git/info/sparse-checkout &&
+       git checkout -f init &&
+       mkdir sub &&
+       touch sub/added sub/addedtoo &&
+       test_must_fail git checkout top 2>actual &&
+       cat >expected <<\EOF &&
+error: The following untracked working tree files would be overwritten by checkout:
+       sub/added
+       sub/addedtoo
+Please move or remove them before you can switch branches.
+Aborting
+EOF
+       test_cmp expected actual
+'
+
 test_done
index 3b1b985996e9a6b52b032ef45c5be9b1c57b60f6..e23ac0e69da8d3dba32f0d256a249a987356e825 100755 (executable)
@@ -118,7 +118,7 @@ test_expect_success 'alias expansion' '
        )
 '
 
-test_expect_success '!alias expansion' '
+test_expect_success NOT_MINGW '!alias expansion' '
        pwd >expect &&
        (
                git config alias.test !pwd &&
index 3e140c18f4183c41c76aaab0834f1d23ce7bcd2d..51caff047b0da1a6d1df7fa651b3a8c31e8ae3e2 100755 (executable)
@@ -7,28 +7,28 @@ test_description='Test git config in different settings'
 
 . ./test-lib.sh
 
-test -f .git/config && rm .git/config
-
-git config core.penguin "little blue"
+test_expect_success 'clear default config' '
+       rm -f .git/config
+'
 
 cat > expect << EOF
 [core]
        penguin = little blue
 EOF
-
-test_expect_success 'initial' 'cmp .git/config expect'
-
-git config Core.Movie BadPhysics
+test_expect_success 'initial' '
+       git config core.penguin "little blue" &&
+       test_cmp expect .git/config
+'
 
 cat > expect << EOF
 [core]
        penguin = little blue
        Movie = BadPhysics
 EOF
-
-test_expect_success 'mixed case' 'cmp .git/config expect'
-
-git config Cores.WhatEver Second
+test_expect_success 'mixed case' '
+       git config Core.Movie BadPhysics &&
+       test_cmp expect .git/config
+'
 
 cat > expect << EOF
 [core]
@@ -37,10 +37,10 @@ cat > expect << EOF
 [Cores]
        WhatEver = Second
 EOF
-
-test_expect_success 'similar section' 'cmp .git/config expect'
-
-git config CORE.UPPERCASE true
+test_expect_success 'similar section' '
+       git config Cores.WhatEver Second
+       test_cmp expect .git/config
+'
 
 cat > expect << EOF
 [core]
@@ -50,8 +50,10 @@ cat > expect << EOF
 [Cores]
        WhatEver = Second
 EOF
-
-test_expect_success 'similar section' 'cmp .git/config expect'
+test_expect_success 'uppercase section' '
+       git config CORE.UPPERCASE true &&
+       test_cmp expect .git/config
+'
 
 test_expect_success 'replace with non-match' \
        'git config core.penguin kingpin !blue'
@@ -69,7 +71,34 @@ cat > expect << EOF
        WhatEver = Second
 EOF
 
-test_expect_success 'non-match result' 'cmp .git/config expect'
+test_expect_success 'non-match result' 'test_cmp expect .git/config'
+
+test_expect_success 'find mixed-case key by canonical name' '
+       echo Second >expect &&
+       git config cores.whatever >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'find mixed-case key by non-canonical name' '
+       echo Second >expect &&
+       git config CoReS.WhAtEvEr >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'subsections are not canonicalized by git-config' '
+       cat >>.git/config <<-\EOF &&
+       [section.SubSection]
+       key = one
+       [section "SubSection"]
+       key = two
+       EOF
+       echo one >expect &&
+       git config section.subsection.key >actual &&
+       test_cmp expect actual &&
+       echo two >expect &&
+       git config section.SubSection.key >actual &&
+       test_cmp expect actual
+'
 
 cat > .git/config <<\EOF
 [alpha]
@@ -88,7 +117,7 @@ bar = foo
 [beta]
 EOF
 
-test_expect_success 'unset with cont. lines is correct' 'cmp .git/config expect'
+test_expect_success 'unset with cont. lines is correct' 'test_cmp expect .git/config'
 
 cat > .git/config << EOF
 [beta] ; silly comment # another comment
@@ -116,7 +145,7 @@ noIndent= sillyValue ; 'nother silly comment
 [nextSection] noNewline = ouch
 EOF
 
-test_expect_success 'multiple unset is correct' 'cmp .git/config expect'
+test_expect_success 'multiple unset is correct' 'test_cmp expect .git/config'
 
 cp .git/config2 .git/config
 
@@ -140,9 +169,7 @@ noIndent= sillyValue ; 'nother silly comment
 [nextSection] noNewline = ouch
 EOF
 
-test_expect_success 'all replaced' 'cmp .git/config expect'
-
-git config beta.haha alpha
+test_expect_success 'all replaced' 'test_cmp expect .git/config'
 
 cat > expect << EOF
 [beta] ; silly comment # another comment
@@ -153,10 +180,10 @@ noIndent= sillyValue ; 'nother silly comment
        haha = alpha
 [nextSection] noNewline = ouch
 EOF
-
-test_expect_success 'really mean test' 'cmp .git/config expect'
-
-git config nextsection.nonewline wow
+test_expect_success 'really mean test' '
+       git config beta.haha alpha &&
+       test_cmp expect .git/config
+'
 
 cat > expect << EOF
 [beta] ; silly comment # another comment
@@ -168,11 +195,12 @@ noIndent= sillyValue ; 'nother silly comment
 [nextSection]
        nonewline = wow
 EOF
-
-test_expect_success 'really really mean test' 'cmp .git/config expect'
+test_expect_success 'really really mean test' '
+       git config nextsection.nonewline wow &&
+       test_cmp expect .git/config
+'
 
 test_expect_success 'get value' 'test alpha = $(git config beta.haha)'
-git config --unset beta.haha
 
 cat > expect << EOF
 [beta] ; silly comment # another comment
@@ -183,10 +211,10 @@ noIndent= sillyValue ; 'nother silly comment
 [nextSection]
        nonewline = wow
 EOF
-
-test_expect_success 'unset' 'cmp .git/config expect'
-
-git config nextsection.NoNewLine "wow2 for me" "for me$"
+test_expect_success 'unset' '
+       git config --unset beta.haha &&
+       test_cmp expect .git/config
+'
 
 cat > expect << EOF
 [beta] ; silly comment # another comment
@@ -198,8 +226,10 @@ noIndent= sillyValue ; 'nother silly comment
        nonewline = wow
        NoNewLine = wow2 for me
 EOF
-
-test_expect_success 'multivar' 'cmp .git/config expect'
+test_expect_success 'multivar' '
+       git config nextsection.NoNewLine "wow2 for me" "for me$" &&
+       test_cmp expect .git/config
+'
 
 test_expect_success 'non-match' \
        'git config --get nextsection.nonewline !for'
@@ -214,8 +244,6 @@ test_expect_success 'ambiguous get' '
 test_expect_success 'get multivar' \
        'git config --get-all nextsection.nonewline'
 
-git config nextsection.nonewline "wow3" "wow$"
-
 cat > expect << EOF
 [beta] ; silly comment # another comment
 noIndent= sillyValue ; 'nother silly comment
@@ -226,8 +254,10 @@ noIndent= sillyValue ; 'nother silly comment
        nonewline = wow3
        NoNewLine = wow2 for me
 EOF
-
-test_expect_success 'multivar replace' 'cmp .git/config expect'
+test_expect_success 'multivar replace' '
+       git config nextsection.nonewline "wow3" "wow$" &&
+       test_cmp expect .git/config
+'
 
 test_expect_success 'ambiguous value' '
        test_must_fail git config nextsection.nonewline
@@ -241,8 +271,6 @@ test_expect_success 'invalid unset' '
        test_must_fail git config --unset somesection.nonewline
 '
 
-git config --unset nextsection.nonewline "wow3$"
-
 cat > expect << EOF
 [beta] ; silly comment # another comment
 noIndent= sillyValue ; 'nother silly comment
@@ -253,7 +281,10 @@ noIndent= sillyValue ; 'nother silly comment
        NoNewLine = wow2 for me
 EOF
 
-test_expect_success 'multivar unset' 'cmp .git/config expect'
+test_expect_success 'multivar unset' '
+       git config --unset nextsection.nonewline "wow3$" &&
+       test_cmp expect .git/config
+'
 
 test_expect_success 'invalid key' 'test_must_fail git config inval.2key blabla'
 
@@ -276,7 +307,7 @@ noIndent= sillyValue ; 'nother silly comment
        Alpha = beta
 EOF
 
-test_expect_success 'hierarchical section value' 'cmp .git/config expect'
+test_expect_success 'hierarchical section value' 'test_cmp expect .git/config'
 
 cat > expect << EOF
 beta.noindent=sillyValue
@@ -304,15 +335,16 @@ EOF
 test_expect_success '--get-regexp' \
        'git config --get-regexp in > output && cmp output expect'
 
-git config --add nextsection.nonewline "wow4 for you"
-
 cat > expect << EOF
 wow2 for me
 wow4 for you
 EOF
 
-test_expect_success '--add' \
-       'git config --get-all nextsection.nonewline > output && cmp output expect'
+test_expect_success '--add' '
+       git config --add nextsection.nonewline "wow4 for you" &&
+       git config --get-all nextsection.nonewline > output &&
+       test_cmp expect output
+'
 
 cat > .git/config << EOF
 [novalue]
@@ -333,6 +365,12 @@ test_expect_success 'get-regexp variable with no value' \
        'git config --get-regexp novalue > output &&
         cmp output expect'
 
+echo 'novalue.variable true' > expect
+
+test_expect_success 'get-regexp --bool variable with no value' \
+       'git config --bool --get-regexp novalue > output &&
+        cmp output expect'
+
 echo 'emptyvalue.variable ' > expect
 
 test_expect_success 'get-regexp variable with empty value' \
@@ -361,8 +399,6 @@ cat > .git/config << EOF
        c = d
 EOF
 
-git config a.x y
-
 cat > expect << EOF
 [a.b]
        c = d
@@ -370,10 +406,10 @@ cat > expect << EOF
        x = y
 EOF
 
-test_expect_success 'new section is partial match of another' 'cmp .git/config expect'
-
-git config b.x y
-git config a.b c
+test_expect_success 'new section is partial match of another' '
+       git config a.x y &&
+       test_cmp expect .git/config
+'
 
 cat > expect << EOF
 [a.b]
@@ -385,7 +421,11 @@ cat > expect << EOF
        x = y
 EOF
 
-test_expect_success 'new variable inserts into proper section' 'cmp .git/config expect'
+test_expect_success 'new variable inserts into proper section' '
+       git config b.x y &&
+       git config a.b c &&
+       test_cmp expect .git/config
+'
 
 test_expect_success 'alternative GIT_CONFIG (non-existing file should fail)' \
        'test_must_fail git config --file non-existing-config -l'
@@ -399,9 +439,10 @@ cat > expect << EOF
 ein.bahn=strasse
 EOF
 
-GIT_CONFIG=other-config git config -l > output
-
-test_expect_success 'alternative GIT_CONFIG' 'cmp output expect'
+test_expect_success 'alternative GIT_CONFIG' '
+       GIT_CONFIG=other-config git config -l >output &&
+       test_cmp expect output
+'
 
 test_expect_success 'alternative GIT_CONFIG (--file)' \
        'git config --file other-config -l > output && cmp output expect'
@@ -417,8 +458,6 @@ test_expect_success 'refer config from subdirectory' '
 
 '
 
-GIT_CONFIG=other-config git config anwohner.park ausweis
-
 cat > expect << EOF
 [ein]
        bahn = strasse
@@ -426,7 +465,10 @@ cat > expect << EOF
        park = ausweis
 EOF
 
-test_expect_success '--set in alternative GIT_CONFIG' 'cmp other-config expect'
+test_expect_success '--set in alternative GIT_CONFIG' '
+       GIT_CONFIG=other-config git config anwohner.park ausweis &&
+       test_cmp expect other-config
+'
 
 cat > .git/config << EOF
 # Hallo
@@ -516,8 +558,6 @@ EOF
 test_expect_success "section was removed properly" \
        "test_cmp expect .git/config"
 
-rm .git/config
-
 cat > expect << EOF
 [gitcvs]
        enabled = true
@@ -528,10 +568,11 @@ EOF
 
 test_expect_success 'section ending' '
 
+       rm -f .git/config &&
        git config gitcvs.enabled true &&
        git config gitcvs.ext.dbname %Ggitcvs1.%a.%m.sqlite &&
        git config gitcvs.dbname %Ggitcvs2.%a.%m.sqlite &&
-       cmp .git/config expect
+       test_cmp expect .git/config
 
 '
 
@@ -600,8 +641,6 @@ test_expect_success 'invalid bool (set)' '
 
        test_must_fail git config --bool bool.nobool foobar'
 
-rm .git/config
-
 cat > expect <<\EOF
 [bool]
        true1 = true
@@ -616,6 +655,7 @@ EOF
 
 test_expect_success 'set --bool' '
 
+       rm -f .git/config &&
        git config --bool bool.true1 01 &&
        git config --bool bool.true2 -1 &&
        git config --bool bool.true3 YeS &&
@@ -626,8 +666,6 @@ test_expect_success 'set --bool' '
        git config --bool bool.false4 FALSE &&
        cmp expect .git/config'
 
-rm .git/config
-
 cat > expect <<\EOF
 [int]
        val1 = 1
@@ -637,13 +675,12 @@ EOF
 
 test_expect_success 'set --int' '
 
+       rm -f .git/config &&
        git config --int int.val1 01 &&
        git config --int int.val2 -1 &&
        git config --int int.val3 5m &&
        cmp expect .git/config'
 
-rm .git/config
-
 cat >expect <<\EOF
 [bool]
        true1 = true
@@ -657,6 +694,7 @@ cat >expect <<\EOF
 EOF
 
 test_expect_success 'get --bool-or-int' '
+       rm -f .git/config &&
        (
                echo "[bool]"
                echo true1
@@ -676,7 +714,6 @@ test_expect_success 'get --bool-or-int' '
 
 '
 
-rm .git/config
 cat >expect <<\EOF
 [bool]
        true1 = true
@@ -690,6 +727,7 @@ cat >expect <<\EOF
 EOF
 
 test_expect_success 'set --bool-or-int' '
+       rm -f .git/config &&
        git config --bool-or-int bool.true1 true &&
        git config --bool-or-int bool.false1 false &&
        git config --bool-or-int bool.true2 yes &&
@@ -700,8 +738,6 @@ test_expect_success 'set --bool-or-int' '
        test_cmp expect .git/config
 '
 
-rm .git/config
-
 cat >expect <<\EOF
 [path]
        home = ~/
@@ -710,6 +746,7 @@ cat >expect <<\EOF
 EOF
 
 test_expect_success NOT_MINGW 'set --path' '
+       rm -f .git/config &&
        git config --path path.home "~/" &&
        git config --path path.normal "/dev/null" &&
        git config --path path.trailingtilde "foo~" &&
@@ -750,13 +787,6 @@ test_expect_success NOT_MINGW 'get --path copes with unset $HOME' '
        test_cmp expect result
 '
 
-rm .git/config
-
-git config quote.leading " test"
-git config quote.ending "test "
-git config quote.semicolon "test;test"
-git config quote.hash "test#test"
-
 cat > expect << EOF
 [quote]
        leading = " test"
@@ -764,8 +794,14 @@ cat > expect << EOF
        semicolon = "test;test"
        hash = "test#test"
 EOF
-
-test_expect_success 'quoting' 'cmp .git/config expect'
+test_expect_success 'quoting' '
+       rm -f .git/config &&
+       git config quote.leading " test" &&
+       git config quote.ending "test " &&
+       git config quote.semicolon "test;test" &&
+       git config quote.hash "test#test" &&
+       test_cmp expect .git/config
+'
 
 test_expect_success 'key with newline' '
        test_must_fail git config "key.with
@@ -790,9 +826,10 @@ section.noncont=not continued
 section.quotecont=cont;inued
 EOF
 
-git config --list > result
-
-test_expect_success 'value continued on next line' 'cmp result expect'
+test_expect_success 'value continued on next line' '
+       git config --list > result &&
+       cmp result expect
+'
 
 cat > .git/config <<\EOF
 [section "sub=section"]
@@ -813,16 +850,17 @@ barQsection.sub=section.val3
 Qsection.sub=section.val4
 Qsection.sub=section.val5Q
 EOF
+test_expect_success '--null --list' '
+       git config --null --list | nul_to_q >result &&
+       echo >>result &&
+       test_cmp expect result
+'
 
-git config --null --list | perl -pe 'y/\000/Q/' > result
-echo >>result
-
-test_expect_success '--null --list' 'cmp result expect'
-
-git config --null --get-regexp 'val[0-9]' | perl -pe 'y/\000/Q/' > result
-echo >>result
-
-test_expect_success '--null --get-regexp' 'cmp result expect'
+test_expect_success '--null --get-regexp' '
+       git config --null --get-regexp "val[0-9]" | nul_to_q >result &&
+       echo >>result &&
+       test_cmp expect result
+'
 
 test_expect_success 'inner whitespace kept verbatim' '
        git config section.val "foo       bar" &&
index b5d89a2250e41afe544b0d0f59f638e02d570cdd..2b962cfda70d998e9c2799cf8fc720b330cd118a 100755 (executable)
@@ -25,6 +25,11 @@ else
        test_set_prereq SETFACL
 fi
 
+if test -z "$LOGNAME"
+then
+       LOGNAME=$USER
+fi
+
 check_perms_and_acl () {
        test -r "$1" &&
        getfacl "$1" > actual &&
index ed4275afe3100491ea57025632665182127f33cf..1ae4d87c929ec55a66d44bb23aecea8a2aea4346 100755 (executable)
@@ -5,34 +5,126 @@ test_description='Test git check-ref-format'
 . ./test-lib.sh
 
 valid_ref() {
-       test_expect_success "ref name '$1' is valid" \
-               "git check-ref-format '$1'"
+       prereq=
+       case $1 in
+       [A-Z]*)
+               prereq=$1
+               shift
+       esac
+       test_expect_success $prereq "ref name '$1' is valid${2:+ with options $2}" "
+               git check-ref-format $2 '$1'
+       "
 }
 invalid_ref() {
-       test_expect_success "ref name '$1' is not valid" \
-               "test_must_fail git check-ref-format '$1'"
+       prereq=
+       case $1 in
+       [A-Z]*)
+               prereq=$1
+               shift
+       esac
+       test_expect_success $prereq "ref name '$1' is invalid${2:+ with options $2}" "
+               test_must_fail git check-ref-format $2 '$1'
+       "
 }
 
-valid_ref 'heads/foo'
-invalid_ref 'foo'
+invalid_ref ''
+invalid_ref NOT_MINGW '/'
+invalid_ref NOT_MINGW '/' --allow-onelevel
+invalid_ref NOT_MINGW '/' --normalize
+invalid_ref NOT_MINGW '/' '--allow-onelevel --normalize'
 valid_ref 'foo/bar/baz'
-valid_ref 'refs///heads/foo'
+valid_ref 'foo/bar/baz' --normalize
+invalid_ref 'refs///heads/foo'
+valid_ref 'refs///heads/foo' --normalize
 invalid_ref 'heads/foo/'
-valid_ref '/heads/foo'
-valid_ref '///heads/foo'
-invalid_ref '/foo'
+invalid_ref NOT_MINGW '/heads/foo'
+valid_ref NOT_MINGW '/heads/foo' --normalize
+invalid_ref '///heads/foo'
+valid_ref '///heads/foo' --normalize
 invalid_ref './foo'
+invalid_ref './foo/bar'
+invalid_ref 'foo/./bar'
+invalid_ref 'foo/bar/.'
 invalid_ref '.refs/foo'
 invalid_ref 'heads/foo..bar'
 invalid_ref 'heads/foo?bar'
 valid_ref 'foo./bar'
 invalid_ref 'heads/foo.lock'
+invalid_ref 'heads///foo.lock'
+invalid_ref 'foo.lock/bar'
+invalid_ref 'foo.lock///bar'
 valid_ref 'heads/foo@bar'
 invalid_ref 'heads/v@{ation'
 invalid_ref 'heads/foo\bar'
 invalid_ref "$(printf 'heads/foo\t')"
 invalid_ref "$(printf 'heads/foo\177')"
 valid_ref "$(printf 'heads/fu\303\237')"
+invalid_ref 'heads/*foo/bar' --refspec-pattern
+invalid_ref 'heads/foo*/bar' --refspec-pattern
+invalid_ref 'heads/f*o/bar' --refspec-pattern
+
+ref='foo'
+invalid_ref "$ref"
+valid_ref "$ref" --allow-onelevel
+invalid_ref "$ref" --refspec-pattern
+valid_ref "$ref" '--refspec-pattern --allow-onelevel'
+invalid_ref "$ref" --normalize
+valid_ref "$ref" '--allow-onelevel --normalize'
+
+ref='foo/bar'
+valid_ref "$ref"
+valid_ref "$ref" --allow-onelevel
+valid_ref "$ref" --refspec-pattern
+valid_ref "$ref" '--refspec-pattern --allow-onelevel'
+valid_ref "$ref" --normalize
+
+ref='foo/*'
+invalid_ref "$ref"
+invalid_ref "$ref" --allow-onelevel
+valid_ref "$ref" --refspec-pattern
+valid_ref "$ref" '--refspec-pattern --allow-onelevel'
+
+ref='*/foo'
+invalid_ref "$ref"
+invalid_ref "$ref" --allow-onelevel
+valid_ref "$ref" --refspec-pattern
+valid_ref "$ref" '--refspec-pattern --allow-onelevel'
+invalid_ref "$ref" --normalize
+valid_ref "$ref" '--refspec-pattern --normalize'
+
+ref='foo/*/bar'
+invalid_ref "$ref"
+invalid_ref "$ref" --allow-onelevel
+valid_ref "$ref" --refspec-pattern
+valid_ref "$ref" '--refspec-pattern --allow-onelevel'
+
+ref='*'
+invalid_ref "$ref"
+invalid_ref "$ref" --allow-onelevel
+invalid_ref "$ref" --refspec-pattern
+valid_ref "$ref" '--refspec-pattern --allow-onelevel'
+
+ref='foo/*/*'
+invalid_ref "$ref" --refspec-pattern
+invalid_ref "$ref" '--refspec-pattern --allow-onelevel'
+
+ref='*/foo/*'
+invalid_ref "$ref" --refspec-pattern
+invalid_ref "$ref" '--refspec-pattern --allow-onelevel'
+
+ref='*/*/foo'
+invalid_ref "$ref" --refspec-pattern
+invalid_ref "$ref" '--refspec-pattern --allow-onelevel'
+
+ref='/foo'
+invalid_ref NOT_MINGW "$ref"
+invalid_ref NOT_MINGW "$ref" --allow-onelevel
+invalid_ref NOT_MINGW "$ref" --refspec-pattern
+invalid_ref NOT_MINGW "$ref" '--refspec-pattern --allow-onelevel'
+invalid_ref NOT_MINGW "$ref" --normalize
+valid_ref NOT_MINGW "$ref" '--allow-onelevel --normalize'
+invalid_ref NOT_MINGW "$ref" '--refspec-pattern --normalize'
+valid_ref NOT_MINGW "$ref" '--refspec-pattern --allow-onelevel --normalize'
 
 test_expect_success "check-ref-format --branch @{-1}" '
        T=$(git write-tree) &&
@@ -65,23 +157,41 @@ test_expect_success 'check-ref-format --branch from subdir' '
 '
 
 valid_ref_normalized() {
-       test_expect_success "ref name '$1' simplifies to '$2'" "
-               refname=\$(git check-ref-format --print '$1') &&
-               test \"\$refname\" = '$2'"
+       prereq=
+       case $1 in
+       [A-Z]*)
+               prereq=$1
+               shift
+       esac
+       test_expect_success $prereq "ref name '$1' simplifies to '$2'" "
+               refname=\$(git check-ref-format --normalize '$1') &&
+               test \"\$refname\" = '$2'
+       "
 }
 invalid_ref_normalized() {
-       test_expect_success "check-ref-format --print rejects '$1'" "
-               test_must_fail git check-ref-format --print '$1'"
+       prereq=
+       case $1 in
+       [A-Z]*)
+               prereq=$1
+               shift
+       esac
+       test_expect_success $prereq "check-ref-format --normalize rejects '$1'" "
+               test_must_fail git check-ref-format --normalize '$1'
+       "
 }
 
 valid_ref_normalized 'heads/foo' 'heads/foo'
 valid_ref_normalized 'refs///heads/foo' 'refs/heads/foo'
-valid_ref_normalized '/heads/foo' 'heads/foo'
+valid_ref_normalized NOT_MINGW '/heads/foo' 'heads/foo'
 valid_ref_normalized '///heads/foo' 'heads/foo'
 invalid_ref_normalized 'foo'
-invalid_ref_normalized '/foo'
+invalid_ref_normalized NOT_MINGW '/foo'
 invalid_ref_normalized 'heads/foo/../bar'
 invalid_ref_normalized 'heads/./foo'
 invalid_ref_normalized 'heads\foo'
+invalid_ref_normalized 'heads/foo.lock'
+invalid_ref_normalized 'heads///foo.lock'
+invalid_ref_normalized 'foo.lock/bar'
+invalid_ref_normalized 'foo.lock///bar'
 
 test_done
index 2366f0f4141656666db9cdcd8975335df57b6a8f..068fba4c8e8706aa615d0401da1da47901113156 100755 (executable)
@@ -12,11 +12,14 @@ check_not_detached () {
 }
 
 ORPHAN_WARNING='you are leaving .* commit.*behind'
+PREV_HEAD_DESC='Previous HEAD position was'
 check_orphan_warning() {
-       test_i18ngrep "$ORPHAN_WARNING" "$1"
+       test_i18ngrep "$ORPHAN_WARNING" "$1" &&
+       test_i18ngrep ! "$PREV_HEAD_DESC" "$1"
 }
 check_no_orphan_warning() {
-       test_i18ngrep ! "$ORPHAN_WARNING" "$1"
+       test_i18ngrep ! "$ORPHAN_WARNING" "$1" &&
+       test_i18ngrep "$PREV_HEAD_DESC" "$1"
 }
 
 reset () {
diff --git a/t/t2022-checkout-paths.sh b/t/t2022-checkout-paths.sh
new file mode 100755 (executable)
index 0000000..56090d2
--- /dev/null
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+test_description='checkout $tree -- $paths'
+. ./test-lib.sh
+
+test_expect_success setup '
+       mkdir dir &&
+       >dir/master &&
+       echo common >dir/common &&
+       git add dir/master dir/common &&
+       test_tick && git commit -m "master has dir/master" &&
+       git checkout -b next &&
+       git mv dir/master dir/next0 &&
+       echo next >dir/next1 &&
+       git add dir &&
+       test_tick && git commit -m "next has dir/next but not dir/master"
+'
+
+test_expect_success 'checking out paths out of a tree does not clobber unrelated paths' '
+       git checkout next &&
+       git reset --hard &&
+       rm dir/next0 &&
+       cat dir/common >expect.common &&
+       echo modified >expect.next1 &&
+       cat expect.next1 >dir/next1 &&
+       echo untracked >expect.next2 &&
+       cat expect.next2 >dir/next2 &&
+
+       git checkout master dir &&
+
+       test_cmp expect.common dir/common &&
+       test_path_is_file dir/master &&
+       git diff --exit-code master dir/master &&
+
+       test_path_is_missing dir/next0 &&
+       test_cmp expect.next1 dir/next1 &&
+       test_path_is_file dir/next2 &&
+       test_must_fail git ls-files --error-unmatch dir/next2 &&
+       test_cmp expect.next2 dir/next2
+'
+
+test_done
index 2eec0118c4235c0aa9d85cb7112e1f72b49c5c5f..88be904c09214586b18f867306f7a8dcf1170cb7 100755 (executable)
@@ -65,4 +65,23 @@ test_expect_success '--no-empty-directory hides empty directory' '
        test_cmp expected3 output
 '
 
+test_expect_success SYMLINKS 'ls-files --others with symlinked submodule' '
+       git init super &&
+       git init sub &&
+       (
+               cd sub &&
+               >a &&
+               git add a &&
+               git commit -m sub &&
+               git pack-refs --all
+       ) &&
+       (
+               cd super &&
+               "$SHELL_PATH" "$TEST_DIRECTORY/../contrib/workdir/git-new-workdir" ../sub sub
+               git ls-files --others --exclude-standard >../actual
+       ) &&
+       echo sub/ >expect &&
+       test_cmp expect actual
+'
+
 test_done
index 7633930bb472e1c9aebf2799f3eaf3bc53c7263c..bc73c2099b5069ab136a1d93aef5a8ab4a0dad1f 100755 (executable)
@@ -23,7 +23,7 @@ test_expect_success \
 test_expect_success \
     'git branch --help should not have created a bogus branch' '
      git branch --help </dev/null >/dev/null 2>/dev/null;
-     ! test -f .git/refs/heads/--help
+     test_path_is_missing .git/refs/heads/--help
 '
 
 test_expect_success 'branch -h in broken repository' '
@@ -39,11 +39,11 @@ test_expect_success 'branch -h in broken repository' '
 
 test_expect_success \
     'git branch abc should create a branch' \
-    'git branch abc && test -f .git/refs/heads/abc'
+    'git branch abc && test_path_is_file .git/refs/heads/abc'
 
 test_expect_success \
     'git branch a/b/c should create a branch' \
-    'git branch a/b/c && test -f .git/refs/heads/a/b/c'
+    'git branch a/b/c && test_path_is_file .git/refs/heads/a/b/c'
 
 cat >expect <<EOF
 $_z40 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000        branch: Created from master
@@ -52,15 +52,15 @@ test_expect_success \
     'git branch -l d/e/f should create a branch and a log' \
        'GIT_COMMITTER_DATE="2005-05-26 23:30" \
      git branch -l d/e/f &&
-        test -f .git/refs/heads/d/e/f &&
-        test -f .git/logs/refs/heads/d/e/f &&
+        test_path_is_file .git/refs/heads/d/e/f &&
+        test_path_is_file .git/logs/refs/heads/d/e/f &&
         test_cmp expect .git/logs/refs/heads/d/e/f'
 
 test_expect_success \
     'git branch -d d/e/f should delete a branch and a log' \
        'git branch -d d/e/f &&
-        test ! -f .git/refs/heads/d/e/f &&
-        test ! -f .git/logs/refs/heads/d/e/f'
+        test_path_is_missing .git/refs/heads/d/e/f &&
+        test_path_is_missing .git/logs/refs/heads/d/e/f'
 
 test_expect_success \
     'git branch j/k should work after branch j has been deleted' \
@@ -74,17 +74,22 @@ test_expect_success \
         git branch -d l/m &&
         git branch l'
 
+test_expect_success \
+    'git branch -m dumps usage' \
+       'test_expect_code 129 git branch -m 2>err &&
+       grep "[Uu]sage: git branch" err'
+
 test_expect_success \
     'git branch -m m m/m should work' \
        'git branch -l m &&
         git branch -m m m/m &&
-        test -f .git/logs/refs/heads/m/m'
+       test_path_is_file .git/logs/refs/heads/m/m'
 
 test_expect_success \
     'git branch -m n/n n should work' \
        'git branch -l n/n &&
         git branch -m n/n n
-        test -f .git/logs/refs/heads/n'
+       test_path_is_file .git/logs/refs/heads/n'
 
 test_expect_success 'git branch -m o/o o should fail when o/p exists' '
        git branch o/o &&
@@ -110,6 +115,38 @@ test_expect_success 'git branch -M baz bam should succeed when baz is checked ou
        git branch -M baz bam
 '
 
+test_expect_success 'git branch -v -d t should work' '
+       git branch t &&
+       test_path_is_file .git/refs/heads/t &&
+       git branch -v -d t &&
+       test_path_is_missing .git/refs/heads/t
+'
+
+test_expect_success 'git branch -v -m t s should work' '
+       git branch t &&
+       test_path_is_file .git/refs/heads/t &&
+       git branch -v -m t s &&
+       test_path_is_missing .git/refs/heads/t &&
+       test_path_is_file .git/refs/heads/s &&
+       git branch -d s
+'
+
+test_expect_success 'git branch -m -d t s should fail' '
+       git branch t &&
+       test_path_is_file .git/refs/heads/t &&
+       test_must_fail git branch -m -d t s &&
+       git branch -d t &&
+       test_path_is_missing .git/refs/heads/t
+'
+
+test_expect_success 'git branch --list -d t should fail' '
+       git branch t &&
+       test_path_is_file .git/refs/heads/t &&
+       test_must_fail git branch --list -d t &&
+       git branch -d t &&
+       test_path_is_missing .git/refs/heads/t
+'
+
 mv .git/config .git/config-saved
 
 test_expect_success 'git branch -m q q2 without config should succeed' '
@@ -124,12 +161,12 @@ git config branch.s/s.dummy Hello
 test_expect_success \
     'git branch -m s/s s should work when s/t is deleted' \
        'git branch -l s/s &&
-        test -f .git/logs/refs/heads/s/s &&
+       test_path_is_file .git/logs/refs/heads/s/s &&
         git branch -l s/t &&
-        test -f .git/logs/refs/heads/s/t &&
+       test_path_is_file .git/logs/refs/heads/s/t &&
         git branch -d s/t &&
         git branch -m s/s s &&
-        test -f .git/logs/refs/heads/s'
+       test_path_is_file .git/logs/refs/heads/s'
 
 test_expect_success 'config information was renamed, too' \
        "test $(git config branch.s.dummy) = Hello &&
@@ -140,8 +177,8 @@ test_expect_success 'renaming a symref is not allowed' \
        git symbolic-ref refs/heads/master2 refs/heads/master &&
        test_must_fail git branch -m master2 master3 &&
        git symbolic-ref refs/heads/master2 &&
-       test -f .git/refs/heads/master &&
-       ! test -f .git/refs/heads/master3
+       test_path_is_file .git/refs/heads/master &&
+       test_path_is_missing .git/refs/heads/master3
 '
 
 test_expect_success SYMLINKS \
@@ -250,8 +287,8 @@ test_expect_success \
     'git checkout -b g/h/i -l should create a branch and a log' \
        'GIT_COMMITTER_DATE="2005-05-26 23:30" \
      git checkout -b g/h/i -l master &&
-        test -f .git/refs/heads/g/h/i &&
-        test -f .git/logs/refs/heads/g/h/i &&
+        test_path_is_file .git/refs/heads/g/h/i &&
+        test_path_is_file .git/logs/refs/heads/g/h/i &&
         test_cmp expect .git/logs/refs/heads/g/h/i'
 
 test_expect_success 'checkout -b makes reflog by default' '
index 6b7c118e4fcbf3855318b2e9e3b721d07950fd25..76fe7e0060c20507cb6eb317451b69a2a0c9146d 100755 (executable)
@@ -32,6 +32,20 @@ test_expect_success 'git branch shows local branches' '
        test_cmp expect actual
 '
 
+test_expect_success 'git branch --list shows local branches' '
+       git branch --list >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+  branch-one
+  branch-two
+EOF
+test_expect_success 'git branch --list pattern shows matching local branches' '
+       git branch --list branch* >actual &&
+       test_cmp expect actual
+'
+
 cat >expect <<'EOF'
   origin/HEAD -> origin/branch-one
   origin/branch-one
@@ -66,6 +80,20 @@ test_expect_success 'git branch -v shows branch summaries' '
        test_cmp expect actual
 '
 
+cat >expect <<'EOF'
+two
+one
+EOF
+test_expect_success 'git branch --list -v pattern shows branch summaries' '
+       git branch --list -v branch* >tmp &&
+       awk "{print \$NF}" <tmp >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'git branch -v pattern does not show branch summaries' '
+       test_must_fail git branch -v branch*
+'
+
 cat >expect <<'EOF'
 * (no branch)
   branch-one
index 212ec54aaf0d805bbdecd93f3311d248a57d5b08..ee1659c17810d2c9ce7836582a80fd3d0d89ca24 100755 (executable)
@@ -77,6 +77,21 @@ test_expect_success 'cherry-pick --no-commit does not set CHERRY_PICK_HEAD' '
        test_must_fail git rev-parse --verify CHERRY_PICK_HEAD
 '
 
+test_expect_success 'cherry-pick w/dirty tree does not set CHERRY_PICK_HEAD' '
+       pristine_detach initial &&
+       echo foo > foo &&
+       test_must_fail git cherry-pick base &&
+       test_must_fail git rev-parse --verify CHERRY_PICK_HEAD
+'
+
+test_expect_success \
+       'cherry-pick --strategy=resolve w/dirty tree does not set CHERRY_PICK_HEAD' '
+       pristine_detach initial &&
+       echo foo > foo &&
+       test_must_fail git cherry-pick --strategy=resolve base &&
+       test_must_fail git rev-parse --verify CHERRY_PICK_HEAD
+'
+
 test_expect_success 'GIT_CHERRY_PICK_HELP suppresses CHERRY_PICK_HEAD' '
        pristine_detach initial &&
        (
@@ -238,6 +253,60 @@ test_expect_success 'revert also handles conflicts sanely' '
        test_cmp expected actual
 '
 
+test_expect_success 'failed revert sets REVERT_HEAD' '
+       pristine_detach initial &&
+       test_must_fail git revert picked &&
+       test_cmp_rev picked REVERT_HEAD
+'
+
+test_expect_success 'successful revert does not set REVERT_HEAD' '
+       pristine_detach base &&
+       git revert base &&
+       test_must_fail git rev-parse --verify CHERRY_PICK_HEAD &&
+       test_must_fail git rev-parse --verify REVERT_HEAD
+'
+
+test_expect_success 'revert --no-commit sets REVERT_HEAD' '
+       pristine_detach base &&
+       git revert --no-commit base &&
+       test_must_fail git rev-parse --verify CHERRY_PICK_HEAD &&
+       test_cmp_rev base REVERT_HEAD
+'
+
+test_expect_success 'revert w/dirty tree does not set REVERT_HEAD' '
+       pristine_detach base &&
+       echo foo > foo &&
+       test_must_fail git revert base &&
+       test_must_fail git rev-parse --verify CHERRY_PICK_HEAD &&
+       test_must_fail git rev-parse --verify REVERT_HEAD
+'
+
+test_expect_success 'GIT_CHERRY_PICK_HELP does not suppress REVERT_HEAD' '
+       pristine_detach initial &&
+       (
+               GIT_CHERRY_PICK_HELP="and then do something else" &&
+               GIT_REVERT_HELP="and then do something else, again" &&
+               export GIT_CHERRY_PICK_HELP GIT_REVERT_HELP &&
+               test_must_fail git revert picked
+       ) &&
+       test_must_fail git rev-parse --verify CHERRY_PICK_HEAD &&
+       test_cmp_rev picked REVERT_HEAD
+'
+
+test_expect_success 'git reset clears REVERT_HEAD' '
+       pristine_detach initial &&
+       test_must_fail git revert picked &&
+       git reset &&
+       test_must_fail git rev-parse --verify REVERT_HEAD
+'
+
+test_expect_success 'failed commit does not clear REVERT_HEAD' '
+       pristine_detach initial &&
+       test_must_fail git revert picked &&
+       test_must_fail git commit &&
+       test_cmp_rev picked REVERT_HEAD
+'
+
 test_expect_success 'revert conflict, diff3 -m style' '
        pristine_detach initial &&
        git config merge.conflictstyle diff3 &&
index 3bca2b3dd5cc252cad615b83f84891f6dc2bacfe..2c4c1c851dcbb1cd38edc2a2620e2b04e36f7192 100755 (executable)
@@ -2,6 +2,7 @@
 
 test_description='Test cherry-pick continuation features
 
+  + yetanotherpick: rewrites foo to e
   + anotherpick: rewrites foo to d
   + picked: rewrites foo to c
   + unrelatedpick: rewrites unrelated to reallyunrelated
@@ -13,12 +14,18 @@ test_description='Test cherry-pick continuation features
 . ./test-lib.sh
 
 pristine_detach () {
-       git cherry-pick --reset &&
+       git cherry-pick --quit &&
        git checkout -f "$1^0" &&
        git read-tree -u --reset HEAD &&
        git clean -d -f -f -q -x
 }
 
+test_cmp_rev () {
+       git rev-parse --verify "$1" >expect.rev &&
+       git rev-parse --verify "$2" >actual.rev &&
+       test_cmp expect.rev actual.rev
+}
+
 test_expect_success setup '
        echo unrelated >unrelated &&
        git add unrelated &&
@@ -27,6 +34,7 @@ test_expect_success setup '
        test_commit unrelatedpick unrelated reallyunrelated &&
        test_commit picked foo c &&
        test_commit anotherpick foo d &&
+       test_commit yetanotherpick foo e &&
        git config advice.detachedhead false
 
 '
@@ -70,18 +78,117 @@ test_expect_success 'cherry-pick cleans up sequencer state upon success' '
        test_path_is_missing .git/sequencer
 '
 
-test_expect_success '--reset does not complain when no cherry-pick is in progress' '
+test_expect_success '--quit does not complain when no cherry-pick is in progress' '
        pristine_detach initial &&
-       git cherry-pick --reset
+       git cherry-pick --quit
 '
 
-test_expect_success '--reset cleans up sequencer state' '
+test_expect_success '--abort requires cherry-pick in progress' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick --abort
+'
+
+test_expect_success '--quit cleans up sequencer state' '
        pristine_detach initial &&
        test_must_fail git cherry-pick base..picked &&
-       git cherry-pick --reset &&
+       git cherry-pick --quit &&
        test_path_is_missing .git/sequencer
 '
 
+test_expect_success '--quit keeps HEAD and conflicted index intact' '
+       pristine_detach initial &&
+       cat >expect <<-\EOF &&
+       OBJID
+       :100644 100644 OBJID OBJID M    unrelated
+       OBJID
+       :000000 100644 OBJID OBJID A    foo
+       :000000 100644 OBJID OBJID A    unrelated
+       EOF
+       test_must_fail git cherry-pick base..picked &&
+       git cherry-pick --quit &&
+       test_path_is_missing .git/sequencer &&
+       test_must_fail git update-index --refresh &&
+       {
+               git rev-list HEAD |
+               git diff-tree --root --stdin |
+               sed "s/$_x40/OBJID/g"
+       } >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '--abort to cancel multiple cherry-pick' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick base..anotherpick &&
+       git cherry-pick --abort &&
+       test_path_is_missing .git/sequencer &&
+       test_cmp_rev initial HEAD &&
+       git update-index --refresh &&
+       git diff-index --exit-code HEAD
+'
+
+test_expect_success '--abort to cancel single cherry-pick' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick picked &&
+       git cherry-pick --abort &&
+       test_path_is_missing .git/sequencer &&
+       test_cmp_rev initial HEAD &&
+       git update-index --refresh &&
+       git diff-index --exit-code HEAD
+'
+
+test_expect_success 'cherry-pick --abort to cancel multiple revert' '
+       pristine_detach anotherpick &&
+       test_must_fail git revert base..picked &&
+       git cherry-pick --abort &&
+       test_path_is_missing .git/sequencer &&
+       test_cmp_rev anotherpick HEAD &&
+       git update-index --refresh &&
+       git diff-index --exit-code HEAD
+'
+
+test_expect_success 'revert --abort works, too' '
+       pristine_detach anotherpick &&
+       test_must_fail git revert base..picked &&
+       git revert --abort &&
+       test_path_is_missing .git/sequencer &&
+       test_cmp_rev anotherpick HEAD
+'
+
+test_expect_success '--abort to cancel single revert' '
+       pristine_detach anotherpick &&
+       test_must_fail git revert picked &&
+       git revert --abort &&
+       test_path_is_missing .git/sequencer &&
+       test_cmp_rev anotherpick HEAD &&
+       git update-index --refresh &&
+       git diff-index --exit-code HEAD
+'
+
+test_expect_success '--abort keeps unrelated change, easy case' '
+       pristine_detach unrelatedpick &&
+       echo changed >expect &&
+       test_must_fail git cherry-pick picked..yetanotherpick &&
+       echo changed >unrelated &&
+       git cherry-pick --abort &&
+       test_cmp expect unrelated
+'
+
+test_expect_success '--abort refuses to clobber unrelated change, harder case' '
+       pristine_detach initial &&
+       echo changed >expect &&
+       test_must_fail git cherry-pick base..anotherpick &&
+       echo changed >unrelated &&
+       test_must_fail git cherry-pick --abort &&
+       test_cmp expect unrelated &&
+       git rev-list HEAD >log &&
+       test_line_count = 2 log &&
+       test_must_fail git update-index --refresh &&
+
+       git checkout unrelated &&
+       git cherry-pick --abort &&
+       test_cmp_rev initial HEAD
+'
+
 test_expect_success 'cherry-pick cleans up sequencer state when one commit is left' '
        pristine_detach initial &&
        test_must_fail git cherry-pick base..picked &&
@@ -106,6 +213,16 @@ test_expect_success 'cherry-pick cleans up sequencer state when one commit is le
        test_cmp expect actual
 '
 
+test_expect_failure '--abort after last commit in sequence' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick base..picked &&
+       git cherry-pick --abort &&
+       test_path_is_missing .git/sequencer &&
+       test_cmp_rev initial HEAD &&
+       git update-index --refresh &&
+       git diff-index --exit-code HEAD
+'
+
 test_expect_success 'cherry-pick does not implicitly stomp an existing operation' '
        pristine_detach initial &&
        test_must_fail git cherry-pick base..anotherpick &&
diff --git a/t/t4051-diff-function-context.sh b/t/t4051-diff-function-context.sh
new file mode 100755 (executable)
index 0000000..001d678
--- /dev/null
@@ -0,0 +1,92 @@
+#!/bin/sh
+
+test_description='diff function context'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/diff-lib.sh
+
+
+cat <<\EOF >hello.c
+#include <stdio.h>
+
+static int a(void)
+{
+       /*
+        * Dummy.
+        */
+}
+
+static int hello_world(void)
+{
+       /* Classic. */
+       printf("Hello world.\n");
+
+       /* Success! */
+       return 0;
+}
+static int b(void)
+{
+       /*
+        * Dummy, too.
+        */
+}
+
+int main(int argc, char **argv)
+{
+       a();
+       b();
+       return hello_world();
+}
+EOF
+
+test_expect_success 'setup' '
+       git add hello.c &&
+       test_tick &&
+       git commit -m initial &&
+
+       grep -v Classic <hello.c >hello.c.new &&
+       mv hello.c.new hello.c
+'
+
+cat <<\EOF >expected
+diff --git a/hello.c b/hello.c
+--- a/hello.c
++++ b/hello.c
+@@ -10,8 +10,7 @@ static int a(void)
+ static int hello_world(void)
+ {
+-      /* Classic. */
+       printf("Hello world.\n");
+       /* Success! */
+       return 0;
+ }
+EOF
+
+test_expect_success 'diff -U0 -W' '
+       git diff -U0 -W >actual &&
+       compare_diff_patch actual expected
+'
+
+cat <<\EOF >expected
+diff --git a/hello.c b/hello.c
+--- a/hello.c
++++ b/hello.c
+@@ -9,9 +9,8 @@ static int a(void)
+ static int hello_world(void)
+ {
+-      /* Classic. */
+       printf("Hello world.\n");
+       /* Success! */
+       return 0;
+ }
+EOF
+
+test_expect_success 'diff -W' '
+       git diff -W >actual &&
+       compare_diff_patch actual expected
+'
+
+test_done
diff --git a/t/t4254-am-corrupt.sh b/t/t4254-am-corrupt.sh
new file mode 100755 (executable)
index 0000000..b7da95f
--- /dev/null
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+test_description='git am with corrupt input'
+. ./test-lib.sh
+
+# Note the missing "+++" line:
+cat > bad-patch.diff <<'EOF'
+From: A U Thor <au.thor@example.com>
+diff --git a/f b/f
+index 7898192..6178079 100644
+--- a/f
+@@ -1 +1 @@
+-a
++b
+EOF
+
+test_expect_success setup '
+       test $? = 0 &&
+       echo a > f &&
+       git add f &&
+       test_tick &&
+       git commit -m initial
+'
+
+# This used to fail before, too, but with a different diagnostic.
+#   fatal: unable to write file '(null)' mode 100644: Bad address
+# Also, it had the unwanted side-effect of deleting f.
+test_expect_success 'try to apply corrupted patch' '
+       git am bad-patch.diff 2> actual
+       test $? = 1
+'
+
+cat > expected <<EOF
+fatal: git diff header lacks filename information (line 4)
+EOF
+
+test_expect_success 'compare diagnostic; ensure file is still here' '
+       test $? = 0 &&
+       test -f f &&
+       test_cmp expected actual
+'
+
+test_done
index 02d4d2284d8bcf9637dbd6a670e376b4ae9f7af1..f47d8717fdd93cf8ebf356c2675511567782335e 100755 (executable)
@@ -57,6 +57,15 @@ test_expect_missing  worktree/ignored
 test_expect_exists     worktree/ignored-by-tree
 test_expect_missing    worktree/ignored-by-worktree
 
+test_expect_success 'git archive --worktree-attributes option' '
+       git archive --worktree-attributes --worktree-attributes HEAD >worktree.tar &&
+       (mkdir worktree2 && cd worktree2 && "$TAR" xf -) <worktree.tar
+'
+
+test_expect_missing    worktree2/ignored
+test_expect_exists     worktree2/ignored-by-tree
+test_expect_missing    worktree2/ignored-by-worktree
+
 test_expect_success 'git archive vs. bare' '
        (cd bare && git archive HEAD) >bare-archive.tar &&
        test_cmp archive.tar bare-archive.tar
index d05a9138b46eec072750142f912a41caa3e23d83..1753ef2b91e3e5846f12fc16e1209b1895210f3b 100755 (executable)
@@ -31,44 +31,44 @@ EOF
 done
 
 test_expect_success 'post-checkout runs as expected ' '
-        GIT_DIR=clone1/.git git checkout master &&
-        test -e clone1/.git/post-checkout.args
+       GIT_DIR=clone1/.git git checkout master &&
+       test -e clone1/.git/post-checkout.args
 '
 
 test_expect_success 'post-checkout receives the right arguments with HEAD unchanged ' '
-        old=$(awk "{print \$1}" clone1/.git/post-checkout.args) &&
-        new=$(awk "{print \$2}" clone1/.git/post-checkout.args) &&
-        flag=$(awk "{print \$3}" clone1/.git/post-checkout.args) &&
-        test $old = $new -a $flag = 1
+       old=$(awk "{print \$1}" clone1/.git/post-checkout.args) &&
+       new=$(awk "{print \$2}" clone1/.git/post-checkout.args) &&
+       flag=$(awk "{print \$3}" clone1/.git/post-checkout.args) &&
+       test $old = $new -a $flag = 1
 '
 
 test_expect_success 'post-checkout runs as expected ' '
-        GIT_DIR=clone1/.git git checkout master &&
-        test -e clone1/.git/post-checkout.args
+       GIT_DIR=clone1/.git git checkout master &&
+       test -e clone1/.git/post-checkout.args
 '
 
 test_expect_success 'post-checkout args are correct with git checkout -b ' '
-        GIT_DIR=clone1/.git git checkout -b new1 &&
-        old=$(awk "{print \$1}" clone1/.git/post-checkout.args) &&
-        new=$(awk "{print \$2}" clone1/.git/post-checkout.args) &&
-        flag=$(awk "{print \$3}" clone1/.git/post-checkout.args) &&
-        test $old = $new -a $flag = 1
+       GIT_DIR=clone1/.git git checkout -b new1 &&
+       old=$(awk "{print \$1}" clone1/.git/post-checkout.args) &&
+       new=$(awk "{print \$2}" clone1/.git/post-checkout.args) &&
+       flag=$(awk "{print \$3}" clone1/.git/post-checkout.args) &&
+       test $old = $new -a $flag = 1
 '
 
 test_expect_success 'post-checkout receives the right args with HEAD changed ' '
-        GIT_DIR=clone2/.git git checkout new2 &&
-        old=$(awk "{print \$1}" clone2/.git/post-checkout.args) &&
-        new=$(awk "{print \$2}" clone2/.git/post-checkout.args) &&
-        flag=$(awk "{print \$3}" clone2/.git/post-checkout.args) &&
-        test $old != $new -a $flag = 1
+       GIT_DIR=clone2/.git git checkout new2 &&
+       old=$(awk "{print \$1}" clone2/.git/post-checkout.args) &&
+       new=$(awk "{print \$2}" clone2/.git/post-checkout.args) &&
+       flag=$(awk "{print \$3}" clone2/.git/post-checkout.args) &&
+       test $old != $new -a $flag = 1
 '
 
 test_expect_success 'post-checkout receives the right args when not switching branches ' '
-        GIT_DIR=clone2/.git git checkout master b &&
-        old=$(awk "{print \$1}" clone2/.git/post-checkout.args) &&
-        new=$(awk "{print \$2}" clone2/.git/post-checkout.args) &&
-        flag=$(awk "{print \$3}" clone2/.git/post-checkout.args) &&
-        test $old = $new -a $flag = 0
+       GIT_DIR=clone2/.git git checkout master b &&
+       old=$(awk "{print \$1}" clone2/.git/post-checkout.args) &&
+       new=$(awk "{print \$2}" clone2/.git/post-checkout.args) &&
+       flag=$(awk "{print \$3}" clone2/.git/post-checkout.args) &&
+       test $old = $new -a $flag = 0
 '
 
 if test "$(git config --bool core.filemode)" = true; then
index b5ced8483a8fc663eb53b514bbb56df92f217deb..1bc57ac03f9df7e82fab5478b7234ab1435c60e3 100755 (executable)
@@ -28,7 +28,7 @@ test_expect_success setup '
                done
        ) &&
        (
-               git clone --reference=original "file:///$(pwd)/original" one &&
+               git clone --reference=original "file://$(pwd)/original" one &&
                cd one &&
                echo Z >count &&
                git add count &&
index 0d0222ea2ace733ffe3fbfbdee58520abcd40e92..e8af615e6dcdf365416ef9f71825c17a0d601186 100755 (executable)
@@ -631,6 +631,37 @@ test_expect_success 'rename a remote' '
 
 '
 
+test_expect_success 'rename does not update a non-default fetch refspec' '
+
+       git clone one four.one &&
+       (cd four.one &&
+        git config remote.origin.fetch +refs/heads/*:refs/heads/origin/* &&
+        git remote rename origin upstream &&
+        test "$(git config remote.upstream.fetch)" = "+refs/heads/*:refs/heads/origin/*" &&
+        git rev-parse -q origin/master)
+
+'
+
+test_expect_success 'rename a remote with name part of fetch spec' '
+
+       git clone one four.two &&
+       (cd four.two &&
+        git remote rename origin remote &&
+        git remote rename remote upstream &&
+        test "$(git config remote.upstream.fetch)" = "+refs/heads/*:refs/remotes/upstream/*")
+
+'
+
+test_expect_success 'rename a remote with name prefix of other remote' '
+
+       git clone one four.three &&
+       (cd four.three &&
+        git remote add o git://example.com/repo.git &&
+        git remote rename o upstream &&
+        test "$(git rev-parse origin/master)" = "$(git rev-parse master)")
+
+'
+
 cat > remotes_origin << EOF
 URL: $(pwd)/one
 Push: refs/heads/master:refs/heads/upstream
index 7e433b179f9fcb0b3ccdd0ec83c6ec850735e391..e0af4c4e62c40a563f692361944cc58e2c8e38e2 100755 (executable)
@@ -76,6 +76,56 @@ test_expect_success "fetch test for-merge" '
        cut -f -2 .git/FETCH_HEAD >actual &&
        test_cmp expected actual'
 
+test_expect_success 'fetch --prune on its own works as expected' '
+       cd "$D" &&
+       git clone . prune &&
+       cd prune &&
+       git fetch origin refs/heads/master:refs/remotes/origin/extrabranch &&
+
+       git fetch --prune origin &&
+       test_must_fail git rev-parse origin/extrabranch
+'
+
+test_expect_success 'fetch --prune with a branch name keeps branches' '
+       cd "$D" &&
+       git clone . prune-branch &&
+       cd prune-branch &&
+       git fetch origin refs/heads/master:refs/remotes/origin/extrabranch &&
+
+       git fetch --prune origin master &&
+       git rev-parse origin/extrabranch
+'
+
+test_expect_success 'fetch --prune with a namespace keeps other namespaces' '
+       cd "$D" &&
+       git clone . prune-namespace &&
+       cd prune-namespace &&
+
+       git fetch --prune origin refs/heads/a/*:refs/remotes/origin/a/* &&
+       git rev-parse origin/master
+'
+
+test_expect_success 'fetch --prune --tags does not delete the remote-tracking branches' '
+       cd "$D" &&
+       git clone . prune-tags &&
+       cd prune-tags &&
+       git fetch origin refs/heads/master:refs/tags/sometag &&
+
+       git fetch --prune --tags origin &&
+       git rev-parse origin/master &&
+       test_must_fail git rev-parse somebranch
+'
+
+test_expect_success 'fetch --prune --tags with branch does not delete other remote-tracking branches' '
+       cd "$D" &&
+       git clone . prune-tags-branch &&
+       cd prune-tags-branch &&
+       git fetch origin refs/heads/master:refs/remotes/origin/extrabranch &&
+
+       git fetch --prune --tags origin master &&
+       git rev-parse origin/extrabranch
+'
+
 test_expect_success 'fetch tags when there is no tags' '
 
     cd "$D" &&
index 3abb2907ea91c5ef6298ca7ee64b7a8156d65d96..b69cf574d7e9a272ea70864e87ed6556abe94f13 100755 (executable)
@@ -40,6 +40,40 @@ mk_test () {
        )
 }
 
+mk_test_with_hooks() {
+       mk_test "$@" &&
+       (
+               cd testrepo &&
+               mkdir .git/hooks &&
+               cd .git/hooks &&
+
+               cat >pre-receive <<-'EOF' &&
+               #!/bin/sh
+               cat - >>pre-receive.actual
+               EOF
+
+               cat >update <<-'EOF' &&
+               #!/bin/sh
+               printf "%s %s %s\n" "$@" >>update.actual
+               EOF
+
+               cat >post-receive <<-'EOF' &&
+               #!/bin/sh
+               cat - >>post-receive.actual
+               EOF
+
+               cat >post-update <<-'EOF' &&
+               #!/bin/sh
+               for ref in "$@"
+               do
+                       printf "%s\n" "$ref" >>post-update.actual
+               done
+               EOF
+
+               chmod +x pre-receive update post-receive post-update
+       )
+}
+
 mk_child() {
        rm -rf "$1" &&
        git clone testrepo "$1"
@@ -559,6 +593,169 @@ test_expect_success 'allow deleting an invalid remote ref' '
 
 '
 
+test_expect_success 'pushing valid refs triggers post-receive and post-update hooks' '
+       mk_test_with_hooks heads/master heads/next &&
+       orgmaster=$(cd testrepo && git show-ref -s --verify refs/heads/master) &&
+       newmaster=$(git show-ref -s --verify refs/heads/master) &&
+       orgnext=$(cd testrepo && git show-ref -s --verify refs/heads/next) &&
+       newnext=$_z40 &&
+       git push testrepo refs/heads/master:refs/heads/master :refs/heads/next &&
+       (
+               cd testrepo/.git &&
+               cat >pre-receive.expect <<-EOF &&
+               $orgmaster $newmaster refs/heads/master
+               $orgnext $newnext refs/heads/next
+               EOF
+
+               cat >update.expect <<-EOF &&
+               refs/heads/master $orgmaster $newmaster
+               refs/heads/next $orgnext $newnext
+               EOF
+
+               cat >post-receive.expect <<-EOF &&
+               $orgmaster $newmaster refs/heads/master
+               $orgnext $newnext refs/heads/next
+               EOF
+
+               cat >post-update.expect <<-EOF &&
+               refs/heads/master
+               refs/heads/next
+               EOF
+
+               test_cmp pre-receive.expect pre-receive.actual &&
+               test_cmp update.expect update.actual &&
+               test_cmp post-receive.expect post-receive.actual &&
+               test_cmp post-update.expect post-update.actual
+       )
+'
+
+test_expect_success 'deleting dangling ref triggers hooks with correct args' '
+       mk_test_with_hooks heads/master &&
+       rm -f testrepo/.git/objects/??/* &&
+       git push testrepo :refs/heads/master &&
+       (
+               cd testrepo/.git &&
+               cat >pre-receive.expect <<-EOF &&
+               $_z40 $_z40 refs/heads/master
+               EOF
+
+               cat >update.expect <<-EOF &&
+               refs/heads/master $_z40 $_z40
+               EOF
+
+               cat >post-receive.expect <<-EOF &&
+               $_z40 $_z40 refs/heads/master
+               EOF
+
+               cat >post-update.expect <<-EOF &&
+               refs/heads/master
+               EOF
+
+               test_cmp pre-receive.expect pre-receive.actual &&
+               test_cmp update.expect update.actual &&
+               test_cmp post-receive.expect post-receive.actual &&
+               test_cmp post-update.expect post-update.actual
+       )
+'
+
+test_expect_success 'deletion of a non-existent ref is not fed to post-receive and post-update hooks' '
+       mk_test_with_hooks heads/master &&
+       orgmaster=$(cd testrepo && git show-ref -s --verify refs/heads/master) &&
+       newmaster=$(git show-ref -s --verify refs/heads/master) &&
+       git push testrepo master :refs/heads/nonexistent &&
+       (
+               cd testrepo/.git &&
+               cat >pre-receive.expect <<-EOF &&
+               $orgmaster $newmaster refs/heads/master
+               $_z40 $_z40 refs/heads/nonexistent
+               EOF
+
+               cat >update.expect <<-EOF &&
+               refs/heads/master $orgmaster $newmaster
+               refs/heads/nonexistent $_z40 $_z40
+               EOF
+
+               cat >post-receive.expect <<-EOF &&
+               $orgmaster $newmaster refs/heads/master
+               EOF
+
+               cat >post-update.expect <<-EOF &&
+               refs/heads/master
+               EOF
+
+               test_cmp pre-receive.expect pre-receive.actual &&
+               test_cmp update.expect update.actual &&
+               test_cmp post-receive.expect post-receive.actual &&
+               test_cmp post-update.expect post-update.actual
+       )
+'
+
+test_expect_success 'deletion of a non-existent ref alone does trigger post-receive and post-update hooks' '
+       mk_test_with_hooks heads/master &&
+       git push testrepo :refs/heads/nonexistent &&
+       (
+               cd testrepo/.git &&
+               cat >pre-receive.expect <<-EOF &&
+               $_z40 $_z40 refs/heads/nonexistent
+               EOF
+
+               cat >update.expect <<-EOF &&
+               refs/heads/nonexistent $_z40 $_z40
+               EOF
+
+               test_cmp pre-receive.expect pre-receive.actual &&
+               test_cmp update.expect update.actual &&
+               test_path_is_missing post-receive.actual &&
+               test_path_is_missing post-update.actual
+       )
+'
+
+test_expect_success 'mixed ref updates, deletes, invalid deletes trigger hooks with correct input' '
+       mk_test_with_hooks heads/master heads/next heads/pu &&
+       orgmaster=$(cd testrepo && git show-ref -s --verify refs/heads/master) &&
+       newmaster=$(git show-ref -s --verify refs/heads/master) &&
+       orgnext=$(cd testrepo && git show-ref -s --verify refs/heads/next) &&
+       newnext=$_z40 &&
+       orgpu=$(cd testrepo && git show-ref -s --verify refs/heads/pu) &&
+       newpu=$(git show-ref -s --verify refs/heads/master) &&
+       git push testrepo refs/heads/master:refs/heads/master \
+           refs/heads/master:refs/heads/pu :refs/heads/next \
+           :refs/heads/nonexistent &&
+       (
+               cd testrepo/.git &&
+               cat >pre-receive.expect <<-EOF &&
+               $orgmaster $newmaster refs/heads/master
+               $orgnext $newnext refs/heads/next
+               $orgpu $newpu refs/heads/pu
+               $_z40 $_z40 refs/heads/nonexistent
+               EOF
+
+               cat >update.expect <<-EOF &&
+               refs/heads/master $orgmaster $newmaster
+               refs/heads/next $orgnext $newnext
+               refs/heads/pu $orgpu $newpu
+               refs/heads/nonexistent $_z40 $_z40
+               EOF
+
+               cat >post-receive.expect <<-EOF &&
+               $orgmaster $newmaster refs/heads/master
+               $orgnext $newnext refs/heads/next
+               $orgpu $newpu refs/heads/pu
+               EOF
+
+               cat >post-update.expect <<-EOF &&
+               refs/heads/master
+               refs/heads/next
+               refs/heads/pu
+               EOF
+
+               test_cmp pre-receive.expect pre-receive.actual &&
+               test_cmp update.expect update.actual &&
+               test_cmp post-receive.expect post-receive.actual &&
+               test_cmp post-update.expect post-update.actual
+       )
+'
+
 test_expect_success 'allow deleting a ref using --delete' '
        mk_test heads/master &&
        (cd testrepo && git config receive.denyDeleteCurrent warn) &&
index a1883ca6b649a236e5e08a5e4b76d1479353dcac..311a33ca84f693b9410bab9e282ad0ebcae87441 100755 (executable)
@@ -8,8 +8,8 @@ if test -n "$NO_CURL"; then
        test_done
 fi
 
-. "$TEST_DIRECTORY"/lib-httpd.sh
 LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5550'}
+. "$TEST_DIRECTORY"/lib-httpd.sh
 start_httpd
 
 test_expect_success 'setup repository' '
@@ -35,11 +35,54 @@ test_expect_success 'clone http repository' '
        test_cmp file clone/file
 '
 
-test_expect_success 'clone http repository with authentication' '
+test_expect_success 'create password-protected repository' '
        mkdir "$HTTPD_DOCUMENT_ROOT_PATH/auth/" &&
-       cp -Rf "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" "$HTTPD_DOCUMENT_ROOT_PATH/auth/repo.git" &&
-       git clone $AUTH_HTTPD_URL/auth/repo.git clone-auth &&
-       test_cmp file clone-auth/file
+       cp -Rf "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" \
+              "$HTTPD_DOCUMENT_ROOT_PATH/auth/repo.git"
+'
+
+test_expect_success 'setup askpass helpers' '
+       cat >askpass <<-EOF &&
+       #!/bin/sh
+       echo >>"$PWD/askpass-query" "askpass: \$*" &&
+       cat "$PWD/askpass-response"
+       EOF
+       chmod +x askpass &&
+       GIT_ASKPASS="$PWD/askpass" &&
+       export GIT_ASKPASS &&
+       >askpass-expect-none &&
+       echo "askpass: Password for '\''$HTTPD_DEST'\'': " >askpass-expect-pass &&
+       { echo "askpass: Username for '\''$HTTPD_DEST'\'': " &&
+         cat askpass-expect-pass
+       } >askpass-expect-both
+'
+
+test_expect_success 'cloning password-protected repository can fail' '
+       >askpass-query &&
+       echo wrong >askpass-response &&
+       test_must_fail git clone "$HTTPD_URL/auth/repo.git" clone-auth-fail &&
+       test_cmp askpass-expect-both askpass-query
+'
+
+test_expect_success 'http auth can use user/pass in URL' '
+       >askpass-query &&
+       echo wrong >askpass-reponse &&
+       git clone "$HTTPD_URL_USER_PASS/auth/repo.git" clone-auth-none &&
+       test_cmp askpass-expect-none askpass-query
+'
+
+test_expect_success 'http auth can use just user in URL' '
+       >askpass-query &&
+       echo user@host >askpass-response &&
+       git clone "$HTTPD_URL_USER/auth/repo.git" clone-auth-pass &&
+       test_cmp askpass-expect-pass askpass-query
+'
+
+test_expect_success 'http auth can request both user and pass' '
+       >askpass-query &&
+       echo user@host >askpass-response &&
+       git clone "$HTTPD_URL/auth/repo.git" clone-auth-both &&
+       test_cmp askpass-expect-both askpass-query
 '
 
 test_expect_success 'fetch changes via http' '
index e8103144bb026afb12f5b058b9ec399b70abebbd..87ee01662c2fcfa58d8f9896fd43881e2e7ed7d6 100755 (executable)
@@ -206,6 +206,20 @@ test_expect_success 'clone from .git file' '
        git clone dst/.git dst2
 '
 
+test_expect_success 'fetch from .git gitfile' '
+       (
+               cd dst2 &&
+               git fetch ../dst/.git
+       )
+'
+
+test_expect_success 'fetch from gitfile parent' '
+       (
+               cd dst2 &&
+               git fetch ../dst
+       )
+'
+
 test_expect_success 'clone separate gitdir where target already exists' '
        rm -rf dst &&
        test_must_fail git clone --separate-git-dir realgitdir src dst
index 895f5595aee9341276e79497b9c4a8736c78e5e7..c4c375ac042bbb7f58998c87d8c9277d2f5004a6 100755 (executable)
@@ -146,4 +146,11 @@ test_expect_success 'cloning with reference being subset of source (-l -s)' \
 
 cd "$base_dir"
 
+test_expect_success 'clone with reference from a tagged repository' '
+       (
+               cd A && git tag -a -m 'tagged' HEAD
+       ) &&
+       git clone --reference=A A I
+'
+
 test_done
index c6f1f9f8ab2353ec81401828f8500cb7c1a869fb..691e4a4481eba2994ec40e498d4cd25fcff67f26 100755 (executable)
@@ -164,7 +164,7 @@ test_expect_success 'bisect start: existing ".git/BISECT_START" not modified if
        cp .git/BISECT_START saved &&
        test_must_fail git bisect start $HASH4 foo -- &&
        git branch > branch.output &&
-       grep "* (no branch)" branch.output > /dev/null &&
+       test_i18ngrep "* (no branch)" branch.output > /dev/null &&
        test_cmp saved .git/BISECT_START
 '
 test_expect_success 'bisect start: no ".git/BISECT_START" if mistaken rev' '
index 19de5b16eb530d428f66e5161a3ab48fce34049d..19272bc551277903bc1c444f4f0f05d8f2d7d672 100755 (executable)
@@ -51,6 +51,22 @@ test_expect_success 'branch -v' '
        test_i18ncmp expect actual
 '
 
+cat >expect <<\EOF
+b1 origin/master: ahead 1, behind 1
+b2 origin/master: ahead 1, behind 1
+b3 origin/master: behind 1
+b4 origin/master: ahead 2
+EOF
+
+test_expect_success 'branch -vv' '
+       (
+               cd test &&
+               git branch -vv
+       ) |
+       sed -n -e "$script" >actual &&
+       test_i18ncmp expect actual
+'
+
 test_expect_success 'checkout' '
        (
                cd test && git checkout b1
index 7dc8a510c7f33d048cd3268424298fdda2f8c2bb..172178490ad126e07b70a6e3f72e0bf723d2c42b 100755 (executable)
@@ -6,6 +6,7 @@
 test_description='for-each-ref test'
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-gpg.sh
 
 # Mon Jul 3 15:18:43 2006 +0000
 datestamp=1151939923
@@ -37,11 +38,13 @@ test_atom() {
        case "$1" in
                head) ref=refs/heads/master ;;
                 tag) ref=refs/tags/testtag ;;
+                  *) ref=$1 ;;
        esac
        printf '%s\n' "$3" >expected
-       test_expect_${4:-success} "basic atom: $1 $2" "
+       test_expect_${4:-success} $PREREQ "basic atom: $1 $2" "
                git for-each-ref --format='%($2)' $ref >actual &&
-               test_cmp expected actual
+               sanitize_pgp <actual >actual.clean &&
+               test_cmp expected actual.clean
        "
 }
 
@@ -71,7 +74,10 @@ test_atom head taggerdate ''
 test_atom head creator 'C O Mitter <committer@example.com> 1151939923 +0200'
 test_atom head creatordate 'Mon Jul 3 17:18:43 2006 +0200'
 test_atom head subject 'Initial'
+test_atom head contents:subject 'Initial'
 test_atom head body ''
+test_atom head contents:body ''
+test_atom head contents:signature ''
 test_atom head contents 'Initial
 '
 
@@ -101,7 +107,10 @@ test_atom tag taggerdate 'Mon Jul 3 17:18:45 2006 +0200'
 test_atom tag creator 'C O Mitter <committer@example.com> 1151939925 +0200'
 test_atom tag creatordate 'Mon Jul 3 17:18:45 2006 +0200'
 test_atom tag subject 'Tagging at 1151939927'
+test_atom tag contents:subject 'Tagging at 1151939927'
 test_atom tag body ''
+test_atom tag contents:body ''
+test_atom tag contents:signature ''
 test_atom tag contents 'Tagging at 1151939927
 '
 
@@ -359,4 +368,92 @@ test_expect_success 'an unusual tag with an incomplete line' '
 
 '
 
+test_expect_success 'create tag with subject and body content' '
+       cat >>msg <<-\EOF &&
+               the subject line
+
+               first body line
+               second body line
+       EOF
+       git tag -F msg subject-body
+'
+test_atom refs/tags/subject-body subject 'the subject line'
+test_atom refs/tags/subject-body body 'first body line
+second body line
+'
+test_atom refs/tags/subject-body contents 'the subject line
+
+first body line
+second body line
+'
+
+test_expect_success 'create tag with multiline subject' '
+       cat >msg <<-\EOF &&
+               first subject line
+               second subject line
+
+               first body line
+               second body line
+       EOF
+       git tag -F msg multiline
+'
+test_atom refs/tags/multiline subject 'first subject line second subject line'
+test_atom refs/tags/multiline contents:subject 'first subject line second subject line'
+test_atom refs/tags/multiline body 'first body line
+second body line
+'
+test_atom refs/tags/multiline contents:body 'first body line
+second body line
+'
+test_atom refs/tags/multiline contents:signature ''
+test_atom refs/tags/multiline contents 'first subject line
+second subject line
+
+first body line
+second body line
+'
+
+test_expect_success GPG 'create signed tags' '
+       git tag -s -m "" signed-empty &&
+       git tag -s -m "subject line" signed-short &&
+       cat >msg <<-\EOF &&
+       subject line
+
+       body contents
+       EOF
+       git tag -s -F msg signed-long
+'
+
+sig='-----BEGIN PGP SIGNATURE-----
+-----END PGP SIGNATURE-----
+'
+
+PREREQ=GPG
+test_atom refs/tags/signed-empty subject ''
+test_atom refs/tags/signed-empty contents:subject ''
+test_atom refs/tags/signed-empty body "$sig"
+test_atom refs/tags/signed-empty contents:body ''
+test_atom refs/tags/signed-empty contents:signature "$sig"
+test_atom refs/tags/signed-empty contents "$sig"
+
+test_atom refs/tags/signed-short subject 'subject line'
+test_atom refs/tags/signed-short contents:subject 'subject line'
+test_atom refs/tags/signed-short body "$sig"
+test_atom refs/tags/signed-short contents:body ''
+test_atom refs/tags/signed-short contents:signature "$sig"
+test_atom refs/tags/signed-short contents "subject line
+$sig"
+
+test_atom refs/tags/signed-long subject 'subject line'
+test_atom refs/tags/signed-long contents:subject 'subject line'
+test_atom refs/tags/signed-long body "body contents
+$sig"
+test_atom refs/tags/signed-long contents:body 'body contents
+'
+test_atom refs/tags/signed-long contents:signature "$sig"
+test_atom refs/tags/signed-long contents "subject line
+
+body contents
+$sig"
+
 test_done
index 097ce2bc8382e748925aa25c2dc0a1f06f6c7b4c..e93ac73829f332cdbf53b05fcc611d4ea38c4c55 100755 (executable)
@@ -8,6 +8,7 @@ test_description='git tag
 Tests for operations with tags.'
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-gpg.sh
 
 # creating and listing lightweight tags:
 
@@ -585,24 +586,6 @@ test_expect_success \
        test_cmp expect actual
 '
 
-# subsequent tests require gpg; check if it is available
-gpg --version >/dev/null 2>/dev/null
-if [ $? -eq 127 ]; then
-       say "# gpg not found - skipping tag signing and verification tests"
-else
-       # As said here: http://www.gnupg.org/documentation/faqs.html#q6.19
-       # the gpg version 1.0.6 didn't parse trust packets correctly, so for
-       # that version, creation of signed tags using the generated key fails.
-       case "$(gpg --version)" in
-       'gpg (GnuPG) 1.0.6'*)
-               say "Skipping signed tag tests, because a bug in 1.0.6 version"
-               ;;
-       *)
-               test_set_prereq GPG
-               ;;
-       esac
-fi
-
 # trying to verify annotated non-signed tags:
 
 test_expect_success GPG \
@@ -625,16 +608,6 @@ test_expect_success GPG \
 
 # creating and verifying signed tags:
 
-# key generation info: gpg --homedir t/t7004 --gen-key
-# Type DSA and Elgamal, size 2048 bits, no expiration date.
-# Name and email: C O Mitter <committer@example.com>
-# No password given, to enable non-interactive operation.
-
-cp -R "$TEST_DIRECTORY"/t7004 ./gpghome
-chmod 0700 gpghome
-GNUPGHOME="$(pwd)/gpghome"
-export GNUPGHOME
-
 get_tag_header signed-tag $commit commit $time >expect
 echo 'A signed tag message' >>expect
 echo '-----BEGIN PGP SIGNATURE-----' >>expect
diff --git a/t/t7004/pubring.gpg b/t/t7004/pubring.gpg
deleted file mode 100644 (file)
index 83855fa..0000000
Binary files a/t/t7004/pubring.gpg and /dev/null differ
diff --git a/t/t7004/random_seed b/t/t7004/random_seed
deleted file mode 100644 (file)
index 8fed133..0000000
Binary files a/t/t7004/random_seed and /dev/null differ
diff --git a/t/t7004/secring.gpg b/t/t7004/secring.gpg
deleted file mode 100644 (file)
index d831cd9..0000000
Binary files a/t/t7004/secring.gpg and /dev/null differ
diff --git a/t/t7004/trustdb.gpg b/t/t7004/trustdb.gpg
deleted file mode 100644 (file)
index abace96..0000000
Binary files a/t/t7004/trustdb.gpg and /dev/null differ
index 4956caaf82be8c42ef01a9adae55387e87adaa04..83f7ea59c9b4e05aa200bc4ebd77f0c16bd8ef33 100755 (executable)
@@ -12,7 +12,7 @@ test_description='Test interaction of reset --hard with sequencer
 . ./test-lib.sh
 
 pristine_detach () {
-       git cherry-pick --reset &&
+       git cherry-pick --quit &&
        git checkout -f "$1^0" &&
        git read-tree -u --reset HEAD &&
        git clean -d -f -f -q -x
@@ -41,4 +41,12 @@ test_expect_success 'reset --hard cleans up sequencer state, providing one-level
        test_path_is_missing .git/sequencer-old
 '
 
+test_expect_success 'cherry-pick --abort does not leave sequencer-old dir' '
+       pristine_detach initial &&
+       test_must_fail git cherry-pick base..anotherpick &&
+       git cherry-pick --abort &&
+       test_path_is_missing .git/sequencer &&
+       test_path_is_missing .git/sequencer-old
+'
+
 test_done
index 69115269c2a284dcfc99abd2f7feb3d87f4e5bcc..695f7afdf31cc589e9c31d764d1e713ad64240c0 100755 (executable)
@@ -362,10 +362,10 @@ test_expect_success 'update --init' '
        git submodule update init > update.out &&
        cat update.out &&
        test_i18ngrep "not initialized" update.out &&
-       ! test -d init/.git &&
+       test_must_fail git rev-parse --resolve-git-dir init/.git &&
 
        git submodule update --init init &&
-       test -d init/.git
+       git rev-parse --resolve-git-dir init/.git
 '
 
 test_expect_success 'do not add files from a submodule' '
index 95ffe349a769a84d2eec850d376f11bdfcf2b56f..3620215c1f84b8e184b470c92b9a01c15fd75dc2 100755 (executable)
@@ -56,8 +56,9 @@ test_expect_success '"git submodule sync" should update submodule URLs' '
         git pull --no-recurse-submodules &&
         git submodule sync
        ) &&
-       test -d "$(git config -f super-clone/submodule/.git/config \
-                               remote.origin.url)" &&
+       test -d "$(cd super-clone/submodule &&
+        git config remote.origin.url
+       )" &&
        (cd super-clone/submodule &&
         git checkout master &&
         git pull
index a8fb30b7921dd17f910d48e82fbb2374fcb45ac3..0d5b42a25bbe79d3fb7b09ce4bf5107ee55928e5 100755 (executable)
@@ -228,4 +228,55 @@ test_expect_success 'merging with a modify/modify conflict between merge bases'
        git merge d
 '
 
+# canonical criss-cross history in top and submodule
+test_expect_success 'setup for recursive merge with submodule' '
+       mkdir merge-recursive &&
+       (cd merge-recursive &&
+        git init &&
+        mkdir sub &&
+        (cd sub &&
+         git init &&
+         test_commit a &&
+         git checkout -b sub-b master &&
+         test_commit b &&
+         git checkout -b sub-c master &&
+         test_commit c &&
+         git checkout -b sub-bc sub-b &&
+         git merge sub-c &&
+         git checkout -b sub-cb sub-c &&
+         git merge sub-b &&
+         git checkout master) &&
+        git add sub &&
+        git commit -m a &&
+        git checkout -b top-b master &&
+        (cd sub && git checkout sub-b) &&
+        git add sub &&
+        git commit -m b &&
+        git checkout -b top-c master &&
+        (cd sub && git checkout sub-c) &&
+        git add sub &&
+        git commit -m c &&
+        git checkout -b top-bc top-b &&
+        git merge -s ours --no-commit top-c &&
+        (cd sub && git checkout sub-bc) &&
+        git add sub &&
+        git commit -m bc &&
+        git checkout -b top-cb top-c &&
+        git merge -s ours --no-commit top-b &&
+        (cd sub && git checkout sub-cb) &&
+        git add sub &&
+        git commit -m cb)
+'
+
+# merge should leave submodule unmerged in index
+test_expect_success 'recursive merge with submodule' '
+       (cd merge-recursive &&
+        test_must_fail git merge top-bc &&
+        echo "160000 $(git rev-parse top-cb:sub) 2     sub" > expect2 &&
+        echo "160000 $(git rev-parse top-bc:sub) 3     sub" > expect3 &&
+        git ls-files -u > actual &&
+        grep "$(cat expect2)" actual > /dev/null &&
+        grep "$(cat expect3)" actual > /dev/null)
+'
+
 test_done
index c679f36a4daeab3f114c9cf0c84c93549d28c680..33b292b8a8e992c98774ad6e51270a9babc7e6de 100755 (executable)
@@ -30,6 +30,7 @@ test_expect_success 'setup a submodule tree' '
        git clone super submodule &&
        git clone super rebasing &&
        git clone super merging &&
+       git clone super none &&
        (cd super &&
         git submodule add ../submodule submodule &&
         test_tick &&
@@ -58,6 +59,11 @@ test_expect_success 'setup a submodule tree' '
         test_tick &&
         git commit -m "rebasing"
        )
+       (cd super &&
+        git submodule add ../none none &&
+        test_tick &&
+        git commit -m "none"
+       )
 '
 
 test_expect_success 'submodule update detaching the HEAD ' '
@@ -298,6 +304,62 @@ test_expect_success 'submodule update ignores update=rebase config for new submo
        )
 '
 
+test_expect_success 'submodule init picks up update=none' '
+       (cd super &&
+        git config -f .gitmodules submodule.none.update none &&
+        git submodule init none &&
+        test "none" = "$(git config submodule.none.update)"
+       )
+'
+
+test_expect_success 'submodule update - update=none in .git/config' '
+       (cd super &&
+        git config submodule.submodule.update none &&
+        (cd submodule &&
+         git checkout master &&
+         compare_head
+        ) &&
+        git diff --raw | grep "        submodule" &&
+        git submodule update &&
+        git diff --raw | grep "        submodule" &&
+        (cd submodule &&
+         compare_head
+        ) &&
+        git config --unset submodule.submodule.update &&
+        git submodule update submodule
+       )
+'
+
+test_expect_success 'submodule update - update=none in .git/config but --checkout given' '
+       (cd super &&
+        git config submodule.submodule.update none &&
+        (cd submodule &&
+         git checkout master &&
+         compare_head
+        ) &&
+        git diff --raw | grep "        submodule" &&
+        git submodule update --checkout &&
+        test_must_fail git diff --raw \| grep "        submodule" &&
+        (cd submodule &&
+         test_must_fail compare_head
+        ) &&
+        git config --unset submodule.submodule.update
+       )
+'
+
+test_expect_success 'submodule update --init skips submodule with update=none' '
+       (cd super &&
+        git add .gitmodules &&
+        git commit -m ".gitmodules"
+       ) &&
+       git clone super cloned &&
+       (cd cloned &&
+        git submodule update --init &&
+        test -e submodule/.git &&
+        test_must_fail test -e none/.git
+       )
+'
+
 test_expect_success 'submodule update continues after checkout error' '
        (cd super &&
         git reset --hard HEAD &&
@@ -408,6 +470,7 @@ test_expect_success 'submodule update exit immediately in case of merge conflict
         test_cmp expect actual
        )
 '
+
 test_expect_success 'submodule update exit immediately after recursive rebase error' '
        (cd super &&
         git checkout master &&
@@ -442,4 +505,110 @@ test_expect_success 'submodule update exit immediately after recursive rebase er
         test_cmp expect actual
        )
 '
+
+test_expect_success 'add different submodules to the same path' '
+       (cd super &&
+        git submodule add ../submodule s1 &&
+        test_must_fail git submodule add ../merging s1
+       )
+'
+
+test_expect_success 'submodule add places git-dir in superprojects git-dir' '
+       (cd super &&
+        mkdir deeper &&
+        git submodule add ../submodule deeper/submodule &&
+        (cd deeper/submodule &&
+         git log > ../../expected
+        ) &&
+        (cd .git/modules/deeper/submodule &&
+         git log > ../../../../actual
+        ) &&
+        test_cmp actual expected
+       )
+'
+
+test_expect_success 'submodule update places git-dir in superprojects git-dir' '
+       (cd super &&
+        git commit -m "added submodule"
+       ) &&
+       git clone super super2 &&
+       (cd super2 &&
+        git submodule init deeper/submodule &&
+        git submodule update &&
+        (cd deeper/submodule &&
+         git log > ../../expected
+        ) &&
+        (cd .git/modules/deeper/submodule &&
+         git log > ../../../../actual
+        ) &&
+        test_cmp actual expected
+       )
+'
+
+test_expect_success 'submodule add places git-dir in superprojects git-dir recursive' '
+       (cd super2 &&
+        (cd deeper/submodule &&
+         git submodule add ../submodule subsubmodule &&
+         (cd subsubmodule &&
+          git log > ../../../expected
+         ) &&
+         git commit -m "added subsubmodule" &&
+         git push
+        ) &&
+        (cd .git/modules/deeper/submodule/modules/subsubmodule &&
+         git log > ../../../../../actual
+        ) &&
+        git add deeper/submodule &&
+        git commit -m "update submodule" &&
+        git push &&
+        test_cmp actual expected
+       )
+'
+
+test_expect_success 'submodule update places git-dir in superprojects git-dir recursive' '
+       mkdir super_update_r &&
+       (cd super_update_r &&
+        git init --bare
+       ) &&
+       mkdir subsuper_update_r &&
+       (cd subsuper_update_r &&
+        git init --bare
+       ) &&
+       mkdir subsubsuper_update_r &&
+       (cd subsubsuper_update_r &&
+        git init --bare
+       ) &&
+       git clone subsubsuper_update_r subsubsuper_update_r2 &&
+       (cd subsubsuper_update_r2 &&
+        test_commit "update_subsubsuper" file &&
+        git push origin master
+       ) &&
+       git clone subsuper_update_r subsuper_update_r2 &&
+       (cd subsuper_update_r2 &&
+        test_commit "update_subsuper" file &&
+        git submodule add ../subsubsuper_update_r subsubmodule &&
+        git commit -am "subsubmodule" &&
+        git push origin master
+       ) &&
+       git clone super_update_r super_update_r2 &&
+       (cd super_update_r2 &&
+        test_commit "update_super" file &&
+        git submodule add ../subsuper_update_r submodule &&
+        git commit -am "submodule" &&
+        git push origin master
+       ) &&
+       rm -rf super_update_r2 &&
+       git clone super_update_r super_update_r2 &&
+       (cd super_update_r2 &&
+        git submodule update --init --recursive &&
+        (cd submodule/subsubmodule &&
+         git log > ../../expected
+        ) &&
+        (cd .git/modules/submodule/modules/subsubmodule
+         git log > ../../../../../actual
+        )
+        test_cmp actual expected
+       )
+'
+
 test_done
index be745fb23f3f27ed319143d3d3ab3f26d01857bc..9b69fe2e1470e406b005e629a6f7759f8e564ddf 100755 (executable)
@@ -118,19 +118,19 @@ test_expect_success 'use "submodule foreach" to checkout 2nd level submodule' '
        git clone super clone2 &&
        (
                cd clone2 &&
-               test ! -d sub1/.git &&
-               test ! -d sub2/.git &&
-               test ! -d sub3/.git &&
-               test ! -d nested1/.git &&
+               test_must_fail git rev-parse --resolve-git-dir sub1/.git &&
+               test_must_fail git rev-parse --resolve-git-dir sub2/.git &&
+               test_must_fail git rev-parse --resolve-git-dir sub3/.git &&
+               test_must_fail git rev-parse --resolve-git-dir nested1/.git &&
                git submodule update --init &&
-               test -d sub1/.git &&
-               test -d sub2/.git &&
-               test -d sub3/.git &&
-               test -d nested1/.git &&
-               test ! -d nested1/nested2/.git &&
+               git rev-parse --resolve-git-dir sub1/.git &&
+               git rev-parse --resolve-git-dir sub2/.git &&
+               git rev-parse --resolve-git-dir sub3/.git &&
+               git rev-parse --resolve-git-dir nested1/.git &&
+               test_must_fail git rev-parse --resolve-git-dir nested1/nested2/.git &&
                git submodule foreach "git submodule update --init" &&
-               test -d nested1/nested2/.git &&
-               test ! -d nested1/nested2/nested3/.git
+               git rev-parse --resolve-git-dir nested1/nested1/nested2/.git
+               test_must_fail git rev-parse --resolve-git-dir nested1/nested2/nested3/.git
        )
 '
 
@@ -138,8 +138,8 @@ test_expect_success 'use "foreach --recursive" to checkout all submodules' '
        (
                cd clone2 &&
                git submodule foreach --recursive "git submodule update --init" &&
-               test -d nested1/nested2/nested3/.git &&
-               test -d nested1/nested2/nested3/submodule/.git
+               git rev-parse --resolve-git-dir nested1/nested2/nested3/.git &&
+               git rev-parse --resolve-git-dir nested1/nested2/nested3/submodule/.git
        )
 '
 
@@ -183,18 +183,18 @@ test_expect_success 'use "update --recursive" to checkout all submodules' '
        git clone super clone3 &&
        (
                cd clone3 &&
-               test ! -d sub1/.git &&
-               test ! -d sub2/.git &&
-               test ! -d sub3/.git &&
-               test ! -d nested1/.git &&
+               test_must_fail git rev-parse --resolve-git-dir sub1/.git &&
+               test_must_fail git rev-parse --resolve-git-dir sub2/.git &&
+               test_must_fail git rev-parse --resolve-git-dir sub3/.git &&
+               test_must_fail git rev-parse --resolve-git-dir nested1/.git &&
                git submodule update --init --recursive &&
-               test -d sub1/.git &&
-               test -d sub2/.git &&
-               test -d sub3/.git &&
-               test -d nested1/.git &&
-               test -d nested1/nested2/.git &&
-               test -d nested1/nested2/nested3/.git &&
-               test -d nested1/nested2/nested3/submodule/.git
+               git rev-parse --resolve-git-dir sub1/.git &&
+               git rev-parse --resolve-git-dir sub2/.git &&
+               git rev-parse --resolve-git-dir sub3/.git &&
+               git rev-parse --resolve-git-dir nested1/.git &&
+               git rev-parse --resolve-git-dir nested1/nested2/.git &&
+               git rev-parse --resolve-git-dir nested1/nested2/nested3/.git &&
+               git rev-parse --resolve-git-dir nested1/nested2/nested3/submodule/.git
        )
 '
 
@@ -247,14 +247,17 @@ test_expect_success 'ensure "status --cached --recursive" preserves the --cached
 
 test_expect_success 'use "git clone --recursive" to checkout all submodules' '
        git clone --recursive super clone4 &&
-       test -d clone4/.git &&
-       test -d clone4/sub1/.git &&
-       test -d clone4/sub2/.git &&
-       test -d clone4/sub3/.git &&
-       test -d clone4/nested1/.git &&
-       test -d clone4/nested1/nested2/.git &&
-       test -d clone4/nested1/nested2/nested3/.git &&
-       test -d clone4/nested1/nested2/nested3/submodule/.git
+       (
+               cd clone4 &&
+               git rev-parse --resolve-git-dir .git &&
+               git rev-parse --resolve-git-dir sub1/.git &&
+               git rev-parse --resolve-git-dir sub2/.git &&
+               git rev-parse --resolve-git-dir sub3/.git &&
+               git rev-parse --resolve-git-dir nested1/.git &&
+               git rev-parse --resolve-git-dir nested1/nested2/.git &&
+               git rev-parse --resolve-git-dir nested1/nested2/nested3/.git &&
+               git rev-parse --resolve-git-dir nested1/nested2/nested3/submodule/.git
+       )
 '
 
 test_expect_success 'test "update --recursive" with a flag with spaces' '
@@ -262,14 +265,14 @@ test_expect_success 'test "update --recursive" with a flag with spaces' '
        git clone super clone5 &&
        (
                cd clone5 &&
-               test ! -d nested1/.git &&
+               test_must_fail git rev-parse --resolve-git-dir d nested1/.git &&
                git submodule update --init --recursive --reference="$(dirname "$PWD")/common objects" &&
-               test -d nested1/.git &&
-               test -d nested1/nested2/.git &&
-               test -d nested1/nested2/nested3/.git &&
-               test -f nested1/.git/objects/info/alternates &&
-               test -f nested1/nested2/.git/objects/info/alternates &&
-               test -f nested1/nested2/nested3/.git/objects/info/alternates
+               git rev-parse --resolve-git-dir nested1/.git &&
+               git rev-parse --resolve-git-dir nested1/nested2/.git &&
+               git rev-parse --resolve-git-dir nested1/nested2/nested3/.git &&
+               test -f .git/modules/nested1/objects/info/alternates &&
+               test -f .git/modules/nested1/modules/nested2/objects/info/alternates &&
+               test -f .git/modules/nested1/modules/nested2/modules/nested3/objects/info/alternates
        )
 '
 
@@ -277,18 +280,18 @@ test_expect_success 'use "update --recursive nested1" to checkout all submodules
        git clone super clone6 &&
        (
                cd clone6 &&
-               test ! -d sub1/.git &&
-               test ! -d sub2/.git &&
-               test ! -d sub3/.git &&
-               test ! -d nested1/.git &&
+               test_must_fail git rev-parse --resolve-git-dir sub1/.git &&
+               test_must_fail git rev-parse --resolve-git-dir sub2/.git &&
+               test_must_fail git rev-parse --resolve-git-dir sub3/.git &&
+               test_must_fail git rev-parse --resolve-git-dir nested1/.git &&
                git submodule update --init --recursive -- nested1 &&
-               test ! -d sub1/.git &&
-               test ! -d sub2/.git &&
-               test ! -d sub3/.git &&
-               test -d nested1/.git &&
-               test -d nested1/nested2/.git &&
-               test -d nested1/nested2/nested3/.git &&
-               test -d nested1/nested2/nested3/submodule/.git
+               test_must_fail git rev-parse --resolve-git-dir sub1/.git &&
+               test_must_fail git rev-parse --resolve-git-dir sub2/.git &&
+               test_must_fail git rev-parse --resolve-git-dir sub3/.git &&
+               git rev-parse --resolve-git-dir nested1/.git &&
+               git rev-parse --resolve-git-dir nested1/nested2/.git &&
+               git rev-parse --resolve-git-dir nested1/nested2/nested3/.git &&
+               git rev-parse --resolve-git-dir nested1/nested2/nested3/submodule/.git
        )
 '
 
index cc16d3f05de7ec51e8a071253654a6698f8f33c4..ab37c368d071236d0a2851417e85d2a216c7f4fc 100755 (executable)
@@ -43,7 +43,7 @@ git commit -m B-super-added'
 cd "$base_dir"
 
 test_expect_success 'after add: existence of info/alternates' \
-'test `wc -l <super/sub/.git/objects/info/alternates` = 1'
+'test `wc -l <super/.git/modules/sub/objects/info/alternates` = 1'
 
 cd "$base_dir"
 
@@ -66,7 +66,7 @@ test_expect_success 'update with reference' \
 cd "$base_dir"
 
 test_expect_success 'after update: existence of info/alternates' \
-'test `wc -l <super-clone/sub/.git/objects/info/alternates` = 1'
+'test `wc -l <super-clone/.git/modules/sub/objects/info/alternates` = 1'
 
 cd "$base_dir"
 
index 905255adf0ca5b15d9befa772cda4a650bd15f34..fc57b135c50e34ab86c04d0a0ec81052ce40f8ff 100755 (executable)
@@ -189,7 +189,7 @@ test_expect_success 'status with gitignore' '
        #       untracked
        EOF
        git status --ignored >output &&
-       test_cmp expect output
+       test_i18ncmp expect output
 '
 
 test_expect_success 'status with gitignore (nothing untracked)' '
@@ -247,7 +247,7 @@ test_expect_success 'status with gitignore (nothing untracked)' '
        #       untracked
        EOF
        git status --ignored >output &&
-       test_cmp expect output
+       test_i18ncmp expect output
 '
 
 rm -f .gitignore
diff --git a/t/t7511-status-index.sh b/t/t7511-status-index.sh
new file mode 100755 (executable)
index 0000000..b5fdc04
--- /dev/null
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+test_description='git status with certain file name lengths'
+
+. ./test-lib.sh
+
+files="0 1 2 3 4 5 6 7 8 9 a b c d e f g h i j k l m n o p q r s t u v w x y z"
+
+check() {
+       len=$1
+       prefix=$2
+
+       for i in $files
+       do
+               : >$prefix$i
+       done
+
+       test_expect_success "status, filename length $len" "
+               git add $prefix* &&
+               git status
+       "
+       rm $prefix* .git/index
+}
+
+check  1
+check  2 p
+check  3 px
+check  4 pre
+check  5 pref
+check  6 prefi
+check  7 prefix
+check  8 prefix-
+check  9 prefix-p
+check 10 prefix-pr
+check 11 prefix-pre
+check 12 prefix-pref
+check 13 prefix-prefi
+check 14 prefix-prefix
+check 15 prefix-prefix-
+check 16 prefix-prefix-p
+check 17 prefix-prefix-pr
+check 18 prefix-prefix-pre
+check 19 prefix-prefix-pref
+check 20 prefix-prefix-prefi
+check 21 prefix-prefix-prefix
+check 22 prefix-prefix-prefix-
+check 23 prefix-prefix-prefix-p
+check 24 prefix-prefix-prefix-pr
+
+test_done
index 87aac835a1b864eab083b25940892b93af491326..3008e4e1210900be3bdc2e28dddd681ce8ec3551 100755 (executable)
@@ -643,4 +643,27 @@ test_expect_success 'amending no-ff merge commit' '
 
 test_debug 'git log --graph --decorate --oneline --all'
 
+cat >editor <<\EOF
+#!/bin/sh
+# Add a new message string that was not in the template
+(
+       echo "Merge work done on the side branch c1"
+       echo
+       cat <"$1"
+) >"$1.tmp" && mv "$1.tmp" "$1"
+# strip comments and blank lines from end of message
+sed -e '/^#/d' < "$1" | sed -e :a -e '/^\n*$/{$d;N;ba' -e '}' > expected
+EOF
+chmod 755 editor
+
+test_expect_success 'merge --no-ff --edit' '
+       git reset --hard c0 &&
+       EDITOR=./editor git merge --no-ff --edit c1 &&
+       verify_parents $c0 $c1 &&
+       git cat-file commit HEAD >raw &&
+       grep "work done on the side branch" raw &&
+       sed "1,/^$/d" >actual raw &&
+       test_cmp actual expected
+'
+
 test_done
index 72a8731d5e28d71577d0c53fc25a5ddc1d2ad6de..aa74184c31cd6b2bd2e0e566e6805e60eed7aff8 100755 (executable)
@@ -107,6 +107,7 @@ error: The following untracked working tree files would be overwritten by merge:
        sub
        sub2
 Please move or remove them before you can merge.
+Aborting
 EOF
 
 test_expect_success 'will not overwrite untracked file in leading path' '
index c994836c53224dd0a8302f7a6ab63b35f8b493e3..0e4a682c6428a96af4d98bb7e24b0de7f4be865c 100755 (executable)
@@ -32,6 +32,7 @@ error: The following untracked working tree files would be overwritten by merge:
        three
        two
 Please move or remove them before you can merge.
+Aborting
 EOF
 
 test_expect_success 'untracked files overwritten by merge (fast and non-fast forward)' '
@@ -56,6 +57,7 @@ Please, commit your changes or stash them before you can merge.
 error: The following untracked working tree files would be overwritten by merge:
        five
 Please move or remove them before you can merge.
+Aborting
 EOF
 
 test_expect_success 'untracked files or local changes ovewritten by merge' '
@@ -71,6 +73,7 @@ error: Your local changes to the following files would be overwritten by checkou
        rep/one
        rep/two
 Please, commit your changes or stash them before you can switch branches.
+Aborting
 EOF
 
 test_expect_success 'cannot switch branches because of local changes' '
@@ -92,6 +95,7 @@ error: Your local changes to the following files would be overwritten by checkou
        rep/one
        rep/two
 Please, commit your changes or stash them before you can switch branches.
+Aborting
 EOF
 
 test_expect_success 'not uptodate file porcelain checkout error' '
@@ -105,6 +109,7 @@ error: Updating the following directories would lose untracked files in it:
        rep
        rep2
 
+Aborting
 EOF
 
 test_expect_success 'not_uptodate_dir porcelain checkout error' '
index cbc08e32761c6e35a3eb25519873e74218fd441a..4aab2a75b8ef5836a58f30b6c27a8eec4a733afc 100755 (executable)
@@ -16,6 +16,7 @@ Testing basic merge tool invocation'
 test_expect_success 'setup' '
     git config rerere.enabled true &&
     echo master >file1 &&
+    echo master spaced >"spaced name" &&
     echo master file11 >file11 &&
     echo master file12 >file12 &&
     echo master file13 >file13 &&
@@ -30,13 +31,14 @@ test_expect_success 'setup' '
        git commit -m "Add foo"
     ) &&
     git submodule add git://example.com/submod submod &&
-    git add file1 file1[1-4] subdir/file3 .gitmodules submod &&
+    git add file1 "spaced name" file1[1-4] subdir/file3 .gitmodules submod &&
     git commit -m "add initial versions" &&
 
     git checkout -b branch1 master &&
     git submodule update -N &&
     echo branch1 change >file1 &&
     echo branch1 newfile >file2 &&
+    echo branch1 spaced >"spaced name" &&
     echo branch1 change file11 >file11 &&
     echo branch1 change file13 >file13 &&
     echo branch1 sub >subdir/file3 &&
@@ -47,7 +49,7 @@ test_expect_success 'setup' '
        git commit -m "Add bar on branch1" &&
        git checkout -b submod-branch1
     ) &&
-    git add file1 file11 file13 file2 subdir/file3 submod &&
+    git add file1 "spaced name" file11 file13 file2 subdir/file3 submod &&
     git rm file12 &&
     git commit -m "branch1 changes" &&
 
@@ -55,6 +57,7 @@ test_expect_success 'setup' '
     git submodule update -N &&
     echo master updated >file1 &&
     echo master new >file2 &&
+    echo master updated spaced >"spaced name" &&
     echo master updated file12 >file12 &&
     echo master updated file14 >file14 &&
     echo master new sub >subdir/file3 &&
@@ -65,7 +68,7 @@ test_expect_success 'setup' '
        git commit -m "Add bar on master" &&
        git checkout -b submod-master
     ) &&
-    git add file1 file12 file14 file2 subdir/file3 submod &&
+    git add file1 "spaced name" file12 file14 file2 subdir/file3 submod &&
     git rm file11 &&
     git commit -m "master updates" &&
 
@@ -78,8 +81,8 @@ test_expect_success 'custom mergetool' '
     git checkout -b test1 branch1 &&
     git submodule update -N &&
     test_must_fail git merge master >/dev/null 2>&1 &&
-    ( yes "" | git mergetool file1 >/dev/null 2>&1 ) &&
-    ( yes "" | git mergetool file2 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool file1 file1 ) &&
+    ( yes "" | git mergetool file2 "spaced name" >/dev/null 2>&1 ) &&
     ( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file11 >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file12 >/dev/null 2>&1 ) &&
@@ -97,6 +100,7 @@ test_expect_success 'mergetool crlf' '
     test_must_fail git merge master >/dev/null 2>&1 &&
     ( yes "" | git mergetool file1 >/dev/null 2>&1 ) &&
     ( yes "" | git mergetool file2 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool "spaced name" >/dev/null 2>&1 ) &&
     ( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file11 >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file12 >/dev/null 2>&1 ) &&
@@ -126,7 +130,7 @@ test_expect_success 'mergetool on file in parent dir' '
     (
        cd subdir &&
        ( yes "" | git mergetool ../file1 >/dev/null 2>&1 ) &&
-       ( yes "" | git mergetool ../file2 >/dev/null 2>&1 ) &&
+       ( yes "" | git mergetool ../file2 ../spaced\ name >/dev/null 2>&1 ) &&
        ( yes "d" | git mergetool ../file11 >/dev/null 2>&1 ) &&
        ( yes "d" | git mergetool ../file12 >/dev/null 2>&1 ) &&
        ( yes "l" | git mergetool ../submod >/dev/null 2>&1 ) &&
@@ -180,6 +184,24 @@ test_expect_success 'mergetool skips resolved paths when rerere is active' '
     git reset --hard
 '
 
+test_expect_success 'mergetool takes partial path' '
+    git config rerere.enabled false &&
+    git checkout -b test12 branch1 &&
+    git submodule update -N &&
+    test_must_fail git merge master &&
+
+    #shouldnt need these lines
+    #( yes "d" | git mergetool file11 >/dev/null 2>&1 ) &&
+    #( yes "d" | git mergetool file12 >/dev/null 2>&1 ) &&
+    #( yes "l" | git mergetool submod >/dev/null 2>&1 ) &&
+    #( yes "" | git mergetool file1 file2 >/dev/null 2>&1 ) &&
+
+    ( yes "" | git mergetool subdir ) &&
+
+    test "$(cat subdir/file3)" = "master new sub" &&
+    git reset --hard
+'
+
 test_expect_success 'deleted vs modified submodule' '
     git checkout -b test6 branch1 &&
     git submodule update -N &&
@@ -189,7 +211,7 @@ test_expect_success 'deleted vs modified submodule' '
     git checkout -b test6.a test6 &&
     test_must_fail git merge master &&
     test -n "$(git ls-files -u)" &&
-    ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
     ( yes "r" | git mergetool submod ) &&
     rmdir submod && mv submod-movedaside submod &&
@@ -205,7 +227,7 @@ test_expect_success 'deleted vs modified submodule' '
     git submodule update -N &&
     test_must_fail git merge master &&
     test -n "$(git ls-files -u)" &&
-    ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
     ( yes "l" | git mergetool submod ) &&
     test ! -e submod &&
@@ -218,7 +240,7 @@ test_expect_success 'deleted vs modified submodule' '
     git submodule update -N &&
     test_must_fail git merge test6 &&
     test -n "$(git ls-files -u)" &&
-    ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
     ( yes "r" | git mergetool submod ) &&
     test ! -e submod &&
@@ -233,7 +255,7 @@ test_expect_success 'deleted vs modified submodule' '
     git submodule update -N &&
     test_must_fail git merge test6 &&
     test -n "$(git ls-files -u)" &&
-    ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
     ( yes "l" | git mergetool submod ) &&
     test "$(cat submod/bar)" = "master submodule" &&
@@ -256,7 +278,7 @@ test_expect_success 'file vs modified submodule' '
     git checkout -b test7.a branch1 &&
     test_must_fail git merge master &&
     test -n "$(git ls-files -u)" &&
-    ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
     ( yes "r" | git mergetool submod ) &&
     rmdir submod && mv submod-movedaside submod &&
@@ -271,7 +293,7 @@ test_expect_success 'file vs modified submodule' '
     git checkout -b test7.b test7 &&
     test_must_fail git merge master &&
     test -n "$(git ls-files -u)" &&
-    ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
     ( yes "l" | git mergetool submod ) &&
     git submodule update -N &&
@@ -286,7 +308,7 @@ test_expect_success 'file vs modified submodule' '
     git submodule update -N &&
     test_must_fail git merge test7 &&
     test -n "$(git ls-files -u)" &&
-    ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
     ( yes "r" | git mergetool submod ) &&
     test -d submod.orig &&
@@ -301,7 +323,7 @@ test_expect_success 'file vs modified submodule' '
     git submodule update -N &&
     test_must_fail git merge test7 &&
     test -n "$(git ls-files -u)" &&
-    ( yes "" | git mergetool file1 file2 subdir/file3 >/dev/null 2>&1 ) &&
+    ( yes "" | git mergetool file1 file2 spaced\ name subdir/file3 >/dev/null 2>&1 ) &&
     ( yes "d" | git mergetool file11 file12 >/dev/null 2>&1 ) &&
     ( yes "l" | git mergetool submod ) &&
     test "$(cat submod/bar)" = "master submodule" &&
@@ -388,7 +410,7 @@ test_expect_success 'directory vs modified submodule' '
     test "$(cat submod/file16)" = "not a submodule" &&
     rm -rf submod.orig &&
 
-    git reset --hard &&
+    git reset --hard >/dev/null 2>&1 &&
     test_must_fail git merge master &&
     test -n "$(git ls-files -u)" &&
     test ! -e submod.orig &&
@@ -400,7 +422,7 @@ test_expect_success 'directory vs modified submodule' '
     ( cd submod && git clean -f && git reset --hard ) &&
     git submodule update -N &&
     test "$(cat submod/bar)" = "master submodule" &&
-    git reset --hard && rm -rf submod-movedaside &&
+    git reset --hard >/dev/null 2>&1 && rm -rf submod-movedaside &&
 
     git checkout -b test11.c master &&
     git submodule update -N &&
@@ -410,7 +432,7 @@ test_expect_success 'directory vs modified submodule' '
     git submodule update -N &&
     test "$(cat submod/bar)" = "master submodule" &&
 
-    git reset --hard &&
+    git reset --hard >/dev/null 2>&1 &&
     git submodule update -N &&
     test_must_fail git merge test11 &&
     test -n "$(git ls-files -u)" &&
@@ -418,7 +440,7 @@ test_expect_success 'directory vs modified submodule' '
     ( yes "r" | git mergetool submod ) &&
     test "$(cat submod/file16)" = "not a submodule" &&
 
-    git reset --hard master &&
+    git reset --hard master >/dev/null 2>&1 &&
     ( cd submod && git clean -f && git reset --hard ) &&
     git submodule update -N
 '
index 395adfc8a946bfd67b8a61cbef1366b78d9d855e..4fb4c9384a0045d3b041d627e9d814637d9268e2 100755 (executable)
@@ -38,7 +38,17 @@ restore_test_defaults()
 prompt_given()
 {
        prompt="$1"
-       test "$prompt" = "Hit return to launch 'test-tool': branch"
+       test "$prompt" = "Launch 'test-tool' [Y/n]: branch"
+}
+
+stdin_contains()
+{
+       grep >/dev/null "$1"
+}
+
+stdin_doesnot_contain()
+{
+       ! stdin_contains "$1"
 }
 
 # Create a file on master and change it on branch
@@ -265,4 +275,35 @@ test_expect_success PERL 'difftool --extcmd cat arg2' '
        test "$diff" = branch
 '
 
+# Create a second file on master and a different version on branch
+test_expect_success PERL 'setup with 2 files different' '
+       echo m2 >file2 &&
+       git add file2 &&
+       git commit -m "added file2" &&
+
+       git checkout branch &&
+       echo br2 >file2 &&
+       git add file2 &&
+       git commit -a -m "branch changed file2" &&
+       git checkout master
+'
+
+test_expect_success PERL 'say no to the first file' '
+       diff=$( (echo n; echo) | git difftool -x cat branch ) &&
+
+       echo "$diff" | stdin_contains m2 &&
+       echo "$diff" | stdin_contains br2 &&
+       echo "$diff" | stdin_doesnot_contain master &&
+       echo "$diff" | stdin_doesnot_contain branch
+'
+
+test_expect_success PERL 'say no to the second file' '
+       diff=$( (echo; echo n) | git difftool -x cat branch ) &&
+
+       echo "$diff" | stdin_contains master &&
+       echo "$diff" | stdin_contains branch &&
+       echo "$diff" | stdin_doesnot_contain m2 &&
+       echo "$diff" | stdin_doesnot_contain br2
+'
+
 test_done
index 0d600163c8284a318fbd21f3a00dd7853b2f8956..81263b78516cbe2949ad7e426a5e44af188cccdd 100755 (executable)
@@ -554,7 +554,6 @@ test_expect_success 'outside of git repository' '
        mkdir -p non/git/sub &&
        echo hello >non/git/file1 &&
        echo world >non/git/sub/file2 &&
-       echo ".*o*" >non/git/.gitignore &&
        {
                echo file1:hello &&
                echo sub/file2:world
@@ -571,6 +570,23 @@ test_expect_success 'outside of git repository' '
                test_must_fail git grep o &&
                git grep --no-index o >../../actual.sub &&
                test_cmp ../../expect.sub ../../actual.sub
+       ) &&
+
+       echo ".*o*" >non/git/.gitignore &&
+       (
+               GIT_CEILING_DIRECTORIES="$(pwd)/non/git" &&
+               export GIT_CEILING_DIRECTORIES &&
+               cd non/git &&
+               test_must_fail git grep o &&
+               git grep --no-index --exclude-standard o >../actual.full &&
+               test_cmp ../expect.full ../actual.full &&
+
+               {
+                       echo ".gitignore:.*o*"
+                       cat ../expect.full
+               } >../expect.with.ignored &&
+               git grep --no-index --no-exclude o >../actual.full &&
+               test_cmp ../expect.with.ignored ../actual.full
        )
 '
 
@@ -583,6 +599,10 @@ test_expect_success 'inside git repository but with --no-index' '
        {
                echo file1:hello &&
                echo sub/file2:world
+       } >is/expect.unignored &&
+       {
+               echo ".gitignore:.*o*" &&
+               cat is/expect.unignored
        } >is/expect.full &&
        : >is/expect.empty &&
        echo file2:world >is/expect.sub &&
@@ -591,12 +611,24 @@ test_expect_success 'inside git repository but with --no-index' '
                git init &&
                test_must_fail git grep o >../actual.full &&
                test_cmp ../expect.empty ../actual.full &&
+
+               git grep --untracked o >../actual.unignored &&
+               test_cmp ../expect.unignored ../actual.unignored &&
+
                git grep --no-index o >../actual.full &&
                test_cmp ../expect.full ../actual.full &&
+
+               git grep --no-index --exclude-standard o >../actual.unignored &&
+               test_cmp ../expect.unignored ../actual.unignored &&
+
                cd sub &&
                test_must_fail git grep o >../../actual.sub &&
                test_cmp ../../expect.empty ../../actual.sub &&
+
                git grep --no-index o >../../actual.sub &&
+               test_cmp ../../expect.sub ../../actual.sub &&
+
+               git grep --untracked o >../../actual.sub &&
                test_cmp ../../expect.sub ../../actual.sub
        )
 '
index 32ec82ad678d56bbf27f525fc8588b3391d9117d..4ee42f12f0af6bc7e4b072350f88988b85e40cbb 100755 (executable)
@@ -15,6 +15,7 @@ EOF
 chmod +x helper
 
 test_expect_success 'setup ' '
+       echo "bin: test number 0" >zero.bin &&
        echo "bin: test 1" >one.bin &&
        echo "bin: test number 2" >two.bin &&
        if test_have_prereq SYMLINKS; then
@@ -43,6 +44,7 @@ test_expect_success 'no filter specified' '
 
 test_expect_success 'setup textconv filters' '
        echo "*.bin diff=test" >.gitattributes &&
+       echo "zero.bin eol=crlf" >>.gitattributes &&
        git config diff.test.textconv ./helper &&
        git config diff.test.cachetextconv false
 '
@@ -74,6 +76,15 @@ test_expect_success 'blame --textconv going through revisions' '
        test_cmp expected result
 '
 
+test_expect_success 'blame --textconv with local changes' '
+       test_when_finished "git checkout zero.bin" &&
+       printf "bin: updated number 0\015" >zero.bin &&
+       git blame --textconv zero.bin >blame &&
+       expect="(Not Committed Yet ....-..-.. ..:..:.. +0000 1)" &&
+       expect="$expect converted: updated number 0" &&
+       expr "$(find_blame <blame)" : "^$expect"
+'
+
 test_expect_success 'setup +cachetextconv' '
        git config diff.test.cachetextconv true
 '
index 579ddb7572c27889060da315ddecb773b6fa1272..8c12c65c72658acb37fa715fc93756381bc824bb 100755 (executable)
@@ -23,6 +23,7 @@ test_expect_success $PREREQ \
       echo do
       echo "  echo \"!\$a!\""
       echo "done >commandline\$output"
+      test_have_prereq MINGW && echo "dos2unix commandline\$output"
       echo "cat > msgtxt\$output"
       ) >fake.sendmail &&
      chmod +x ./fake.sendmail &&
@@ -1168,4 +1169,32 @@ test_expect_success $PREREQ '--force sends cover letter template anyway' '
        test -n "$(ls msgtxt*)"
 '
 
+test_expect_success $PREREQ 'sendemail.aliasfiletype=mailrc' '
+       clean_fake_sendmail &&
+       echo "alias sbd  somebody@example.org" >.mailrc &&
+       git config --replace-all sendemail.aliasesfile "$(pwd)/.mailrc" &&
+       git config sendemail.aliasfiletype mailrc &&
+       git send-email \
+         --from="Example <nobody@example.com>" \
+         --to=sbd \
+         --smtp-server="$(pwd)/fake.sendmail" \
+         outdir/0001-*.patch \
+         2>errors >out &&
+       grep "^!somebody@example\.org!$" commandline1
+'
+
+test_expect_success $PREREQ 'sendemail.aliasfile=~/.mailrc' '
+       clean_fake_sendmail &&
+       echo "alias sbd  someone@example.org" >~/.mailrc &&
+       git config --replace-all sendemail.aliasesfile "~/.mailrc" &&
+       git config sendemail.aliasfiletype mailrc &&
+       git send-email \
+         --from="Example <nobody@example.com>" \
+         --to=sbd \
+         --smtp-server="$(pwd)/fake.sendmail" \
+         outdir/0001-*.patch \
+         2>errors >out &&
+       grep "^!someone@example\.org!$" commandline1
+'
+
 test_done
index 85120b70db8cfb60b8d8c38f85a9038aa856468a..69e4815781c190207d406a4871a5dc5252690239 100755 (executable)
@@ -2,6 +2,14 @@
 test_description='git svn handling of root commits in merge ranges'
 . ./lib-git-svn.sh
 
+svn_ver="$(svn --version --quiet)"
+case $svn_ver in
+0.* | 1.[0-4].*)
+       skip_all="skipping git-svn test - SVN too old ($svn_ver)"
+       test_done
+       ;;
+esac
+
 test_expect_success 'test handling of root commits in merge ranges' '
        mkdir -p init/trunk init/branches init/tags &&
        echo "r1" > init/trunk/file.txt &&
diff --git a/t/t9162-git-svn-dcommit-interactive.sh b/t/t9162-git-svn-dcommit-interactive.sh
new file mode 100755 (executable)
index 0000000..e38d9fa
--- /dev/null
@@ -0,0 +1,64 @@
+#!/bin/sh
+#
+# Copyright (c) 2011 Frédéric Heitzmann
+
+test_description='git svn dcommit --interactive series'
+. ./lib-git-svn.sh
+
+test_expect_success 'initialize repo' '
+       svn_cmd mkdir -m"mkdir test-interactive" "$svnrepo/test-interactive" &&
+       git svn clone "$svnrepo/test-interactive" test-interactive &&
+       cd test-interactive &&
+       touch foo && git add foo && git commit -m"foo: first commit" &&
+       git svn dcommit
+       '
+
+test_expect_success 'answers: y [\n] yes' '
+       (
+               echo "change #1" >> foo && git commit -a -m"change #1" &&
+               echo "change #2" >> foo && git commit -a -m"change #2" &&
+               echo "change #3" >> foo && git commit -a -m"change #3" &&
+               ( echo "y
+
+y" | GIT_SVN_NOTTY=1 git svn dcommit --interactive ) &&
+               test $(git rev-parse HEAD) = $(git rev-parse remotes/git-svn)
+       )
+       '
+
+test_expect_success 'answers: yes yes no' '
+       (
+               echo "change #1" >> foo && git commit -a -m"change #1" &&
+               echo "change #2" >> foo && git commit -a -m"change #2" &&
+               echo "change #3" >> foo && git commit -a -m"change #3" &&
+               ( echo "yes
+yes
+no" | GIT_SVN_NOTTY=1 git svn dcommit --interactive ) &&
+               test $(git rev-parse HEAD^^^) = $(git rev-parse remotes/git-svn) &&
+               git reset --hard remotes/git-svn
+       )
+       '
+
+test_expect_success 'answers: yes quit' '
+       (
+               echo "change #1" >> foo && git commit -a -m"change #1" &&
+               echo "change #2" >> foo && git commit -a -m"change #2" &&
+               echo "change #3" >> foo && git commit -a -m"change #3" &&
+               ( echo "yes
+quit" | GIT_SVN_NOTTY=1 git svn dcommit --interactive ) &&
+               test $(git rev-parse HEAD^^^) = $(git rev-parse remotes/git-svn) &&
+               git reset --hard remotes/git-svn
+       )
+       '
+
+test_expect_success 'answers: all' '
+       (
+               echo "change #1" >> foo && git commit -a -m"change #1" &&
+               echo "change #2" >> foo && git commit -a -m"change #2" &&
+               echo "change #3" >> foo && git commit -a -m"change #3" &&
+               ( echo "all" | GIT_SVN_NOTTY=1 git svn dcommit --interactive ) &&
+               test $(git rev-parse HEAD) = $(git rev-parse remotes/git-svn) &&
+               git reset --hard remotes/git-svn
+       )
+       '
+
+test_done
index 1a6c06631c1c2a8fc29b66c9265541b54e8a9715..438aaf6b14b8ae0cb35739d6006eaf646876107c 100755 (executable)
@@ -820,6 +820,18 @@ test_expect_success \
        'test 1 = `git rev-list J | wc -l` &&
         test 0 = `git ls-tree J | wc -l`'
 
+cat >input <<INPUT_END
+reset refs/heads/J2
+
+tag wrong_tag
+from refs/heads/J2
+data <<EOF
+Tag branch that was reset.
+EOF
+INPUT_END
+test_expect_success \
+       'J: tag must fail on empty branch' \
+       'test_must_fail git fast-import <input'
 ###
 ### series K
 ###
@@ -1975,6 +1987,23 @@ test_expect_success \
        'Q: verify second note for second commit' \
        'git cat-file blob refs/notes/foobar:$commit2 >actual && test_cmp expect actual'
 
+cat >input <<EOF
+reset refs/heads/Q0
+
+commit refs/heads/note-Q0
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+Note for an empty branch.
+COMMIT
+
+N inline refs/heads/Q0
+data <<NOTE
+some note
+NOTE
+EOF
+test_expect_success \
+       'Q: deny note on empty branch' \
+       'test_must_fail git fast-import <input'
 ###
 ### series R (feature and option)
 ###
@@ -2208,7 +2237,7 @@ test_expect_success 'R: cat-blob-fd must be a nonnegative integer' '
        test_must_fail git fast-import --cat-blob-fd=-1 </dev/null
 '
 
-test_expect_success 'R: print old blob' '
+test_expect_success NOT_MINGW 'R: print old blob' '
        blob=$(echo "yes it can" | git hash-object -w --stdin) &&
        cat >expect <<-EOF &&
        ${blob} blob 11
@@ -2220,7 +2249,7 @@ test_expect_success 'R: print old blob' '
        test_cmp expect actual
 '
 
-test_expect_success 'R: in-stream cat-blob-fd not respected' '
+test_expect_success NOT_MINGW 'R: in-stream cat-blob-fd not respected' '
        echo hello >greeting &&
        blob=$(git hash-object -w greeting) &&
        cat >expect <<-EOF &&
@@ -2241,7 +2270,7 @@ test_expect_success 'R: in-stream cat-blob-fd not respected' '
        test_cmp expect actual.1
 '
 
-test_expect_success 'R: print new blob' '
+test_expect_success NOT_MINGW 'R: print new blob' '
        blob=$(echo "yep yep yep" | git hash-object --stdin) &&
        cat >expect <<-EOF &&
        ${blob} blob 12
@@ -2259,7 +2288,7 @@ test_expect_success 'R: print new blob' '
        test_cmp expect actual
 '
 
-test_expect_success 'R: print new blob by sha1' '
+test_expect_success NOT_MINGW 'R: print new blob by sha1' '
        blob=$(echo "a new blob named by sha1" | git hash-object --stdin) &&
        cat >expect <<-EOF &&
        ${blob} blob 25
index 3787186703f51f75103824f562c3849483ceaae1..435d8964763d3f048a71db5453b3a19e0cbb7fb9 100755 (executable)
@@ -43,7 +43,11 @@ test_expect_success \
      git config --add test.booltrue true &&
      git config --add test.boolfalse no &&
      git config --add test.boolother other &&
-     git config --add test.int 2k
+     git config --add test.int 2k &&
+     git config --add test.path "~/foo" &&
+     git config --add test.pathexpanded "$HOME/foo" &&
+     git config --add test.pathmulti foo &&
+     git config --add test.pathmulti bar
      '
 
 # The external test will outputs its own plan
index 13ba96e21a9295805aed40c89ecd5178db6bfae4..3b9b48408a87150fad9b1ac1f8cf5ea65e2b09ba 100755 (executable)
@@ -33,6 +33,10 @@ is($r->config_int("test.int"), 2048, "config_int: integer");
 is($r->config_int("test.nonexistent"), undef, "config_int: nonexistent");
 ok($r->config_bool("test.booltrue"), "config_bool: true");
 ok(!$r->config_bool("test.boolfalse"), "config_bool: false");
+is($r->config_path("test.path"), $r->config("test.pathexpanded"),
+   "config_path: ~/foo expansion");
+is_deeply([$r->config_path("test.pathmulti")], ["foo", "bar"],
+   "config_path: multiple values");
 our $ansi_green = "\x1b[32m";
 is($r->get_color("color.test.slot1", "red"), $ansi_green, "get_color");
 # Cannot test $r->get_colorbool("color.foo")) because we do not
index 01ba041fdefe61a3e712344bfe257a1b9cba3b4b..272de3fea37e15b0f29e4a505fcd85b4d4fd1052 100755 (executable)
@@ -2,76 +2,51 @@
 
 test_description='git-p4 tests'
 
-. ./test-lib.sh
+. ./lib-git-p4.sh
 
-( p4 -h && p4d -h ) >/dev/null 2>&1 || {
-       skip_all='skipping git-p4 tests; no p4 or p4d'
-       test_done
-}
-
-GITP4=$GIT_BUILD_DIR/contrib/fast-import/git-p4
-P4DPORT=10669
-
-export P4PORT=localhost:$P4DPORT
-
-db="$TRASH_DIRECTORY/db"
-cli="$TRASH_DIRECTORY/cli"
-git="$TRASH_DIRECTORY/git"
-
-test_debug 'echo p4d -q -d -r "$db" -p $P4DPORT'
-test_expect_success setup '
-       mkdir -p "$db" &&
-       p4d -q -d -r "$db" -p $P4DPORT &&
-       mkdir -p "$cli" &&
-       mkdir -p "$git" &&
-       export P4PORT=localhost:$P4DPORT
+test_expect_success 'start p4d' '
+       start_p4d
 '
 
 test_expect_success 'add p4 files' '
-       cd "$cli" &&
-       p4 client -i <<-EOF &&
-       Client: client
-       Description: client
-       Root: $cli
-       View: //depot/... //client/...
-       EOF
-       export P4CLIENT=client &&
-       echo file1 >file1 &&
-       p4 add file1 &&
-       p4 submit -d "file1" &&
-       echo file2 >file2 &&
-       p4 add file2 &&
-       p4 submit -d "file2" &&
-       cd "$TRASH_DIRECTORY"
+       (
+               cd "$cli" &&
+               echo file1 >file1 &&
+               p4 add file1 &&
+               p4 submit -d "file1" &&
+               echo file2 >file2 &&
+               p4 add file2 &&
+               p4 submit -d "file2"
+       )
 '
 
-cleanup_git() {
-       cd "$TRASH_DIRECTORY" &&
-       rm -rf "$git" &&
-       mkdir "$git"
-}
-
 test_expect_success 'basic git-p4 clone' '
        "$GITP4" clone --dest="$git" //depot &&
        test_when_finished cleanup_git &&
-       cd "$git" &&
-       git log --oneline >lines &&
-       test_line_count = 1 lines
+       (
+               cd "$git" &&
+               git log --oneline >lines &&
+               test_line_count = 1 lines
+       )
 '
 
 test_expect_success 'git-p4 clone @all' '
        "$GITP4" clone --dest="$git" //depot@all &&
        test_when_finished cleanup_git &&
-       cd "$git" &&
-       git log --oneline >lines &&
-       test_line_count = 2 lines
+       (
+               cd "$git" &&
+               git log --oneline >lines &&
+               test_line_count = 2 lines
+       )
 '
 
 test_expect_success 'git-p4 sync uninitialized repo' '
        test_create_repo "$git" &&
        test_when_finished cleanup_git &&
-       cd "$git" &&
-       test_must_fail "$GITP4" sync
+       (
+               cd "$git" &&
+               test_must_fail "$GITP4" sync
+       )
 '
 
 #
@@ -81,17 +56,19 @@ test_expect_success 'git-p4 sync uninitialized repo' '
 test_expect_success 'git-p4 sync new branch' '
        test_create_repo "$git" &&
        test_when_finished cleanup_git &&
-       cd "$git" &&
-       test_commit head &&
-       "$GITP4" sync --branch=refs/remotes/p4/depot //depot@all &&
-       git log --oneline p4/depot >lines &&
-       test_line_count = 2 lines
+       (
+               cd "$git" &&
+               test_commit head &&
+               "$GITP4" sync --branch=refs/remotes/p4/depot //depot@all &&
+               git log --oneline p4/depot >lines &&
+               test_line_count = 2 lines
+       )
 '
 
 test_expect_success 'exit when p4 fails to produce marshaled output' '
        badp4dir="$TRASH_DIRECTORY/badp4dir" &&
-       mkdir -p "$badp4dir" &&
-       test_when_finished "rm -rf $badp4dir" &&
+       mkdir "$badp4dir" &&
+       test_when_finished "rm \"$badp4dir/p4\" && rmdir \"$badp4dir\"" &&
        cat >"$badp4dir"/p4 <<-EOF &&
        #!$SHELL_PATH
        exit 1
@@ -103,61 +80,61 @@ test_expect_success 'exit when p4 fails to produce marshaled output' '
 '
 
 test_expect_success 'add p4 files with wildcards in the names' '
-       cd "$cli" &&
-       echo file-wild-hash >file-wild#hash &&
-       echo file-wild-star >file-wild\*star &&
-       echo file-wild-at >file-wild@at &&
-       echo file-wild-percent >file-wild%percent &&
-       p4 add -f file-wild* &&
-       p4 submit -d "file wildcards"
+       (
+               cd "$cli" &&
+               echo file-wild-hash >file-wild#hash &&
+               echo file-wild-star >file-wild\*star &&
+               echo file-wild-at >file-wild@at &&
+               echo file-wild-percent >file-wild%percent &&
+               p4 add -f file-wild* &&
+               p4 submit -d "file wildcards"
+       )
 '
 
 test_expect_success 'wildcard files git-p4 clone' '
        "$GITP4" clone --dest="$git" //depot &&
        test_when_finished cleanup_git &&
-       cd "$git" &&
-       test -f file-wild#hash &&
-       test -f file-wild\*star &&
-       test -f file-wild@at &&
-       test -f file-wild%percent
+       (
+               cd "$git" &&
+               test -f file-wild#hash &&
+               test -f file-wild\*star &&
+               test -f file-wild@at &&
+               test -f file-wild%percent
+       )
 '
 
 test_expect_success 'clone bare' '
        "$GITP4" clone --dest="$git" --bare //depot &&
        test_when_finished cleanup_git &&
-       cd "$git" &&
-       test ! -d .git &&
-       bare=`git config --get core.bare` &&
-       test "$bare" = true
+       (
+               cd "$git" &&
+               test ! -d .git &&
+               bare=`git config --get core.bare` &&
+               test "$bare" = true
+       )
 '
 
 p4_add_user() {
-    name=$1
-    fullname=$2
-    p4 user -f -i <<EOF &&
-User: $name
-Email: $name@localhost
-FullName: $fullname
-EOF
-    p4 passwd -P secret $name
+       name=$1 fullname=$2 &&
+       p4 user -f -i <<-EOF &&
+       User: $name
+       Email: $name@localhost
+       FullName: $fullname
+       EOF
+       p4 passwd -P secret $name
 }
 
 p4_grant_admin() {
-    name=$1
-    p4 protect -o |\
-       awk "{print}END{print \"    admin user $name * //depot/...\"}" |\
-       p4 protect -i
+       name=$1 &&
+       {
+               p4 protect -o &&
+               echo "    admin user $name * //depot/..."
+       } | p4 protect -i
 }
 
 p4_check_commit_author() {
-    file=$1
-    user=$2
-    if p4 changes -m 1 //depot/$file | grep $user > /dev/null ; then
-       return 0
-    else
-       echo "file $file not modified by user $user" 1>&2
-       return 1
-    fi
+       file=$1 user=$2 &&
+       p4 changes -m 1 //depot/$file | grep -q $user
 }
 
 make_change_by_user() {
@@ -174,15 +151,17 @@ test_expect_success 'preserve users' '
        p4_grant_admin alice &&
        "$GITP4" clone --dest="$git" //depot &&
        test_when_finished cleanup_git &&
-       cd "$git" &&
-       echo "username: a change by alice" >> file1 &&
-       echo "username: a change by bob" >> file2 &&
-       git commit --author "Alice <alice@localhost>" -m "a change by alice" file1 &&
-       git commit --author "Bob <bob@localhost>" -m "a change by bob" file2 &&
-       git config git-p4.skipSubmitEditCheck true &&
-       P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit --preserve-user &&
-       p4_check_commit_author file1 alice &&
-       p4_check_commit_author file2 bob
+       (
+               cd "$git" &&
+               echo "username: a change by alice" >>file1 &&
+               echo "username: a change by bob" >>file2 &&
+               git commit --author "Alice <alice@localhost>" -m "a change by alice" file1 &&
+               git commit --author "Bob <bob@localhost>" -m "a change by bob" file2 &&
+               git config git-p4.skipSubmitEditCheck true &&
+               P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit --preserve-user &&
+               p4_check_commit_author file1 alice &&
+               p4_check_commit_author file2 bob
+       )
 '
 
 # Test username support, submitting as bob, who lacks admin rights. Should
@@ -190,32 +169,37 @@ test_expect_success 'preserve users' '
 test_expect_success 'refuse to preserve users without perms' '
        "$GITP4" clone --dest="$git" //depot &&
        test_when_finished cleanup_git &&
-       cd "$git" &&
-       git config git-p4.skipSubmitEditCheck true &&
-       echo "username-noperms: a change by alice" >> file1 &&
-       git commit --author "Alice <alice@localhost>" -m "perms: a change by alice" file1 &&
-       ! P4EDITOR=touch P4USER=bob P4PASSWD=secret "$GITP4" commit --preserve-user &&
-       ! git diff --exit-code HEAD..p4/master > /dev/null
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEditCheck true &&
+               echo "username-noperms: a change by alice" >>file1 &&
+               git commit --author "Alice <alice@localhost>" -m "perms: a change by alice" file1 &&
+               P4EDITOR=touch P4USER=bob P4PASSWD=secret test_must_fail "$GITP4" commit --preserve-user &&
+               test_must_fail git diff --exit-code HEAD..p4/master
+       )
 '
 
 # What happens with unknown author? Without allowMissingP4Users it should fail.
 test_expect_success 'preserve user where author is unknown to p4' '
        "$GITP4" clone --dest="$git" //depot &&
        test_when_finished cleanup_git &&
-       cd "$git" &&
-       git config git-p4.skipSubmitEditCheck true &&
-       echo "username-bob: a change by bob" >> file1 &&
-       git commit --author "Bob <bob@localhost>" -m "preserve: a change by bob" file1 &&
-       echo "username-unknown: a change by charlie" >> file1 &&
-       git commit --author "Charlie <charlie@localhost>" -m "preserve: a change by charlie" file1 &&
-       ! P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit --preserve-user &&
-       ! git diff --exit-code HEAD..p4/master > /dev/null &&
-       echo "$0: repeat with allowMissingP4Users enabled" &&
-       git config git-p4.allowMissingP4Users true &&
-       git config git-p4.preserveUser true &&
-       P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit &&
-       git diff --exit-code HEAD..p4/master > /dev/null &&
-       p4_check_commit_author file1 alice
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEditCheck true &&
+               echo "username-bob: a change by bob" >>file1 &&
+               git commit --author "Bob <bob@localhost>" -m "preserve: a change by bob" file1 &&
+               echo "username-unknown: a change by charlie" >>file1 &&
+               git commit --author "Charlie <charlie@localhost>" -m "preserve: a change by charlie" file1 &&
+               P4EDITOR=touch P4USER=alice P4PASSWD=secret test_must_fail "$GITP4" commit --preserve-user &&
+               test_must_fail git diff --exit-code HEAD..p4/master &&
+
+               echo "$0: repeat with allowMissingP4Users enabled" &&
+               git config git-p4.allowMissingP4Users true &&
+               git config git-p4.preserveUser true &&
+               P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit &&
+               git diff --exit-code HEAD..p4/master &&
+               p4_check_commit_author file1 alice
+       )
 '
 
 # If we're *not* using --preserve-user, git-p4 should warn if we're submitting
@@ -225,33 +209,35 @@ test_expect_success 'preserve user where author is unknown to p4' '
 test_expect_success 'not preserving user with mixed authorship' '
        "$GITP4" clone --dest="$git" //depot &&
        test_when_finished cleanup_git &&
-       cd "$git" &&
-       git config git-p4.skipSubmitEditCheck true &&
-       p4_add_user derek Derek &&
-
-       make_change_by_user usernamefile3 Derek derek@localhost &&
-       P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual &&
-       grep "git author derek@localhost does not match" actual &&
-
-       make_change_by_user usernamefile3 Charlie charlie@localhost &&
-       P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual &&
-       grep "git author charlie@localhost does not match" actual &&
-
-       make_change_by_user usernamefile3 alice alice@localhost &&
-       P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual &&
-       ! grep "git author.*does not match" actual &&
-
-       git config git-p4.skipUserNameCheck true &&
-       make_change_by_user usernamefile3 Charlie charlie@localhost &&
-       P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual &&
-       ! grep "git author.*does not match" actual &&
-
-       p4_check_commit_author usernamefile3 alice
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEditCheck true &&
+               p4_add_user derek Derek &&
+
+               make_change_by_user usernamefile3 Derek derek@localhost &&
+               P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit |\
+               grep "git author derek@localhost does not match" &&
+
+               make_change_by_user usernamefile3 Charlie charlie@localhost &&
+               P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit |\
+               grep "git author charlie@localhost does not match" &&
+
+               make_change_by_user usernamefile3 alice alice@localhost &&
+               P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" |\
+               test_must_fail grep "git author.*does not match" &&
+
+               git config git-p4.skipUserNameCheck true &&
+               make_change_by_user usernamefile3 Charlie charlie@localhost &&
+               P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit |\
+               test_must_fail grep "git author.*does not match" &&
+
+               p4_check_commit_author usernamefile3 alice
+       )
 '
 
 marshal_dump() {
        what=$1
-       python -c 'import marshal, sys; d = marshal.load(sys.stdin); print d["'$what'"]'
+       "$PYTHON_PATH" -c 'import marshal, sys; d = marshal.load(sys.stdin); print d["'$what'"]'
 }
 
 # Sleep a bit so that the top-most p4 change did not happen "now".  Then
@@ -263,10 +249,12 @@ test_expect_success 'initial import time from top change time' '
        sleep 3 &&
        "$GITP4" clone --dest="$git" //depot &&
        test_when_finished cleanup_git &&
-       cd "$git" &&
-       gittime=$(git show -s --raw --pretty=format:%at HEAD) &&
-       echo $p4time $gittime &&
-       test $p4time = $gittime
+       (
+               cd "$git" &&
+               gittime=$(git show -s --raw --pretty=format:%at HEAD) &&
+               echo $p4time $gittime &&
+               test $p4time = $gittime
+       )
 '
 
 # Rename a file and confirm that rename is not detected in P4.
@@ -279,47 +267,49 @@ test_expect_success 'initial import time from top change time' '
 test_expect_success 'detect renames' '
        "$GITP4" clone --dest="$git" //depot@all &&
        test_when_finished cleanup_git &&
-       cd "$git" &&
-       git config git-p4.skipSubmitEditCheck true &&
-
-       git mv file1 file4 &&
-       git commit -a -m "Rename file1 to file4" &&
-       git diff-tree -r -M HEAD &&
-       "$GITP4" submit &&
-       p4 filelog //depot/file4 &&
-       ! p4 filelog //depot/file4 | grep -q "branch from" &&
-
-       git mv file4 file5 &&
-       git commit -a -m "Rename file4 to file5" &&
-       git diff-tree -r -M HEAD &&
-       git config git-p4.detectRenames true &&
-       "$GITP4" submit &&
-       p4 filelog //depot/file5 &&
-       p4 filelog //depot/file5 | grep -q "branch from //depot/file4" &&
-
-       git mv file5 file6 &&
-       echo update >>file6 &&
-       git add file6 &&
-       git commit -a -m "Rename file5 to file6 with changes" &&
-       git diff-tree -r -M HEAD &&
-       level=$(git diff-tree -r -M HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/R0*//") &&
-       test -n "$level" && test "$level" -gt 0 && test "$level" -lt 98 &&
-       git config git-p4.detectRenames $((level + 2)) &&
-       "$GITP4" submit &&
-       p4 filelog //depot/file6 &&
-       ! p4 filelog //depot/file6 | grep -q "branch from" &&
-
-       git mv file6 file7 &&
-       echo update >>file7 &&
-       git add file7 &&
-       git commit -a -m "Rename file6 to file7 with changes" &&
-       git diff-tree -r -M HEAD &&
-       level=$(git diff-tree -r -M HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/R0*//") &&
-       test -n "$level" && test "$level" -gt 2 && test "$level" -lt 100 &&
-       git config git-p4.detectRenames $((level - 2)) &&
-       "$GITP4" submit &&
-       p4 filelog //depot/file7 &&
-       p4 filelog //depot/file7 | grep -q "branch from //depot/file6"
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEditCheck true &&
+
+               git mv file1 file4 &&
+               git commit -a -m "Rename file1 to file4" &&
+               git diff-tree -r -M HEAD &&
+               "$GITP4" submit &&
+               p4 filelog //depot/file4 &&
+               p4 filelog //depot/file4 | test_must_fail grep -q "branch from" &&
+
+               git mv file4 file5 &&
+               git commit -a -m "Rename file4 to file5" &&
+               git diff-tree -r -M HEAD &&
+               git config git-p4.detectRenames true &&
+               "$GITP4" submit &&
+               p4 filelog //depot/file5 &&
+               p4 filelog //depot/file5 | grep -q "branch from //depot/file4" &&
+
+               git mv file5 file6 &&
+               echo update >>file6 &&
+               git add file6 &&
+               git commit -a -m "Rename file5 to file6 with changes" &&
+               git diff-tree -r -M HEAD &&
+               level=$(git diff-tree -r -M HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/R0*//") &&
+               test -n "$level" && test "$level" -gt 0 && test "$level" -lt 98 &&
+               git config git-p4.detectRenames $(($level + 2)) &&
+               "$GITP4" submit &&
+               p4 filelog //depot/file6 &&
+               p4 filelog //depot/file6 | test_must_fail grep -q "branch from" &&
+
+               git mv file6 file7 &&
+               echo update >>file7 &&
+               git add file7 &&
+               git commit -a -m "Rename file6 to file7 with changes" &&
+               git diff-tree -r -M HEAD &&
+               level=$(git diff-tree -r -M HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/R0*//") &&
+               test -n "$level" && test "$level" -gt 2 && test "$level" -lt 100 &&
+               git config git-p4.detectRenames $(($level - 2)) &&
+               "$GITP4" submit &&
+               p4 filelog //depot/file7 &&
+               p4 filelog //depot/file7 | grep -q "branch from //depot/file6"
+       )
 '
 
 # Copy a file and confirm that copy is not detected in P4.
@@ -336,141 +326,79 @@ test_expect_success 'detect renames' '
 test_expect_success 'detect copies' '
        "$GITP4" clone --dest="$git" //depot@all &&
        test_when_finished cleanup_git &&
-       cd "$git" &&
-       git config git-p4.skipSubmitEditCheck true &&
-
-       cp file2 file8 &&
-       git add file8 &&
-       git commit -a -m "Copy file2 to file8" &&
-       git diff-tree -r -C HEAD &&
-       "$GITP4" submit &&
-       p4 filelog //depot/file8 &&
-       ! p4 filelog //depot/file8 | grep -q "branch from" &&
-
-       cp file2 file9 &&
-       git add file9 &&
-       git commit -a -m "Copy file2 to file9" &&
-       git diff-tree -r -C HEAD &&
-       git config git-p4.detectCopies true &&
-       "$GITP4" submit &&
-       p4 filelog //depot/file9 &&
-       ! p4 filelog //depot/file9 | grep -q "branch from" &&
-
-       echo "file2" >>file2 &&
-       cp file2 file10 &&
-       git add file2 file10 &&
-       git commit -a -m "Modify and copy file2 to file10" &&
-       git diff-tree -r -C HEAD &&
-       "$GITP4" submit &&
-       p4 filelog //depot/file10 &&
-       p4 filelog //depot/file10 | grep -q "branch from //depot/file" &&
-
-       cp file2 file11 &&
-       git add file11 &&
-       git commit -a -m "Copy file2 to file11" &&
-       git diff-tree -r -C --find-copies-harder HEAD &&
-       src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
-       test "$src" = file10 &&
-       git config git-p4.detectCopiesHarder true &&
-       "$GITP4" submit &&
-       p4 filelog //depot/file11 &&
-       p4 filelog //depot/file11 | grep -q "branch from //depot/file" &&
-
-       cp file2 file12 &&
-       echo "some text" >>file12 &&
-       git add file12 &&
-       git commit -a -m "Copy file2 to file12 with changes" &&
-       git diff-tree -r -C --find-copies-harder HEAD &&
-       level=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/C0*//") &&
-       test -n "$level" && test "$level" -gt 0 && test "$level" -lt 98 &&
-       src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
-       test "$src" = file10 &&
-       git config git-p4.detectCopies $((level + 2)) &&
-       "$GITP4" submit &&
-       p4 filelog //depot/file12 &&
-       ! p4 filelog //depot/file12 | grep -q "branch from" &&
-
-       cp file2 file13 &&
-       echo "different text" >>file13 &&
-       git add file13 &&
-       git commit -a -m "Copy file2 to file13 with changes" &&
-       git diff-tree -r -C --find-copies-harder HEAD &&
-       level=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/C0*//") &&
-       test -n "$level" && test "$level" -gt 2 && test "$level" -lt 100 &&
-       src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
-       test "$src" = file10 &&
-       git config git-p4.detectCopies $((level - 2)) &&
-       "$GITP4" submit &&
-       p4 filelog //depot/file13 &&
-       p4 filelog //depot/file13 | grep -q "branch from //depot/file"
-'
-
-# Create a simple branch structure in P4 depot to check if it is correctly
-# cloned.
-test_expect_success 'add simple p4 branches' '
-       cd "$cli" &&
-       mkdir branch1 &&
-       cd branch1 &&
-       echo file1 >file1 &&
-       echo file2 >file2 &&
-       p4 add file1 file2 &&
-       p4 submit -d "branch1" &&
-       p4 integrate //depot/branch1/... //depot/branch2/... &&
-       p4 submit -d "branch2" &&
-       echo file3 >file3 &&
-       p4 add file3 &&
-       p4 submit -d "add file3 in branch1" &&
-       p4 open file2 &&
-       echo update >>file2 &&
-       p4 submit -d "update file2 in branch1" &&
-       p4 integrate //depot/branch1/... //depot/branch3/... &&
-       p4 submit -d "branch3" &&
-       cd "$TRASH_DIRECTORY"
-'
-
-# Configure branches through git-config and clone them.
-# All files are tested to make sure branches were cloned correctly.
-# Finally, make an update to branch1 on P4 side to check if it is imported
-# correctly by git-p4.
-test_expect_success 'git-p4 clone simple branches' '
-       test_when_finished cleanup_git &&
-       test_create_repo "$git" &&
-       cd "$git" &&
-       git config git-p4.branchList branch1:branch2 &&
-       git config --add git-p4.branchList branch1:branch3 &&
-       "$GITP4" clone --dest=. --detect-branches //depot@all &&
-       git log --all --graph --decorate --stat &&
-       git reset --hard p4/depot/branch1 &&
-       test -f file1 &&
-       test -f file2 &&
-       test -f file3 &&
-       grep -q update file2 &&
-       git reset --hard p4/depot/branch2 &&
-       test -f file1 &&
-       test -f file2 &&
-       test ! -f file3 &&
-       ! grep -q update file2 &&
-       git reset --hard p4/depot/branch3 &&
-       test -f file1 &&
-       test -f file2 &&
-       test -f file3 &&
-       grep -q update file2 &&
-       cd "$cli" &&
-       cd branch1 &&
-       p4 edit file2 &&
-       echo file2_ >>file2 &&
-       p4 submit -d "update file2 in branch1" &&
-       cd "$git" &&
-       git reset --hard p4/depot/branch1 &&
-       "$GITP4" rebase &&
-       grep -q file2_ file2
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEditCheck true &&
+
+               cp file2 file8 &&
+               git add file8 &&
+               git commit -a -m "Copy file2 to file8" &&
+               git diff-tree -r -C HEAD &&
+               "$GITP4" submit &&
+               p4 filelog //depot/file8 &&
+               p4 filelog //depot/file8 | test_must_fail grep -q "branch from" &&
+
+               cp file2 file9 &&
+               git add file9 &&
+               git commit -a -m "Copy file2 to file9" &&
+               git diff-tree -r -C HEAD &&
+               git config git-p4.detectCopies true &&
+               "$GITP4" submit &&
+               p4 filelog //depot/file9 &&
+               p4 filelog //depot/file9 | test_must_fail grep -q "branch from" &&
+
+               echo "file2" >>file2 &&
+               cp file2 file10 &&
+               git add file2 file10 &&
+               git commit -a -m "Modify and copy file2 to file10" &&
+               git diff-tree -r -C HEAD &&
+               "$GITP4" submit &&
+               p4 filelog //depot/file10 &&
+               p4 filelog //depot/file10 | grep -q "branch from //depot/file" &&
+
+               cp file2 file11 &&
+               git add file11 &&
+               git commit -a -m "Copy file2 to file11" &&
+               git diff-tree -r -C --find-copies-harder HEAD &&
+               src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
+               test "$src" = file10 &&
+               git config git-p4.detectCopiesHarder true &&
+               "$GITP4" submit &&
+               p4 filelog //depot/file11 &&
+               p4 filelog //depot/file11 | grep -q "branch from //depot/file" &&
+
+               cp file2 file12 &&
+               echo "some text" >>file12 &&
+               git add file12 &&
+               git commit -a -m "Copy file2 to file12 with changes" &&
+               git diff-tree -r -C --find-copies-harder HEAD &&
+               level=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/C0*//") &&
+               test -n "$level" && test "$level" -gt 0 && test "$level" -lt 98 &&
+               src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
+               test "$src" = file10 &&
+               git config git-p4.detectCopies $(($level + 2)) &&
+               "$GITP4" submit &&
+               p4 filelog //depot/file12 &&
+               p4 filelog //depot/file12 | test_must_fail grep -q "branch from" &&
+
+               cp file2 file13 &&
+               echo "different text" >>file13 &&
+               git add file13 &&
+               git commit -a -m "Copy file2 to file13 with changes" &&
+               git diff-tree -r -C --find-copies-harder HEAD &&
+               level=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f1 | cut -d" " -f5 | sed "s/C0*//") &&
+               test -n "$level" && test "$level" -gt 2 && test "$level" -lt 100 &&
+               src=$(git diff-tree -r -C --find-copies-harder HEAD | sed 1d | cut -f2) &&
+               test "$src" = file10 &&
+               git config git-p4.detectCopies $(($level - 2)) &&
+               "$GITP4" submit &&
+               p4 filelog //depot/file13 &&
+               p4 filelog //depot/file13 | grep -q "branch from //depot/file"
+       )
 '
 
-test_expect_success 'shutdown' '
-       pid=`pgrep -f p4d` &&
-       test -n "$pid" &&
-       test_debug "ps wl `echo $pid`" &&
-       kill $pid
+test_expect_success 'kill p4d' '
+       kill_p4d
 '
 
 test_done
diff --git a/t/t9801-git-p4-branch.sh b/t/t9801-git-p4-branch.sh
new file mode 100755 (executable)
index 0000000..a25f18d
--- /dev/null
@@ -0,0 +1,233 @@
+#!/bin/sh
+
+test_description='git-p4 p4 branching tests'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+#
+# 1: //depot/main/f1
+# 2: //depot/main/f2
+# 3: integrate //depot/main/... -> //depot/branch1/...
+# 4: //depot/main/f4
+# 5: //depot/branch1/f5
+# .: named branch branch2
+# 6: integrate -b branch2
+# 7: //depot/branch2/f7
+# 8: //depot/main/f8
+#
+test_expect_success 'basic p4 branches' '
+       (
+               cd "$cli" &&
+               mkdir -p main &&
+
+               echo f1 >main/f1 &&
+               p4 add main/f1 &&
+               p4 submit -d "main/f1" &&
+
+               echo f2 >main/f2 &&
+               p4 add main/f2 &&
+               p4 submit -d "main/f2" &&
+
+               p4 integrate //depot/main/... //depot/branch1/... &&
+               p4 submit -d "integrate main to branch1" &&
+
+               echo f4 >main/f4 &&
+               p4 add main/f4 &&
+               p4 submit -d "main/f4" &&
+
+               echo f5 >branch1/f5 &&
+               p4 add branch1/f5 &&
+               p4 submit -d "branch1/f5" &&
+
+               p4 branch -i <<-EOF &&
+               Branch: branch2
+               View: //depot/main/... //depot/branch2/...
+               EOF
+
+               p4 integrate -b branch2 &&
+               p4 submit -d "integrate main to branch2" &&
+
+               echo f7 >branch2/f7 &&
+               p4 add branch2/f7 &&
+               p4 submit -d "branch2/f7" &&
+
+               echo f8 >main/f8 &&
+               p4 add main/f8 &&
+               p4 submit -d "main/f8"
+       )
+'
+
+test_expect_success 'import main, no branch detection' '
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" //depot/main@all &&
+       (
+               cd "$git" &&
+               git log --oneline --graph --decorate --all &&
+               git rev-list master >wc &&
+               test_line_count = 4 wc
+       )
+'
+
+test_expect_success 'import branch1, no branch detection' '
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" //depot/branch1@all &&
+       (
+               cd "$git" &&
+               git log --oneline --graph --decorate --all &&
+               git rev-list master >wc &&
+               test_line_count = 2 wc
+       )
+'
+
+test_expect_success 'import branch2, no branch detection' '
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" //depot/branch2@all &&
+       (
+               cd "$git" &&
+               git log --oneline --graph --decorate --all &&
+               git rev-list master >wc &&
+               test_line_count = 2 wc
+       )
+'
+
+test_expect_success 'import depot, no branch detection' '
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" //depot@all &&
+       (
+               cd "$git" &&
+               git log --oneline --graph --decorate --all &&
+               git rev-list master >wc &&
+               test_line_count = 8 wc
+       )
+'
+
+test_expect_success 'import depot, branch detection' '
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" --detect-branches //depot@all &&
+       (
+               cd "$git" &&
+
+               git log --oneline --graph --decorate --all &&
+
+               # 4 main commits
+               git rev-list master >wc &&
+               test_line_count = 4 wc &&
+
+               # 3 main, 1 integrate, 1 on branch2
+               git rev-list p4/depot/branch2 >wc &&
+               test_line_count = 5 wc &&
+
+               # no branch1, since no p4 branch created for it
+               test_must_fail git show-ref p4/depot/branch1
+       )
+'
+
+test_expect_success 'import depot, branch detection, branchList branch definition' '
+       test_when_finished cleanup_git &&
+       test_create_repo "$git" &&
+       (
+               cd "$git" &&
+               git config git-p4.branchList main:branch1 &&
+               "$GITP4" clone --dest=. --detect-branches //depot@all &&
+
+               git log --oneline --graph --decorate --all &&
+
+               # 4 main commits
+               git rev-list master >wc &&
+               test_line_count = 4 wc &&
+
+               # 3 main, 1 integrate, 1 on branch2
+               git rev-list p4/depot/branch2 >wc &&
+               test_line_count = 5 wc &&
+
+               # 2 main, 1 integrate, 1 on branch1
+               git rev-list p4/depot/branch1 >wc &&
+               test_line_count = 4 wc
+       )
+'
+
+test_expect_success 'restart p4d' '
+       kill_p4d &&
+       start_p4d
+'
+
+#
+# 1: //depot/branch1/file1
+#    //depot/branch1/file2
+# 2: integrate //depot/branch1/... -> //depot/branch2/...
+# 3: //depot/branch1/file3
+# 4: //depot/branch1/file2 (edit)
+# 5: integrate //depot/branch1/... -> //depot/branch3/...
+#
+## Create a simple branch structure in P4 depot.
+test_expect_success 'add simple p4 branches' '
+       (
+               cd "$cli" &&
+               mkdir branch1 &&
+               cd branch1 &&
+               echo file1 >file1 &&
+               echo file2 >file2 &&
+               p4 add file1 file2 &&
+               p4 submit -d "branch1" &&
+               p4 integrate //depot/branch1/... //depot/branch2/... &&
+               p4 submit -d "branch2" &&
+               echo file3 >file3 &&
+               p4 add file3 &&
+               p4 submit -d "add file3 in branch1" &&
+               p4 open file2 &&
+               echo update >>file2 &&
+               p4 submit -d "update file2 in branch1" &&
+               p4 integrate //depot/branch1/... //depot/branch3/... &&
+               p4 submit -d "branch3"
+       )
+'
+
+# Configure branches through git-config and clone them.
+# All files are tested to make sure branches were cloned correctly.
+# Finally, make an update to branch1 on P4 side to check if it is imported
+# correctly by git-p4.
+test_expect_success 'git-p4 clone simple branches' '
+       test_when_finished cleanup_git &&
+       test_create_repo "$git" &&
+       (
+               cd "$git" &&
+               git config git-p4.branchList branch1:branch2 &&
+               git config --add git-p4.branchList branch1:branch3 &&
+               "$GITP4" clone --dest=. --detect-branches //depot@all &&
+               git log --all --graph --decorate --stat &&
+               git reset --hard p4/depot/branch1 &&
+               test -f file1 &&
+               test -f file2 &&
+               test -f file3 &&
+               grep -q update file2 &&
+               git reset --hard p4/depot/branch2 &&
+               test -f file1 &&
+               test -f file2 &&
+               test ! -f file3 &&
+               test_must_fail grep -q update file2 &&
+               git reset --hard p4/depot/branch3 &&
+               test -f file1 &&
+               test -f file2 &&
+               test -f file3 &&
+               grep -q update file2 &&
+               cd "$cli" &&
+               cd branch1 &&
+               p4 edit file2 &&
+               echo file2_ >>file2 &&
+               p4 submit -d "update file2 in branch3" &&
+               cd "$git" &&
+               git reset --hard p4/depot/branch1 &&
+               "$GITP4" rebase &&
+               grep -q file2_ file2
+       )
+'
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
diff --git a/t/t9802-git-p4-filetype.sh b/t/t9802-git-p4-filetype.sh
new file mode 100755 (executable)
index 0000000..992bb8c
--- /dev/null
@@ -0,0 +1,139 @@
+#!/bin/sh
+
+test_description='git-p4 p4 filetype tests'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+test_expect_success 'utf-16 file create' '
+       (
+               cd "$cli" &&
+
+               # p4 saves this verbatim
+               printf "three\nline\ntext\n" >f-ascii &&
+               p4 add -t text f-ascii &&
+
+               # p4 adds \377\376 header
+               cp f-ascii f-ascii-as-utf16 &&
+               p4 add -t utf16 f-ascii-as-utf16 &&
+
+               # p4 saves this exactly as iconv produced it
+               printf "three\nline\ntext\n" | iconv -f ascii -t utf-16 >f-utf16 &&
+               p4 add -t utf16 f-utf16 &&
+
+               # this also is unchanged
+               cp f-utf16 f-utf16-as-text &&
+               p4 add -t text f-utf16-as-text &&
+
+               p4 submit -d "f files" &&
+
+               # force update of client files
+               p4 sync -f
+       )
+'
+
+test_expect_success 'utf-16 file test' '
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" //depot@all &&
+       (
+               cd "$git" &&
+
+               test_cmp "$cli/f-ascii" f-ascii &&
+               test_cmp "$cli/f-ascii-as-utf16" f-ascii-as-utf16 &&
+               test_cmp "$cli/f-utf16" f-utf16 &&
+               test_cmp "$cli/f-utf16-as-text" f-utf16-as-text
+       )
+'
+
+test_expect_success 'keyword file create' '
+       (
+               cd "$cli" &&
+
+               printf "id\n\$Id\$\n\$Author\$\ntext\n" >k-text-k &&
+               p4 add -t text+k k-text-k &&
+
+               cp k-text-k k-text-ko &&
+               p4 add -t text+ko k-text-ko &&
+
+               cat k-text-k | iconv -f ascii -t utf-16 >k-utf16-k &&
+               p4 add -t utf16+k k-utf16-k &&
+
+               cp k-utf16-k k-utf16-ko &&
+               p4 add -t utf16+ko k-utf16-ko &&
+
+               p4 submit -d "k files" &&
+               p4 sync -f
+       )
+'
+
+build_smush() {
+       cat >k_smush.py <<-\EOF &&
+       import re, sys
+       sys.stdout.write(re.sub(r'(?i)\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$]*\$', r'$\1$', sys.stdin.read()))
+       EOF
+       cat >ko_smush.py <<-\EOF
+       import re, sys
+       sys.stdout.write(re.sub(r'(?i)\$(Id|Header):[^$]*\$', r'$\1$', sys.stdin.read()))
+       EOF
+}
+
+test_expect_success 'keyword file test' '
+       build_smush &&
+       test_when_finished rm -f k_smush.py ko_smush.py &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" //depot@all &&
+       (
+               cd "$git" &&
+
+               # text, ensure unexpanded
+               "$PYTHON_PATH" "$TRASH_DIRECTORY/k_smush.py" <"$cli/k-text-k" >cli-k-text-k-smush &&
+               test_cmp cli-k-text-k-smush k-text-k &&
+               "$PYTHON_PATH" "$TRASH_DIRECTORY/ko_smush.py" <"$cli/k-text-ko" >cli-k-text-ko-smush &&
+               test_cmp cli-k-text-ko-smush k-text-ko &&
+
+               # utf16, even though p4 expands keywords, git-p4 does not
+               # try to undo that
+               test_cmp "$cli/k-utf16-k" k-utf16-k &&
+               test_cmp "$cli/k-utf16-ko" k-utf16-ko
+       )
+'
+
+build_gendouble() {
+       cat >gendouble.py <<-\EOF
+       import sys
+       import struct
+       import array
+
+       s = array.array("c", '\0' * 26)
+       struct.pack_into(">L", s,  0, 0x00051607)  # AppleDouble
+       struct.pack_into(">L", s,  4, 0x00020000)  # version 2
+       s.tofile(sys.stdout)
+       EOF
+}
+
+test_expect_success 'ignore apple' '
+       test_when_finished rm -f gendouble.py &&
+       build_gendouble &&
+       (
+               cd "$cli" &&
+               test-genrandom apple 1024 >double.png &&
+               "$PYTHON_PATH" "$TRASH_DIRECTORY/gendouble.py" >%double.png &&
+               p4 add -t apple double.png &&
+               p4 submit -d appledouble
+       ) &&
+       test_when_finished cleanup_git &&
+       "$GITP4" clone --dest="$git" //depot@all &&
+       (
+               cd "$git" &&
+               test ! -f double.png
+       )
+'
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
diff --git a/t/t9803-git-shell-metachars.sh b/t/t9803-git-shell-metachars.sh
new file mode 100755 (executable)
index 0000000..db04375
--- /dev/null
@@ -0,0 +1,64 @@
+#!/bin/sh
+
+test_description='git-p4 transparency to shell metachars in filenames'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+test_expect_success 'init depot' '
+       (
+               cd "$cli" &&
+               echo file1 >file1 &&
+               p4 add file1 &&
+               p4 submit -d "file1"
+       )
+'
+
+test_expect_success 'shell metachars in filenames' '
+       "$GITP4" clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEditCheck true &&
+               echo f1 >foo\$bar &&
+               git add foo\$bar &&
+               echo f2 >"file with spaces" &&
+               git add "file with spaces" &&
+               git commit -m "add files" &&
+               P4EDITOR=touch "$GITP4" submit
+       ) &&
+       (
+               cd "$cli" &&
+               p4 sync ... &&
+               test -e "file with spaces" &&
+               test -e "foo\$bar"
+       )
+'
+
+test_expect_success 'deleting with shell metachars' '
+       "$GITP4" clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEditCheck true &&
+               git rm foo\$bar &&
+               git rm file\ with\ spaces &&
+               git commit -m "remove files" &&
+               P4EDITOR=touch "$GITP4" submit
+       ) &&
+       (
+               cd "$cli" &&
+               p4 sync ... &&
+               test ! -e "file with spaces" &&
+               test ! -e foo\$bar
+       )
+'
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
diff --git a/t/t9805-skip-submit-edit.sh b/t/t9805-skip-submit-edit.sh
new file mode 100755 (executable)
index 0000000..734ccf2
--- /dev/null
@@ -0,0 +1,82 @@
+#!/bin/sh
+
+test_description='git-p4 skipSubmitEdit config variables'
+
+. ./lib-git-p4.sh
+
+test_expect_success 'start p4d' '
+       start_p4d
+'
+
+test_expect_success 'init depot' '
+       (
+               cd "$cli" &&
+               echo file1 >file1 &&
+               p4 add file1 &&
+               p4 submit -d "change 1"
+       )
+'
+
+# this works because EDITOR is set to :
+test_expect_success 'no config, unedited, say yes' '
+       "$GITP4" clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               echo line >>file1 &&
+               git commit -a -m "change 2" &&
+               echo y | "$GITP4" submit &&
+               p4 changes //depot/... >wc &&
+               test_line_count = 2 wc
+       )
+'
+
+test_expect_success 'no config, unedited, say no' '
+       "$GITP4" clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               echo line >>file1 &&
+               git commit -a -m "change 3 (not really)" &&
+               printf "bad response\nn\n" | "$GITP4" submit
+               p4 changes //depot/... >wc &&
+               test_line_count = 2 wc
+       )
+'
+
+test_expect_success 'skipSubmitEdit' '
+       "$GITP4" clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEdit true &&
+               # will fail if editor is even invoked
+               git config core.editor /bin/false &&
+               echo line >>file1 &&
+               git commit -a -m "change 3" &&
+               "$GITP4" submit &&
+               p4 changes //depot/... >wc &&
+               test_line_count = 3 wc
+       )
+'
+
+test_expect_success 'skipSubmitEditCheck' '
+       "$GITP4" clone --dest="$git" //depot &&
+       test_when_finished cleanup_git &&
+       (
+               cd "$git" &&
+               git config git-p4.skipSubmitEditCheck true &&
+               echo line >>file1 &&
+               git commit -a -m "change 4" &&
+               "$GITP4" submit &&
+               p4 changes //depot/... >wc &&
+               test_line_count = 4 wc
+       )
+'
+
+
+test_expect_success 'kill p4d' '
+       kill_p4d
+'
+
+test_done
diff --git a/t/t9901-git-web--browse.sh b/t/t9901-git-web--browse.sh
new file mode 100755 (executable)
index 0000000..b0a6bad
--- /dev/null
@@ -0,0 +1,63 @@
+#!/bin/sh
+#
+
+test_description='git web--browse basic tests
+
+This test checks that git web--browse can handle various valid URLs.'
+
+. ./test-lib.sh
+
+test_web_browse () {
+       # browser=$1 url=$2
+       git web--browse --browser="$1" "$2" >actual &&
+       tr -d '\015' <actual >text &&
+       test_cmp expect text
+}
+
+test_expect_success \
+       'URL with an ampersand in it' '
+       echo http://example.com/foo\&bar >expect &&
+       git config browser.custom.cmd echo &&
+       test_web_browse custom http://example.com/foo\&bar
+'
+
+test_expect_success \
+       'URL with a semi-colon in it' '
+       echo http://example.com/foo\;bar >expect &&
+       git config browser.custom.cmd echo &&
+       test_web_browse custom http://example.com/foo\;bar
+'
+
+test_expect_success \
+       'URL with a hash in it' '
+       echo http://example.com/foo#bar >expect &&
+       git config browser.custom.cmd echo &&
+       test_web_browse custom http://example.com/foo#bar
+'
+
+test_expect_success \
+       'browser paths are properly quoted' '
+       echo fake: http://example.com/foo >expect &&
+       cat >"fake browser" <<-\EOF &&
+       #!/bin/sh
+       echo fake: "$@"
+       EOF
+       chmod +x "fake browser" &&
+       git config browser.w3m.path "`pwd`/fake browser" &&
+       test_web_browse w3m http://example.com/foo
+'
+
+test_expect_success \
+       'browser command allows arbitrary shell code' '
+       echo "arg: http://example.com/foo" >expect &&
+       git config browser.custom.cmd "
+               f() {
+                       for i in \"\$@\"; do
+                               echo arg: \$i
+                       done
+               }
+               f" &&
+       test_web_browse custom http://example.com/foo
+'
+
+test_done
index b187c4bb1f256e19c25f80dd64f3451ced77e123..18c48297652174ffae65b877dd131711a5746181 100755 (executable)
@@ -18,6 +18,9 @@ fi
 # If you want to allow non-ascii filenames set this variable to true.
 allownonascii=$(git config hooks.allownonascii)
 
+# Redirect output to stderr.
+exec 1>&2
+
 # Cross platform projects tend to avoid non-ascii filenames; prevent
 # them from being added to the repository. We exploit the fact that the
 # printable range starts at the space character and ends with tilde.
@@ -25,8 +28,8 @@ if [ "$allownonascii" != "true" ] &&
        # Note that the use of brackets around a tr range is ok here, (it's
        # even required, for portability to Solaris 10's /usr/bin/tr), since
        # the square bracket bytes happen to fall in the designated range.
-       test "$(git diff --cached --name-only --diff-filter=A -z $against |
-         LC_ALL=C tr -d '[ -~]\0')"
+       test $(git diff --cached --name-only --diff-filter=A -z $against |
+         LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
 then
        echo "Error: Attempt to add a non-ascii file name."
        echo
@@ -43,4 +46,5 @@ then
        exit 1
 fi
 
+# If there are whitespace errors, print the offending file names and fail.
 exec git diff-index --check --cached $against --
index 033c74911ed035ed36b16b0e2bf7327d6cdc5823..707a821f03d59b1b3685b380fa4f3e83535c5342 100644 (file)
@@ -1,78 +1,42 @@
 #include "cache.h"
 
+static int rc;
 
-static int test_isdigit(int c)
+static void report_error(const char *class, int ch)
 {
-       return isdigit(c);
+       printf("%s classifies char %d (0x%02x) wrongly\n", class, ch, ch);
+       rc = 1;
 }
 
-static int test_isspace(int c)
+static int is_in(const char *s, int ch)
 {
-       return isspace(c);
+       /* We can't find NUL using strchr.  It's classless anyway. */
+       if (ch == '\0')
+               return 0;
+       return !!strchr(s, ch);
 }
 
-static int test_isalpha(int c)
-{
-       return isalpha(c);
-}
-
-static int test_isalnum(int c)
-{
-       return isalnum(c);
-}
-
-static int test_is_glob_special(int c)
-{
-       return is_glob_special(c);
-}
-
-static int test_is_regex_special(int c)
-{
-       return is_regex_special(c);
+#define TEST_CLASS(t,s) {                      \
+       int i;                                  \
+       for (i = 0; i < 256; i++) {             \
+               if (is_in(s, i) != t(i))        \
+                       report_error(#t, i);    \
+       }                                       \
 }
 
 #define DIGIT "0123456789"
 #define LOWER "abcdefghijklmnopqrstuvwxyz"
 #define UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
 
-static const struct ctype_class {
-       const char *name;
-       int (*test_fn)(int);
-       const char *members;
-} classes[] = {
-       { "isdigit", test_isdigit, DIGIT },
-       { "isspace", test_isspace, " \n\r\t" },
-       { "isalpha", test_isalpha, LOWER UPPER },
-       { "isalnum", test_isalnum, LOWER UPPER DIGIT },
-       { "is_glob_special", test_is_glob_special, "*?[\\" },
-       { "is_regex_special", test_is_regex_special, "$()*+.?[\\^{|" },
-       { NULL }
-};
-
-static int test_class(const struct ctype_class *test)
-{
-       int i, rc = 0;
-
-       for (i = 0; i < 256; i++) {
-               int expected = i ? !!strchr(test->members, i) : 0;
-               int actual = test->test_fn(i);
-
-               if (actual != expected) {
-                       rc = 1;
-                       printf("%s classifies char %d (0x%02x) wrongly\n",
-                              test->name, i, i);
-               }
-       }
-       return rc;
-}
-
 int main(int argc, char **argv)
 {
-       const struct ctype_class *test;
-       int rc = 0;
-
-       for (test = classes; test->name; test++)
-               rc |= test_class(test);
+       TEST_CLASS(isdigit, DIGIT);
+       TEST_CLASS(isspace, " \n\r\t");
+       TEST_CLASS(isalpha, LOWER UPPER);
+       TEST_CLASS(isalnum, LOWER UPPER DIGIT);
+       TEST_CLASS(is_glob_special, "*?[\\");
+       TEST_CLASS(is_regex_special, "$()*+.?[\\^{|");
+       TEST_CLASS(is_pathspec_magic, "!\"#%&',-/:;<=>@_`~");
 
        return rc;
 }
index 91a5701657556e07b4f4daafa947346d3b197cd1..36487c402b264433a3bfda7f84ef9c9a8df8cbb7 100644 (file)
@@ -54,6 +54,7 @@ int main(int argc, const char **argv)
                OPT_STRING(0, "string2", &string, "str", "get another string"),
                OPT_STRING(0, "st", &string, "st", "get another string (pervert ordering)"),
                OPT_STRING('o', NULL, &string, "str", "get another string"),
+               OPT_NOOP_NOARG(0, "obsolete"),
                OPT_SET_PTR(0, "default-string", &string,
                        "set string to default", (unsigned long)"default"),
                OPT_STRING_LIST(0, "list", &list, "str", "add str to list"),
index 4eab844d4abfa29ce53b6245a5821ee7be03641b..6f227e253bf638de37ce74347213657c23185afa 100644 (file)
@@ -183,7 +183,7 @@ static struct child_process *get_helper(struct transport *transport)
                        ALLOC_GROW(refspecs,
                                   refspec_nr + 1,
                                   refspec_alloc);
-                       refspecs[refspec_nr++] = strdup(capname + strlen("refspec "));
+                       refspecs[refspec_nr++] = xstrdup(capname + strlen("refspec "));
                } else if (!strcmp(capname, "connect")) {
                        data->connect = 1;
                } else if (!prefixcmp(capname, "export-marks ")) {
@@ -445,9 +445,11 @@ static int fetch_with_import(struct transport *transport,
                if (data->refspecs)
                        private = apply_refspecs(data->refspecs, data->refspec_nr, posn->name);
                else
-                       private = strdup(posn->name);
-               read_ref(private, posn->old_sha1);
-               free(private);
+                       private = xstrdup(posn->name);
+               if (private) {
+                       read_ref(private, posn->old_sha1);
+                       free(private);
+               }
        }
        strbuf_release(&buf);
        return 0;
index e1940615e28ebc3b16f80b8f647f216031f64ee8..51814b5da3064b617fa268a6b2bc6b3d6cb0b110 100644 (file)
@@ -755,18 +755,10 @@ void transport_verify_remote_names(int nr_heads, const char **heads)
                        continue;
 
                remote = remote ? (remote + 1) : local;
-               switch (check_ref_format(remote)) {
-               case 0: /* ok */
-               case CHECK_REF_FORMAT_ONELEVEL:
-                       /* ok but a single level -- that is fine for
-                        * a match pattern.
-                        */
-               case CHECK_REF_FORMAT_WILDCARD:
-                       /* ok but ends with a pattern-match character */
-                       continue;
-               }
-               die("remote part of refspec is not a valid name in %s",
-                   heads[i]);
+               if (check_refname_format(remote,
+                               REFNAME_ALLOW_ONELEVEL|REFNAME_REFSPEC_PATTERN))
+                       die("remote part of refspec is not a valid name in %s",
+                               heads[i]);
        }
 }
 
@@ -915,7 +907,7 @@ struct transport *transport_get(struct remote *remote, const char *url)
                ret->fetch = fetch_objs_via_rsync;
                ret->push = rsync_transport_push;
                ret->smart_options = NULL;
-       } else if (is_local(url) && is_file(url)) {
+       } else if (is_local(url) && is_file(url) && is_bundle(url, 1)) {
                struct bundle_transport_data *data = xcalloc(1, sizeof(*data));
                ret->data = data;
                ret->get_refs_list = get_refs_from_bundle;
@@ -1034,8 +1026,8 @@ int transport_push(struct transport *transport,
                if (flags & TRANSPORT_PUSH_MIRROR)
                        match_flags |= MATCH_REFS_MIRROR;
 
-               if (match_refs(local_refs, &remote_refs,
-                              refspec_nr, refspec, match_flags)) {
+               if (match_push_refs(local_refs, &remote_refs,
+                                   refspec_nr, refspec, match_flags)) {
                        return -1;
                }
 
index b3cc2e4753447d4734ed08a70e3396771257dced..7a51d091b5a6babed91f60b9ff7079308afa4e6f 100644 (file)
@@ -21,8 +21,8 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2,
        sha1 = tree_entry_extract(t1, &path1, &mode1);
        sha2 = tree_entry_extract(t2, &path2, &mode2);
 
-       pathlen1 = tree_entry_len(path1, sha1);
-       pathlen2 = tree_entry_len(path2, sha2);
+       pathlen1 = tree_entry_len(&t1->entry);
+       pathlen2 = tree_entry_len(&t2->entry);
        cmp = base_name_compare(path1, pathlen1, mode1, path2, pathlen2, mode2);
        if (cmp < 0) {
                show_entry(opt, "-", t1, base);
@@ -64,14 +64,14 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2,
 static void show_tree(struct diff_options *opt, const char *prefix,
                      struct tree_desc *desc, struct strbuf *base)
 {
-       int match = 0;
+       enum interesting match = entry_not_interesting;
        for (; desc->size; update_tree_entry(desc)) {
-               if (match != 2) {
+               if (match != all_entries_interesting) {
                        match = tree_entry_interesting(&desc->entry, base, 0,
                                                       &opt->pathspec);
-                       if (match < 0)
+                       if (match == all_entries_not_interesting)
                                break;
-                       if (match == 0)
+                       if (match == entry_not_interesting)
                                continue;
                }
                show_entry(opt, prefix, desc, base);
@@ -85,7 +85,7 @@ static void show_entry(struct diff_options *opt, const char *prefix,
        unsigned mode;
        const char *path;
        const unsigned char *sha1 = tree_entry_extract(desc, &path, &mode);
-       int pathlen = tree_entry_len(path, sha1);
+       int pathlen = tree_entry_len(&desc->entry);
        int old_baselen = base->len;
 
        strbuf_add(base, path, pathlen);
@@ -114,12 +114,13 @@ static void show_entry(struct diff_options *opt, const char *prefix,
 }
 
 static void skip_uninteresting(struct tree_desc *t, struct strbuf *base,
-                              struct diff_options *opt, int *match)
+                              struct diff_options *opt,
+                              enum interesting *match)
 {
        while (t->size) {
                *match = tree_entry_interesting(&t->entry, base, 0, &opt->pathspec);
                if (*match) {
-                       if (*match < 0)
+                       if (*match == all_entries_not_interesting)
                                t->size = 0;
                        break;
                }
@@ -132,7 +133,8 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2,
 {
        struct strbuf base;
        int baselen = strlen(base_str);
-       int t1_match = 0, t2_match = 0;
+       enum interesting t1_match = entry_not_interesting;
+       enum interesting t2_match = entry_not_interesting;
 
        /* Enable recursion indefinitely */
        opt->pathspec.recursive = DIFF_OPT_TST(opt, RECURSIVE);
index 808bb55ba3f21d113e6358f6874e2e20cb33bbea..f82dba6a1f43bf2a259952a4fd6db94d6335deb7 100644 (file)
@@ -116,7 +116,7 @@ void setup_traverse_info(struct traverse_info *info, const char *base)
 
 char *make_traverse_path(char *path, const struct traverse_info *info, const struct name_entry *n)
 {
-       int len = tree_entry_len(n->path, n->sha1);
+       int len = tree_entry_len(n);
        int pathlen = info->pathlen;
 
        path[pathlen + len] = 0;
@@ -126,7 +126,7 @@ char *make_traverse_path(char *path, const struct traverse_info *info, const str
                        break;
                path[--pathlen] = '/';
                n = &info->name;
-               len = tree_entry_len(n->path, n->sha1);
+               len = tree_entry_len(n);
                info = info->prev;
                pathlen -= len;
        }
@@ -253,7 +253,7 @@ static void extended_entry_extract(struct tree_desc_x *t,
         * The caller wants "first" from this tree, or nothing.
         */
        path = a->path;
-       len = tree_entry_len(a->path, a->sha1);
+       len = tree_entry_len(a);
        switch (check_entry_match(first, first_len, path, len)) {
        case -1:
                entry_clear(a);
@@ -271,7 +271,7 @@ static void extended_entry_extract(struct tree_desc_x *t,
        while (probe.size) {
                entry_extract(&probe, a);
                path = a->path;
-               len = tree_entry_len(a->path, a->sha1);
+               len = tree_entry_len(a);
                switch (check_entry_match(first, first_len, path, len)) {
                case -1:
                        entry_clear(a);
@@ -344,7 +344,7 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
                unsigned long mask, dirmask;
                const char *first = NULL;
                int first_len = 0;
-               struct name_entry *e;
+               struct name_entry *e = NULL;
                int len;
 
                for (i = 0; i < n; i++) {
@@ -362,7 +362,7 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
                        e = entry + i;
                        if (!e->path)
                                continue;
-                       len = tree_entry_len(e->path, e->sha1);
+                       len = tree_entry_len(e);
                        if (!first) {
                                first = e->path;
                                first_len = len;
@@ -381,7 +381,7 @@ int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
                                /* Cull the ones that are not the earliest */
                                if (!e->path)
                                        continue;
-                               len = tree_entry_len(e->path, e->sha1);
+                               len = tree_entry_len(e);
                                if (name_compare(e->path, len, first, first_len))
                                        entry_clear(e);
                        }
@@ -434,8 +434,8 @@ static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char
                int entrylen, cmp;
 
                sha1 = tree_entry_extract(t, &entry, mode);
+               entrylen = tree_entry_len(&t->entry);
                update_tree_entry(t);
-               entrylen = tree_entry_len(entry, sha1);
                if (entrylen > namelen)
                        continue;
                cmp = memcmp(name, entry, entrylen);
@@ -465,7 +465,6 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch
        int retval;
        void *tree;
        unsigned long size;
-       struct tree_desc t;
        unsigned char root[20];
 
        tree = read_object_with_reference(tree_sha1, tree_type, &size, root);
@@ -478,8 +477,13 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch
                return 0;
        }
 
-       init_tree_desc(&t, tree, size);
-       retval = find_tree_entry(&t, name, sha1, mode);
+       if (!size) {
+               retval = -1;
+       } else {
+               struct tree_desc t;
+               init_tree_desc(&t, tree, size);
+               retval = find_tree_entry(&t, name, sha1, mode);
+       }
        free(tree);
        return retval;
 }
@@ -549,7 +553,7 @@ static int match_entry(const struct name_entry *entry, int pathlen,
        return 0;
 }
 
-static int match_dir_prefix(const char *base, int baselen,
+static int match_dir_prefix(const char *base,
                            const char *match, int matchlen)
 {
        if (strncmp(base, match, matchlen))
@@ -573,30 +577,26 @@ static int match_dir_prefix(const char *base, int baselen,
  *
  * Pre-condition: either baselen == base_offset (i.e. empty path)
  * or base[baselen-1] == '/' (i.e. with trailing slash).
- *
- * Return:
- *  - 2 for "yes, and all subsequent entries will be"
- *  - 1 for yes
- *  - zero for no
- *  - negative for "no, and no subsequent entries will be either"
  */
-int tree_entry_interesting(const struct name_entry *entry,
-                          struct strbuf *base, int base_offset,
-                          const struct pathspec *ps)
+enum interesting tree_entry_interesting(const struct name_entry *entry,
+                                       struct strbuf *base, int base_offset,
+                                       const struct pathspec *ps)
 {
        int i;
        int pathlen, baselen = base->len - base_offset;
-       int never_interesting = ps->has_wildcard ? 0 : -1;
+       int never_interesting = ps->has_wildcard ?
+               entry_not_interesting : all_entries_not_interesting;
 
        if (!ps->nr) {
                if (!ps->recursive || ps->max_depth == -1)
-                       return 2;
-               return !!within_depth(base->buf + base_offset, baselen,
-                                     !!S_ISDIR(entry->mode),
-                                     ps->max_depth);
+                       return all_entries_interesting;
+               return within_depth(base->buf + base_offset, baselen,
+                                   !!S_ISDIR(entry->mode),
+                                   ps->max_depth) ?
+                       entry_interesting : entry_not_interesting;
        }
 
-       pathlen = tree_entry_len(entry->path, entry->sha1);
+       pathlen = tree_entry_len(entry);
 
        for (i = ps->nr - 1; i >= 0; i--) {
                const struct pathspec_item *item = ps->items+i;
@@ -606,42 +606,43 @@ int tree_entry_interesting(const struct name_entry *entry,
 
                if (baselen >= matchlen) {
                        /* If it doesn't match, move along... */
-                       if (!match_dir_prefix(base_str, baselen, match, matchlen))
+                       if (!match_dir_prefix(base_str, match, matchlen))
                                goto match_wildcards;
 
                        if (!ps->recursive || ps->max_depth == -1)
-                               return 2;
+                               return all_entries_interesting;
 
-                       return !!within_depth(base_str + matchlen + 1,
-                                             baselen - matchlen - 1,
-                                             !!S_ISDIR(entry->mode),
-                                             ps->max_depth);
+                       return within_depth(base_str + matchlen + 1,
+                                           baselen - matchlen - 1,
+                                           !!S_ISDIR(entry->mode),
+                                           ps->max_depth) ?
+                               entry_interesting : entry_not_interesting;
                }
 
-               /* Does the base match? */
-               if (!strncmp(base_str, match, baselen)) {
+               /* Either there must be no base, or the base must match. */
+               if (baselen == 0 || !strncmp(base_str, match, baselen)) {
                        if (match_entry(entry, pathlen,
                                        match + baselen, matchlen - baselen,
                                        &never_interesting))
-                               return 1;
+                               return entry_interesting;
 
-                       if (ps->items[i].use_wildcard) {
+                       if (item->use_wildcard) {
                                if (!fnmatch(match + baselen, entry->path, 0))
-                                       return 1;
+                                       return entry_interesting;
 
                                /*
                                 * Match all directories. We'll try to
                                 * match files later on.
                                 */
                                if (ps->recursive && S_ISDIR(entry->mode))
-                                       return 1;
+                                       return entry_interesting;
                        }
 
                        continue;
                }
 
 match_wildcards:
-               if (!ps->items[i].use_wildcard)
+               if (!item->use_wildcard)
                        continue;
 
                /*
@@ -653,7 +654,7 @@ match_wildcards:
 
                if (!fnmatch(match, base->buf + base_offset, 0)) {
                        strbuf_setlen(base, base_offset + baselen);
-                       return 1;
+                       return entry_interesting;
                }
                strbuf_setlen(base, base_offset + baselen);
 
@@ -662,7 +663,7 @@ match_wildcards:
                 * later on.
                 */
                if (ps->recursive && S_ISDIR(entry->mode))
-                       return 1;
+                       return entry_interesting;
        }
        return never_interesting; /* No matches */
 }
index 0089581e1dd55800302799a7381d4a7ad01bd79d..2bf0db9814a5c9f77fe00c97fa4f723986a7aac1 100644 (file)
@@ -20,9 +20,9 @@ static inline const unsigned char *tree_entry_extract(struct tree_desc *desc, co
        return desc->entry.sha1;
 }
 
-static inline int tree_entry_len(const char *name, const unsigned char *sha1)
+static inline int tree_entry_len(const struct name_entry *ne)
 {
-       return (const char *)sha1 - name - 1;
+       return (const char *)ne->sha1 - ne->path - 1;
 }
 
 void update_tree_entry(struct tree_desc *);
@@ -58,9 +58,19 @@ extern void setup_traverse_info(struct traverse_info *info, const char *base);
 
 static inline int traverse_path_len(const struct traverse_info *info, const struct name_entry *n)
 {
-       return info->pathlen + tree_entry_len(n->path, n->sha1);
+       return info->pathlen + tree_entry_len(n);
 }
 
-extern int tree_entry_interesting(const struct name_entry *, struct strbuf *, int, const struct pathspec *ps);
+/* in general, positive means "kind of interesting" */
+enum interesting {
+       all_entries_not_interesting = -1, /* no, and no subsequent entries will be either */
+       entry_not_interesting = 0,
+       entry_interesting = 1,
+       all_entries_interesting = 2 /* yes, and all subsequent entries will be */
+};
+
+extern enum interesting tree_entry_interesting(const struct name_entry *,
+                                              struct strbuf *, int,
+                                              const struct pathspec *ps);
 
 #endif
diff --git a/tree.c b/tree.c
index 698ecf7af13871cf9639e969f368ba5d7b2e940a..676e9f710ca8d5a568e0c6ea2fa88132da81b48c 100644 (file)
--- a/tree.c
+++ b/tree.c
@@ -52,7 +52,8 @@ static int read_tree_1(struct tree *tree, struct strbuf *base,
        struct tree_desc desc;
        struct name_entry entry;
        unsigned char sha1[20];
-       int len, retval = 0, oldlen = base->len;
+       int len, oldlen = base->len;
+       enum interesting retval = entry_not_interesting;
 
        if (parse_tree(tree))
                return -1;
@@ -60,11 +61,11 @@ static int read_tree_1(struct tree *tree, struct strbuf *base,
        init_tree_desc(&desc, tree->buffer, tree->size);
 
        while (tree_entry(&desc, &entry)) {
-               if (retval != 2) {
+               if (retval != all_entries_interesting) {
                        retval = tree_entry_interesting(&entry, base, 0, pathspec);
-                       if (retval < 0)
+                       if (retval == all_entries_not_interesting)
                                break;
-                       if (retval == 0)
+                       if (retval == entry_not_interesting)
                                continue;
                }
 
@@ -99,7 +100,7 @@ static int read_tree_1(struct tree *tree, struct strbuf *base,
                else
                        continue;
 
-               len = tree_entry_len(entry.path, entry.sha1);
+               len = tree_entry_len(&entry);
                strbuf_add(base, entry.path, len);
                strbuf_addch(base, '/');
                retval = read_tree_1(lookup_tree(sha1),
index 670b46473883505c39c5353c65e0eeb93aec21c3..7c9ecf665d062d79e9208875d9bf2577e98f4fb2 100644 (file)
@@ -159,7 +159,7 @@ static void display_error_msgs(struct unpack_trees_options *o)
                string_list_clear(rejects, 0);
        }
        if (something_displayed)
-               printf("Aborting\n");
+               fprintf(stderr, "Aborting\n");
 }
 
 /*
@@ -446,7 +446,7 @@ static int traverse_trees_recursive(int n, unsigned long dirmask,
        newinfo.prev = info;
        newinfo.pathspec = info->pathspec;
        newinfo.name = *p;
-       newinfo.pathlen += tree_entry_len(p->path, p->sha1) + 1;
+       newinfo.pathlen += tree_entry_len(p) + 1;
        newinfo.conflicts |= df_conflicts;
 
        for (i = 0; i < n; i++, dirmask >>= 1) {
@@ -495,7 +495,7 @@ static int do_compare_entry(const struct cache_entry *ce, const struct traverse_
        ce_len -= pathlen;
        ce_name = ce->name + pathlen;
 
-       len = tree_entry_len(n->path, n->sha1);
+       len = tree_entry_len(n);
        return df_name_compare(ce_name, ce_len, S_IFREG, n->path, len, n->mode);
 }
 
@@ -626,7 +626,7 @@ static int find_cache_pos(struct traverse_info *info,
        struct unpack_trees_options *o = info->data;
        struct index_state *index = o->src_index;
        int pfxlen = info->pathlen;
-       int p_len = tree_entry_len(p->path, p->sha1);
+       int p_len = tree_entry_len(p);
 
        for (pos = o->cache_bottom; pos < index->cache_nr; pos++) {
                struct cache_entry *ce = index->cache[pos];
@@ -1091,6 +1091,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
                 */
                mark_new_skip_worktree(o->el, &o->result, CE_ADDED, CE_SKIP_WORKTREE | CE_NEW_SKIP_WORKTREE);
 
+               ret = 0;
                for (i = 0; i < o->result.cache_nr; i++) {
                        struct cache_entry *ce = o->result.cache[i];
 
@@ -1103,19 +1104,30 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
                         * correct CE_NEW_SKIP_WORKTREE
                         */
                        if (ce->ce_flags & CE_ADDED &&
-                           verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o))
-                                       return -1;
+                           verify_absent(ce, ERROR_WOULD_LOSE_UNTRACKED_OVERWRITTEN, o)) {
+                               if (!o->show_all_errors)
+                                       goto return_failed;
+                               ret = -1;
+                       }
 
                        if (apply_sparse_checkout(ce, o)) {
+                               if (!o->show_all_errors)
+                                       goto return_failed;
                                ret = -1;
-                               goto done;
                        }
                        if (!ce_skip_worktree(ce))
                                empty_worktree = 0;
 
                }
+               if (ret < 0)
+                       goto return_failed;
+               /*
+                * Sparse checkout is meant to narrow down checkout area
+                * but it does not make sense to narrow down to empty working
+                * tree. This is usually a mistake in sparse checkout rules.
+                * Do not allow users to do that.
+                */
                if (o->result.cache_nr && empty_worktree) {
-                       /* dubious---why should this fail??? */
                        ret = unpack_failed(o, "Sparse checkout leaves no entry on working directory");
                        goto done;
                }
diff --git a/url.c b/url.c
index 3e06fd34c46a00b41f4843ea87a151314ce0789c..335d97d3f74e5b7d7139e223fbe1d19837a72e81 100644 (file)
--- a/url.c
+++ b/url.c
@@ -18,35 +18,15 @@ int is_urlschemechar(int first_flag, int ch)
 
 int is_url(const char *url)
 {
-       const char *url2, *first_slash;
-
-       if (!url)
-               return 0;
-       url2 = url;
-       first_slash = strchr(url, '/');
-
-       /* Input with no slash at all or slash first can't be URL. */
-       if (!first_slash || first_slash == url)
+       /* Is "scheme" part reasonable? */
+       if (!url || !is_urlschemechar(1, *url++))
                return 0;
-       /* Character before must be : and next must be /. */
-       if (first_slash[-1] != ':' || first_slash[1] != '/')
-               return 0;
-       /* There must be something before the :// */
-       if (first_slash == url + 1)
-               return 0;
-       /*
-        * Check all characters up to first slash - 1. Only alphanum
-        * is allowed.
-        */
-       url2 = url;
-       while (url2 < first_slash - 1) {
-               if (!is_urlschemechar(url2 == url, (unsigned char)*url2))
+       while (*url && *url != ':') {
+               if (!is_urlschemechar(0, *url++))
                        return 0;
-               url2++;
        }
-
-       /* Valid enough. */
-       return 1;
+       /* We've seen "scheme"; we want colon-slash-slash */
+       return (url[0] == ':' && url[1] == '/' && url[2] == '/');
 }
 
 static int url_decode_char(const char *q)
@@ -68,18 +48,20 @@ static int url_decode_char(const char *q)
        return val;
 }
 
-static char *url_decode_internal(const char **query, const char *stop_at,
-                                struct strbuf *out, int decode_plus)
+static char *url_decode_internal(const char **query, int len,
+                                const char *stop_at, struct strbuf *out,
+                                int decode_plus)
 {
        const char *q = *query;
 
-       do {
+       while (len) {
                unsigned char c = *q;
 
                if (!c)
                        break;
                if (stop_at && strchr(stop_at, c)) {
                        q++;
+                       len--;
                        break;
                }
 
@@ -88,6 +70,7 @@ static char *url_decode_internal(const char **query, const char *stop_at,
                        if (0 <= val) {
                                strbuf_addch(out, val);
                                q += 3;
+                               len -= 3;
                                continue;
                        }
                }
@@ -97,34 +80,41 @@ static char *url_decode_internal(const char **query, const char *stop_at,
                else
                        strbuf_addch(out, c);
                q++;
-       } while (1);
+               len--;
+       }
        *query = q;
        return strbuf_detach(out, NULL);
 }
 
 char *url_decode(const char *url)
+{
+       return url_decode_mem(url, strlen(url));
+}
+
+char *url_decode_mem(const char *url, int len)
 {
        struct strbuf out = STRBUF_INIT;
-       const char *colon = strchr(url, ':');
+       const char *colon = memchr(url, ':', len);
 
        /* Skip protocol part if present */
        if (colon && url < colon) {
                strbuf_add(&out, url, colon - url);
+               len -= colon - url;
                url = colon;
        }
-       return url_decode_internal(&url, NULL, &out, 0);
+       return url_decode_internal(&url, len, NULL, &out, 0);
 }
 
 char *url_decode_parameter_name(const char **query)
 {
        struct strbuf out = STRBUF_INIT;
-       return url_decode_internal(query, "&=", &out, 1);
+       return url_decode_internal(query, -1, "&=", &out, 1);
 }
 
 char *url_decode_parameter_value(const char **query)
 {
        struct strbuf out = STRBUF_INIT;
-       return url_decode_internal(query, "&", &out, 1);
+       return url_decode_internal(query, -1, "&", &out, 1);
 }
 
 void end_url_with_slash(struct strbuf *buf, const char *url)
diff --git a/url.h b/url.h
index 7100e3215a97b234c8ab93b76f7f86518f7c3382..abdaf6fa30b68767f48b056c977e498f9cfe7de2 100644 (file)
--- a/url.h
+++ b/url.h
@@ -4,6 +4,7 @@
 extern int is_url(const char *url);
 extern int is_urlschemechar(int first_flag, int ch);
 extern char *url_decode(const char *url);
+extern char *url_decode_mem(const char *url, int len);
 extern char *url_decode_parameter_name(const char **query);
 extern char *url_decode_parameter_value(const char **query);
 
index dce7128daf8487a61e8d2f35cf15fca618964f86..be389dc9bf5161c31be29e3a72264fd6120a0bbc 100644 (file)
--- a/walker.c
+++ b/walker.c
@@ -190,7 +190,7 @@ static int interpret_target(struct walker *walker, char *target, unsigned char *
 {
        if (!get_sha1_hex(target, sha1))
                return 0;
-       if (!check_ref_format(target)) {
+       if (!check_refname_format(target, 0)) {
                struct ref *ref = alloc_ref(target);
                if (!walker->fetch_ref(walker, ref)) {
                        hashcpy(sha1, ref->old_sha1);
index 8836a527d0b1980bd4ebdd50b3225f7ce37ccf79..70fdb76ff2b5100c6d20f9f3b758e1ac8df314e1 100644 (file)
@@ -396,7 +396,7 @@ static void wt_status_collect_changes_worktree(struct wt_status *s)
        if (s->ignore_submodule_arg) {
                DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG);
                handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg);
-    }
+       }
        rev.diffopt.format_callback = wt_status_collect_changed_cb;
        rev.diffopt.format_callback_data = s;
        init_pathspec(&rev.prune_data, s->pathspec);
index 4beb10c678702a34b14914f8b292dd65b5c273cd..00d36c3ac7a7642831d2ffc49647caf77a4d066c 100644 (file)
@@ -43,6 +43,7 @@ extern "C" {
 
 #define XDL_EMIT_FUNCNAMES (1 << 0)
 #define XDL_EMIT_COMMON (1 << 1)
+#define XDL_EMIT_FUNCCONTEXT (1 << 2)
 
 #define XDL_MMB_READONLY (1 << 0)
 
index 277e2eec5b4cced5882f37ac42af7f22efe92902..2e669c3e2570332ee50a72e06a77ad4a356ecfd6 100644 (file)
@@ -100,14 +100,40 @@ static int xdl_emit_common(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
        return 0;
 }
 
+struct func_line {
+       long len;
+       char buf[80];
+};
+
+static long get_func_line(xdfenv_t *xe, xdemitconf_t const *xecfg,
+                         struct func_line *func_line, long start, long limit)
+{
+       find_func_t ff = xecfg->find_func ? xecfg->find_func : def_ff;
+       long l, size, step = (start > limit) ? -1 : 1;
+       char *buf, dummy[1];
+
+       buf = func_line ? func_line->buf : dummy;
+       size = func_line ? sizeof(func_line->buf) : sizeof(dummy);
+
+       for (l = start; l != limit && 0 <= l && l < xe->xdf1.nrec; l += step) {
+               const char *rec;
+               long reclen = xdl_get_rec(&xe->xdf1, l, &rec);
+               long len = ff(rec, reclen, buf, size, xecfg->find_func_priv);
+               if (len >= 0) {
+                       if (func_line)
+                               func_line->len = len;
+                       return l;
+               }
+       }
+       return -1;
+}
+
 int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
                  xdemitconf_t const *xecfg) {
        long s1, s2, e1, e2, lctx;
        xdchange_t *xch, *xche;
-       char funcbuf[80];
-       long funclen = 0;
        long funclineprev = -1;
-       find_func_t ff = xecfg->find_func ?  xecfg->find_func : def_ff;
+       struct func_line func_line = { 0 };
 
        if (xecfg->flags & XDL_EMIT_COMMON)
                return xdl_emit_common(xe, xscr, ecb, xecfg);
@@ -118,6 +144,17 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
                s1 = XDL_MAX(xch->i1 - xecfg->ctxlen, 0);
                s2 = XDL_MAX(xch->i2 - xecfg->ctxlen, 0);
 
+               if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) {
+                       long fs1 = get_func_line(xe, xecfg, NULL, xch->i1, -1);
+                       if (fs1 < 0)
+                               fs1 = 0;
+                       if (fs1 < s1) {
+                               s2 -= s1 - fs1;
+                               s1 = fs1;
+                       }
+               }
+
+ again:
                lctx = xecfg->ctxlen;
                lctx = XDL_MIN(lctx, xe->xdf1.nrec - (xche->i1 + xche->chg1));
                lctx = XDL_MIN(lctx, xe->xdf2.nrec - (xche->i2 + xche->chg2));
@@ -125,27 +162,43 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
                e1 = xche->i1 + xche->chg1 + lctx;
                e2 = xche->i2 + xche->chg2 + lctx;
 
+               if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) {
+                       long fe1 = get_func_line(xe, xecfg, NULL,
+                                                xche->i1 + xche->chg1,
+                                                xe->xdf1.nrec);
+                       if (fe1 < 0)
+                               fe1 = xe->xdf1.nrec;
+                       if (fe1 > e1) {
+                               e2 += fe1 - e1;
+                               e1 = fe1;
+                       }
+
+                       /*
+                        * Overlap with next change?  Then include it
+                        * in the current hunk and start over to find
+                        * its new end.
+                        */
+                       if (xche->next) {
+                               long l = xche->next->i1;
+                               if (l <= e1 ||
+                                   get_func_line(xe, xecfg, NULL, l, e1) < 0) {
+                                       xche = xche->next;
+                                       goto again;
+                               }
+                       }
+               }
+
                /*
                 * Emit current hunk header.
                 */
 
                if (xecfg->flags & XDL_EMIT_FUNCNAMES) {
-                       long l;
-                       for (l = s1 - 1; l >= 0 && l > funclineprev; l--) {
-                               const char *rec;
-                               long reclen = xdl_get_rec(&xe->xdf1, l, &rec);
-                               long newfunclen = ff(rec, reclen, funcbuf,
-                                                    sizeof(funcbuf),
-                                                    xecfg->find_func_priv);
-                               if (newfunclen >= 0) {
-                                       funclen = newfunclen;
-                                       break;
-                               }
-                       }
+                       get_func_line(xe, xecfg, &func_line,
+                                     s1 - 1, funclineprev);
                        funclineprev = s1 - 1;
                }
                if (xdl_emit_hunk_hdr(s1 + 1, e1 - s1, s2 + 1, e2 - s2,
-                                     funcbuf, funclen, ecb) < 0)
+                                     func_line.buf, func_line.len, ecb) < 0)
                        return -1;
 
                /*
index 5a33d1a86964472a82a63f18ab3c9b4da9b23165..e419f4f726019a5b0365c589285439fb3bfb8db2 100644 (file)
@@ -383,7 +383,7 @@ static int xdl_clean_mmatch(char const *dis, long i, long s, long e) {
  * might be potentially discarded if they happear in a run of discardable.
  */
 static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) {
-       long i, nm, nreff;
+       long i, nm, nreff, mlim;
        xrecord_t **recs;
        xdlclass_t *rcrec;
        char *dis, *dis1, *dis2;
@@ -396,16 +396,20 @@ static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xd
        dis1 = dis;
        dis2 = dis1 + xdf1->nrec + 1;
 
+       if ((mlim = xdl_bogosqrt(xdf1->nrec)) > XDL_MAX_EQLIMIT)
+               mlim = XDL_MAX_EQLIMIT;
        for (i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; i <= xdf1->dend; i++, recs++) {
                rcrec = cf->rcrecs[(*recs)->ha];
                nm = rcrec ? rcrec->len2 : 0;
-               dis1[i] = (nm == 0) ? 0: 1;
+               dis1[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1;
        }
 
+       if ((mlim = xdl_bogosqrt(xdf2->nrec)) > XDL_MAX_EQLIMIT)
+               mlim = XDL_MAX_EQLIMIT;
        for (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; i <= xdf2->dend; i++, recs++) {
                rcrec = cf->rcrecs[(*recs)->ha];
                nm = rcrec ? rcrec->len1 : 0;
-               dis2[i] = (nm == 0) ? 0: 1;
+               dis2[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1;
        }
 
        for (nreff = 0, i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart];