Code

Merge branch 'mo/bin-wrappers'
authorJunio C Hamano <gitster@pobox.com>
Thu, 7 Jan 2010 23:35:52 +0000 (15:35 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 7 Jan 2010 23:35:52 +0000 (15:35 -0800)
* mo/bin-wrappers:
  INSTALL: document a simpler way to run uninstalled builds
  run test suite without dashed git-commands in PATH
  build dashless "bin-wrappers" directory similar to installed bindir

146 files changed:
.gitattributes
Documentation/.gitignore
Documentation/Makefile
Documentation/RelNotes-1.6.5.4.txt [new file with mode: 0644]
Documentation/RelNotes-1.6.5.5.txt [new file with mode: 0644]
Documentation/RelNotes-1.6.5.6.txt [new file with mode: 0644]
Documentation/RelNotes-1.6.5.7.txt [new file with mode: 0644]
Documentation/RelNotes-1.6.6.1.txt [new file with mode: 0644]
Documentation/RelNotes-1.6.6.txt
Documentation/RelNotes-1.7.0.txt [new file with mode: 0644]
Documentation/SubmittingPatches
Documentation/config.txt
Documentation/git-archive.txt
Documentation/git-bisect-lk2009.txt [new file with mode: 0644]
Documentation/git-bisect.txt
Documentation/git-bundle.txt
Documentation/git-clone.txt
Documentation/git-commit.txt
Documentation/git-fast-import.txt
Documentation/git-mailinfo.txt
Documentation/git-remote-helpers.txt
Documentation/git-send-email.txt
Documentation/git-status.txt
Documentation/git-upload-pack.txt
Documentation/git.txt
Documentation/gitattributes.txt
Documentation/gitcore-tutorial.txt
Documentation/githooks.txt
Documentation/manpage-base-url.xsl.in [new file with mode: 0644]
Documentation/pretty-formats.txt
Documentation/technical/api-hash.txt
Documentation/technical/api-strbuf.txt
GIT-VERSION-GEN
Makefile
RelNotes
bisect.c
builtin-archive.c
builtin-blame.c
builtin-branch.c
builtin-clone.c
builtin-commit.c
builtin-count-objects.c
builtin-fetch.c
builtin-help.c
builtin-log.c
builtin-ls-remote.c
builtin-mailinfo.c
builtin-merge.c
builtin-mv.c
builtin-push.c
builtin-receive-pack.c
builtin-remote.c
builtin-rerere.c
builtin-reset.c
builtin-rev-list.c
builtin-shortlog.c
combine-diff.c
command-list.txt
configure.ac
contrib/completion/git-completion.bash
diff-lib.c
diff.c
diff.h
diffcore-delta.c
fast-import.c
git-add--interactive.perl
git-am.sh
git-gui/git-gui.sh
git-gui/lib/blame.tcl
git-gui/lib/database.tcl
git-gui/lib/remote_branch_delete.tcl
git-instaweb.sh
git-pull.sh
git-rebase--interactive.sh
git-send-email.perl
git-stash.sh
git-svn.perl
git_remote_helpers/.gitignore [new file with mode: 0644]
git_remote_helpers/Makefile [new file with mode: 0644]
git_remote_helpers/__init__.py [new file with mode: 0644]
git_remote_helpers/git/__init__.py [new file with mode: 0644]
git_remote_helpers/git/git.py [new file with mode: 0644]
git_remote_helpers/setup.py [new file with mode: 0644]
git_remote_helpers/util.py [new file with mode: 0644]
gitk-git/gitk
gitk-git/po/ja.po [new file with mode: 0644]
gitweb/README
gitweb/gitweb.css
gitweb/gitweb.js [new file with mode: 0644]
gitweb/gitweb.perl
help.c
http.c
merge-recursive.c
merge-recursive.h
notes.c
pretty.c
read-cache.c
remote.c
remote.h
revision.c
setup.c
t/t1200-tutorial.sh
t/t1501-worktree.sh
t/t3304-notes-mixed.sh [new file with mode: 0755]
t/t3701-add-interactive.sh
t/t4014-format-patch.sh
t/t4015-diff-whitespace.sh
t/t4019-diff-wserror.sh
t/t4026-color.sh
t/t4034-diff-words.sh
t/t4040-whitespace-status.sh [new file with mode: 0755]
t/t4201-shortlog.sh
t/t5400-send-pack.sh
t/t5401-update-hooks.sh
t/t5403-post-checkout-hook.sh
t/t5405-send-pack-rewind.sh
t/t5501-post-upload-pack.sh [deleted file]
t/t5516-fetch-push.sh
t/t5517-push-mirror.sh
t/t5522-pull-symlink.sh
t/t5701-clone-local.sh
t/t6006-rev-list-format.sh
t/t6030-bisect-porcelain.sh
t/t6040-tracking-info.sh
t/t7060-wtstatus.sh
t/t7102-reset.sh
t/t7103-reset-bare.sh
t/t7506-status-submodule.sh
t/t7508-status.sh
t/t7509-commit.sh [new file with mode: 0755]
t/t7604-merge-custom-message.sh
t/t8003-blame.sh
t/t9001-send-email.sh
t/t9146-git-svn-empty-dirs.sh
t/t9151-svn-mergeinfo.sh
t/t9151/make-svnmerge-dump
t/t9151/svn-mergeinfo.dump
t/t9152-svn-empty-dirs-after-gc.sh [new file with mode: 0755]
t/test-lib.sh
transport-helper.c
transport.c
transport.h
tree-diff.c
upload-pack.c
wt-status.c
wt-status.h

index 0636deea9357d2f1e9331119f02fb75fb6b15393..5e98806c6cc246acef5f539ae191710a0c06ad3f 100644 (file)
@@ -1,2 +1,3 @@
 * whitespace=!indent,trail,space
 *.[ch] whitespace=indent,trail,space
+*.sh whitespace=indent,trail,space
index d8edd904065fbc4bd06365ce378f57d4cd8f9f0d..1c3a9fead579a9b52037f1bbe245998db8a2f40b 100644 (file)
@@ -8,3 +8,4 @@ gitman.info
 howto-index.txt
 doc.dep
 cmds-*.txt
+manpage-base-url.xsl
index cd5b4396dbc174edbba3eb42bb285eefc57f9caf..4797b2dc3522ccd2050ddefe990268fddb6b8a47 100644 (file)
@@ -17,6 +17,7 @@ DOC_HTML=$(MAN_HTML)
 ARTICLES = howto-index
 ARTICLES += everyday
 ARTICLES += git-tools
+ARTICLES += git-bisect-lk2009
 # with their own formatting rules.
 SP_ARTICLES = howto/revert-branch-rebase howto/using-merge-subtree user-manual
 API_DOCS = $(patsubst %.txt,%,$(filter-out technical/api-index-skel.txt technical/api-index.txt, $(wildcard technical/api-*.txt)))
@@ -103,6 +104,17 @@ ifdef DOCBOOK_SUPPRESS_SP
 XMLTO_EXTRA += -m manpage-suppress-sp.xsl
 endif
 
+# Newer DocBook stylesheet emits warning cruft in the output when
+# this is not set, and if set it shows an absolute link.  Older
+# stylesheets simply ignore this parameter.
+#
+# Distros may want to use MAN_BASE_URL=file:///path/to/git/docs/
+# or similar.
+ifndef MAN_BASE_URL
+MAN_BASE_URL = file://$(htmldir)/
+endif
+XMLTO_EXTRA += -m manpage-base-url.xsl
+
 # If your target system uses GNU groff, it may try to render
 # apostrophes as a "pretty" apostrophe using unicode.  This breaks
 # cut&paste, so you should set GNU_ROFF to force them to be ASCII
@@ -230,6 +242,7 @@ clean:
        $(RM) howto-index.txt howto/*.html doc.dep
        $(RM) technical/api-*.html technical/api-index.txt
        $(RM) $(cmds_txt) *.made
+       $(RM) manpage-base-url.xsl
 
 $(MAN_HTML): %.html : %.txt
        $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
@@ -237,7 +250,10 @@ $(MAN_HTML): %.html : %.txt
                $(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) -o $@+ $< && \
        mv $@+ $@
 
-%.1 %.5 %.7 : %.xml
+manpage-base-url.xsl: manpage-base-url.xsl.in
+       sed "s|@@MAN_BASE_URL@@|$(MAN_BASE_URL)|" $< > $@
+
+%.1 %.5 %.7 : %.xml manpage-base-url.xsl
        $(QUIET_XMLTO)$(RM) $@ && \
        xmlto -m $(MANPAGE_XSL) $(XMLTO_EXTRA) man $<
 
diff --git a/Documentation/RelNotes-1.6.5.4.txt b/Documentation/RelNotes-1.6.5.4.txt
new file mode 100644 (file)
index 0000000..e42f8b2
--- /dev/null
@@ -0,0 +1,32 @@
+Git v1.6.5.4 Release Notes
+==========================
+
+Fixes since v1.6.5.3
+--------------------
+
+ * "git help" (without argument) used to check if you are in a directory
+   under git control. There was no breakage in behaviour per-se, but this
+   was unnecessary.
+
+ * "git prune-packed" gave progress output even when its standard error is
+   not connected to a terminal; this caused cron jobs that run it to
+   produce crufts.
+
+ * "git pack-objects --all-progress" is an option to ask progress output
+   from write-object phase _if_ progress output were to be produced, and
+   shouldn't have forced the progress output.
+
+ * "git apply -p<n> --directory=<elsewhere>" did not work well for a
+   non-default value of n.
+
+ * "git merge foo HEAD" was misparsed as an old-style invocation of the
+   command and produced a confusing error message.  As it does not specify
+   any other branch to merge, it shouldn't be mistaken as such.  We will
+   remove the old style "git merge <message> HEAD <commit>..."  syntax in
+   future versions, but not in this release,
+
+ * "git merge -m <message> <branch>..." added the standard merge message
+   on its own after user-supplied message, which should have overrided the
+   standard one.
+
+Other minor documentation updates are included.
diff --git a/Documentation/RelNotes-1.6.5.5.txt b/Documentation/RelNotes-1.6.5.5.txt
new file mode 100644 (file)
index 0000000..ecfc57d
--- /dev/null
@@ -0,0 +1,49 @@
+Git v1.6.5.5 Release Notes
+==========================
+
+Fixes since v1.6.5.4
+--------------------
+
+ * Manual pages can be formatted with older xmlto again.
+
+ * GREP_OPTIONS exported from user's environment could have broken
+   our scripted commands.
+
+ * In configuration files, a few variables that name paths can begin with
+   ~/ and ~username/ and they are expanded as expected.  This is not a
+   bugfix but 1.6.6 will have this and without backporting users cannot
+   easily use the same ~/.gitconfig across versions.
+
+ * "git diff -B -M" did the same computation to hash lines of contents
+   twice, and held onto memory after it has used the data in it
+   unnecessarily before it freed.
+
+ * "git diff -B" and "git diff --dirstat" was not counting newly added
+   contents correctly.
+
+ * "git format-patch revisions... -- path" issued an incorrect error
+   message that suggested to use "--" on the command line when path
+   does not exist in the current work tree (it is a separate matter if
+   it makes sense to limit format-patch with pathspecs like that
+   without using the --full-diff option).
+
+ * "git grep -F -i StRiNg" did not work as expected.
+
+ * Enumeration of available merge strategies iterated over the list of
+   commands in a wrong way, sometimes producing an incorrect result.
+
+ * "git shortlog" did not honor the "encoding" header embedded in the
+   commit object like "git log" did.
+
+ * Reading progress messages that come from the remote side while running
+   "git pull" is given precedence over reading the actual pack data to
+   prevent garbled progress message on the user's terminal.
+
+ * "git rebase" got confused when the log message began with certain
+   strings that looked like Subject:, Date: or From: header.
+
+ * "git reset" accidentally run in .git/ directory checked out the
+   work tree contents in there.
+
+
+Other minor documentation updates are included.
diff --git a/Documentation/RelNotes-1.6.5.6.txt b/Documentation/RelNotes-1.6.5.6.txt
new file mode 100644 (file)
index 0000000..a9eaf76
--- /dev/null
@@ -0,0 +1,23 @@
+Git v1.6.5.6 Release Notes
+==========================
+
+Fixes since v1.6.5.5
+--------------------
+
+ * "git add -p" had a regression since v1.6.5.3 that broke deletion of
+   non-empty files.
+
+ * "git archive -o o.zip -- Makefile" produced an archive in o.zip
+   but in POSIX tar format.
+
+ * Error message given to "git pull --rebase" when the user didn't give
+   enough clue as to what branch to integrate with still talked about
+   "merging with" the branch.
+
+ * Error messages given by "git merge" when the merge resulted in a
+   fast-forward still were in plumbing lingo, even though in v1.6.5
+   we reworded messages in other cases.
+
+ * The post-upload-hook run by upload-pack in response to "git fetch" has
+   been removed, due to security concerns (the hook first appeared in
+   1.6.5).
diff --git a/Documentation/RelNotes-1.6.5.7.txt b/Documentation/RelNotes-1.6.5.7.txt
new file mode 100644 (file)
index 0000000..5b49ea5
--- /dev/null
@@ -0,0 +1,19 @@
+Git v1.6.5.7 Release Notes
+==========================
+
+Fixes since v1.6.5.6
+--------------------
+
+* If a user specifies a color for a <slot> (i.e. a class of things to show
+  in a particular color) that is known only by newer versions of git
+  (e.g. "color.diff.func" was recently added for upcoming 1.6.6 release),
+  an older version of git should just ignore them.  Instead we diagnosed
+  it as an error.
+
+* With help.autocorrect set to non-zero value, the logic to guess typoes
+  in the subcommand name misfired and ran a random nonsense command.
+
+* If a command is run with an absolute path as a pathspec inside a bare
+  repository, e.g. "rev-list HEAD -- /home", the code tried to run
+  strlen() on NULL, which is the result of get_git_work_tree(), and
+  segfaulted.
diff --git a/Documentation/RelNotes-1.6.6.1.txt b/Documentation/RelNotes-1.6.6.1.txt
new file mode 100644 (file)
index 0000000..4c88beb
--- /dev/null
@@ -0,0 +1,15 @@
+Git v1.6.6.1 Release Notes
+==========================
+
+Fixes since v1.6.6
+------------------
+
+ * http-backend was not listed in the command list in the documentation.
+
+Other minor documentation updates are included.
+
+--
+exec >/var/tmp/1
+O=v1.6.6-4-gd828fdb
+echo O=$(git describe maint)
+git shortlog --no-merges $O..maint
index 6163b4aad33ba07f41546a2464ea542e434c01a6..04e205c457cd11cba91327ad1e8b2c023743ac74 100644 (file)
@@ -22,25 +22,29 @@ These changes were discussed long time ago and existing behaviours have
 been identified as more problematic to the userbase than keeping them for
 the sake of backward compatibility.
 
-When necessary, transition strategy for existing users has been designed
+When necessary, transition strategy for existing users has been designed
 not to force them running around setting configuration variables and
-updating their scripts in order to keep the traditional behaviour on the
-day their sysadmin decides to install the new version of git.  When we
-switched from "git-foo" to "git foo" in 1.6.0, even though the change had
-been advertised and the transition guide had been provided for a very long
-time, the users procrastinated during the entire transtion period, and
-ended up panicking on the day their sysadmins updated their git.
-
-For changes decided to be in 1.7.0, we have been much louder to strongly
-discourage such procrastination.  If you have been using recent versions
-of git, you would have already seen warnings issued when you exercised
-features whose behaviour will change, with the instruction on how to keep
-the existing behaviour if you choose to.  You hopefully should be well
-prepared already.
-
-Of course, we have also given "this and that will change in 1.7.0; prepare
-yourselves" warnings in the release notes and announcement messages.
-Let's see how well users will fare this time.
+updating their scripts in order to either keep the traditional behaviour
+or adjust to the new behaviour, on the day their sysadmin decides to install
+the new version of git.  When we switched from "git-foo" to "git foo" in
+1.6.0, even though the change had been advertised and the transition
+guide had been provided for a very long time, the users procrastinated
+during the entire transtion period, and ended up panicking on the day
+their sysadmins updated their git installation.  We are trying to avoid
+repeating that unpleasantness in the 1.7.0 release.
+
+For changes decided to be in 1.7.0, commands that will be affected
+have been much louder to strongly discourage such procrastination, and
+they continue to be in this release.  If you have been using recent
+versions of git, you would have seen warnings issued when you used
+features whose behaviour will change, with a clear instruction on how
+to keep the existing behaviour if you want to.  You hopefully are
+already well prepared.
+
+Of course, we have also been giving "this and that will change in
+1.7.0; prepare yourselves" warnings in the release notes and
+announcement messages for the past few releases.  Let's see how well
+users will fare this time.
 
  * "git push" into a branch that is currently checked out (i.e. pointed by
    HEAD in a repository that is not bare) will be refused by default.
@@ -52,10 +56,10 @@ Let's see how well users will fare this time.
    Setting the configuration variables receive.denyCurrentBranch and
    receive.denyDeleteCurrent to 'ignore' in the receiving repository
    can be used to override these safety features.  Versions of git
-   since 1.6.2 have issued a loud warning when you tried to do them
-   without setting the configuration, so repositories of people who
-   still need to be able to perform such a push should already been
-   future proofed.
+   since 1.6.2 have issued a loud warning when you tried to do these
+   operations without setting the configuration, so repositories of
+   people who still need to be able to perform such a push should
+   already have been future proofed.
 
    Please refer to:
 
@@ -66,11 +70,18 @@ Let's see how well users will fare this time.
    transition process that already took place so far.
 
  * "git send-email" will not make deep threads by default when sending a
-   patch series with more than two messages.  All messages will be sent as
-   a reply to the first message, i.e. cover letter.  It has been possible
-   to configure send-email to do this by setting sendemail.chainreplyto
-   configuration variable to false.  The only thing the new release will
-   do is to change the default when you haven't configured that variable.
+   patch series with more than two messages.  All messages will be sent
+   as a reply to the first message, i.e. cover letter.  Git 1.6.6 (this
+   release) will issue a warning about the upcoming default change, when
+   it uses the traditional "deep threading" behaviour as the built-in
+   default.  To squelch the warning but still use the "deep threading"
+   behaviour, give --chain-reply-to option or set sendemail.chainreplyto
+   to true.
+
+   It has been possible to configure send-email to send "shallow thread"
+   by setting sendemail.chainreplyto configuration variable to false.
+   The only thing 1.7.0 release will do is to change the default when
+   you haven't configured that variable.
 
  * "git status" will not be "git commit --dry-run".  This change does not
    affect you if you run the command without pathspec.
@@ -98,9 +109,15 @@ Updates since v1.6.5
 
 (subsystems)
 
- * various git-gui updates including new translations, wm states, etc.
+ * various gitk updates including use of themed widgets under Tk 8.5,
+   Japanese translation, a fix to a bug when running "gui blame" from
+   a subdirectory, etc.
 
- * git-svn updates.
+ * various git-gui updates including new translations, wm states fixes,
+   Tk bug workaround after quitting, improved heuristics to trigger gc,
+   etc.
+
+ * various git-svn updates.
 
  * "git fetch" over http learned a new mode that is different from the
    traditional "dumb commit walker".
@@ -129,11 +146,19 @@ Updates since v1.6.5
    is only one remote tracking branch "frotz" is taken as a request to
    start the named branch at the corresponding remote tracking branch.
 
+ * "git commit -c/-C/--amend" can be told with a new "--reset-author" option
+   to ignore authorship information in the commit it is taking the message
+   from.
+
  * "git describe" can be told to add "-dirty" suffix with "--dirty" option.
 
  * "git diff" learned --submodule option to show a list of one-line logs
    instead of differences between the commit object names.
 
+ * "git diff" learned to honor diff.color.func configuration to paint
+   function name hint printed on the hunk header "@@ -j,k +l,m @@" line
+   in the specified color.
+
  * "git fetch" learned --all and --multiple options, to run fetch from
    many repositories, and --prune option to remove remote tracking
    branches that went stale.  These make "git remote update" and "git
@@ -172,25 +197,28 @@ Updates since v1.6.5
    the shell, which is done by "edit" to give an opportunity to tweak the
    contents.
 
+ * "git send-email" can be told with "--envelope-sender=auto" to use the
+   same address as "From:" address as the envelope sender address.
+
+ * "git send-email" will issue a warning when it defaults to the
+   --chain-reply-to behaviour without being told by the user and
+   instructs to prepare for the change of the default in 1.7.0 release.
+
  * In "git submodule add <repository> <path>", <path> is now optional and
    inferred from <repository> the same way "git clone <repository>" does.
 
  * "git svn" learned to read SVN 1.5+ and SVK merge tickets.
 
- * Author names shown in gitweb output are links to search commits by the
-   author.
+ * "git svn" learned to recreate empty directories tracked only by SVN.
 
+ * "gitweb" can optionally render its "blame" output incrementally (this
+   requires JavaScript on the client side).
 
-(developers)
+ * Author names shown in gitweb output are links to search commits by the
+   author.
 
 Fixes since v1.6.5
 ------------------
 
 All of the fixes in v1.6.5.X maintenance series are included in this
 release, unless otherwise noted.
-
----
-exec >/var/tmp/1
-echo O=$(git describe master)
-O=v1.6.6-rc0-62-g7fc9d15
-git shortlog --no-merges $O..master --not maint
diff --git a/Documentation/RelNotes-1.7.0.txt b/Documentation/RelNotes-1.7.0.txt
new file mode 100644 (file)
index 0000000..d519d06
--- /dev/null
@@ -0,0 +1,64 @@
+Git v1.7.0 Release Notes
+========================
+
+Notes on behaviour change
+-------------------------
+
+ * "git push" into a branch that is currently checked out (i.e. pointed by
+   HEAD in a repository that is not bare) is refused by default.
+
+   Similarly, "git push $there :$killed" to delete the branch $killed
+   in a remote repository $there, when $killed branch is the current
+   branch pointed at by its HEAD, will be refused by default.
+
+   Setting the configuration variables receive.denyCurrentBranch and
+   receive.denyDeleteCurrent to 'ignore' in the receiving repository
+   can be used to override these safety features.
+
+ * "git send-email" does not make deep threads by default when sending a
+   patch series with more than two messages.  All messages will be sent
+   as a reply to the first message, i.e. cover letter.
+
+   It has been possible to configure send-email to send "shallow thread"
+   by setting sendemail.chainreplyto configuration variable to false.  The
+   only thing this release does is to change the default when you haven't
+   configured that variable.
+
+ * "git status" is not "git commit --dry-run" anymore.  This change does
+   not affect you if you run the command without pathspec.
+
+ * "git diff" traditionally treated various "ignore whitespace" options
+   only as a way to filter the patch output.  "git diff --exit-code -b"
+   exited with non-zero status even if all changes were about changing the
+   ammount of whitespace and nothing else.  and "git diff -b" showed the
+   "diff --git" header line for such a change without patch text.
+
+   In this release, the "ignore whitespaces" options affect the semantics
+   of the diff operation.  A change that does not affect anything but
+   whitespaces is reported with zero exit status when run with
+   --exit-code, and there is no "diff --git" header for such a change.
+
+
+Updates since v1.6.6
+--------------------
+
+(subsystems)
+
+(portability)
+
+(performance)
+
+(usability, bells and whistles)
+
+
+Fixes since v1.6.6
+------------------
+
+All of the fixes in v1.6.6.X maintenance series are included in this
+release, unless otherwise noted.
+
+--
+exec >/var/tmp/1
+O=v1.6.6-27-g648f407
+echo O=$(git describe master)
+git shortlog --no-merges $O..master ^maint
index 76fc84d8780762e083cd4ca584b9d783b8c0cd81..c686f8646b465860c8a096241797709366cc4dc1 100644 (file)
@@ -279,6 +279,20 @@ from the list and queue it to 'pu', in order to make it easier for
 people play with it without having to pick up and apply the patch to
 their trees themselves.
 
+------------------------------------------------
+Know the status of your patch after submission
+
+* You can use Git itself to find out when your patch is merged in
+  master. 'git pull --rebase' will automatically skip already-applied
+  patches, and will let you know. This works only if you rebase on top
+  of the branch in which your patch has been merged (i.e. it will not
+  tell you if your patch is merged in pu if you rebase on top of
+  master).
+
+* Read the git mailing list, the maintainer regularly posts messages
+  entitled "What's cooking in git.git" and "What's in git.git" giving
+  the status of various proposed changes.
+
 ------------------------------------------------
 MUA specific hints
 
index a8e0876a2add7bb53d3194b1f5e0a2031dd19a42..23a965eed7286ba9f92417bdde2f6dd1a0c57bbd 100644 (file)
@@ -297,17 +297,24 @@ false), while all other repositories are assumed to be bare (bare
 = true).
 
 core.worktree::
-       Set the path to the working tree.  The value will not be
-       used in combination with repositories found automatically in
-       a .git directory (i.e. $GIT_DIR is not set).
+       Set the path to the root of the work tree.
        This can be overridden by the GIT_WORK_TREE environment
        variable and the '--work-tree' command line option. It can be
-       a absolute path or relative path to the directory specified by
-       --git-dir or GIT_DIR.
-       Note: If --git-dir or GIT_DIR are specified but none of
+       an absolute path or a relative path to the .git directory,
+       either specified by --git-dir or GIT_DIR, or automatically
+       discovered.
+       If --git-dir or GIT_DIR are specified but none of
        --work-tree, GIT_WORK_TREE and core.worktree is specified,
-       the current working directory is regarded as the top directory
-       of your working tree.
+       the current working directory is regarded as the root of the
+       work tree.
++
+Note that this variable is honored even when set in a configuration
+file in a ".git" subdirectory of a directory, and its value differs
+from the latter directory (e.g. "/path/to/.git/config" has
+core.worktree set to "/different/path"), which is most likely a
+misconfiguration.  Running git commands in "/path/to" directory will
+still use "/different/path" as the root of the work tree and can cause
+great confusion to the users.
 
 core.logAllRefUpdates::
        Enable the reflog. Updates to a ref <ref> is logged to the file
@@ -635,10 +642,10 @@ color.diff.<slot>::
        Use customized color for diff colorization.  `<slot>` specifies
        which part of the patch to use the specified color, and is one
        of `plain` (context text), `meta` (metainformation), `frag`
-       (hunk header), `old` (removed lines), `new` (added lines),
-       `commit` (commit headers), or `whitespace` (highlighting
-       whitespace errors). The values of these variables may be specified as
-       in color.branch.<slot>.
+       (hunk header), 'func' (function in hunk header), `old` (removed lines),
+       `new` (added lines), `commit` (commit headers), or `whitespace`
+       (highlighting whitespace errors). The values of these variables may be
+       specified as in color.branch.<slot>.
 
 color.grep::
        When set to `always`, always highlight matches.  When `false` (or
@@ -1461,6 +1468,10 @@ remote.<name>.tagopt::
        Setting this value to \--no-tags disables automatic tag following when
        fetching from remote <name>
 
+remote.<name>.vcs::
+       Setting this to a value <vcs> will cause git to interact with
+       the remote with the git-remote-<vcs> helper.
+
 remotes.<group>::
        The list of remotes which are fetched by "git remote update
        <group>".  See linkgit:git-remote[1].
index 3d1c1e75b703cc7ba7013b4c8badb50f51966e1f..e57979198ba880379056189860275724eec7fd3e 100644 (file)
@@ -74,8 +74,9 @@ OPTIONS
        The tree or commit to produce an archive for.
 
 path::
-       If one or more paths are specified, include only these in the
-       archive, otherwise include all files and subdirectories.
+       Without an optional path parameter, all files and subdirectories
+       of the current working directory are included in the archive.
+       If one or more paths are specified, only these are included.
 
 BACKEND EXTRA OPTIONS
 ---------------------
diff --git a/Documentation/git-bisect-lk2009.txt b/Documentation/git-bisect-lk2009.txt
new file mode 100644 (file)
index 0000000..6b7b2e5
--- /dev/null
@@ -0,0 +1,1358 @@
+Fighting regressions with git bisect
+====================================
+:Author: Christian Couder
+:Email: chriscool@tuxfamily.org
+:Date: 2009/11/08
+
+Abstract
+--------
+
+"git bisect" enables software users and developers to easily find the
+commit that introduced a regression. We show why it is important to
+have good tools to fight regressions. We describe how "git bisect"
+works from the outside and the algorithms it uses inside. Then we
+explain how to take advantage of "git bisect" to improve current
+practices. And we discuss how "git bisect" could improve in the
+future.
+
+
+Introduction to "git bisect"
+----------------------------
+
+Git is a Distributed Version Control system (DVCS) created by Linus
+Torvalds and maintained by Junio Hamano.
+
+In Git like in many other Version Control Systems (VCS), the different
+states of the data that is managed by the system are called
+commits. And, as VCS are mostly used to manage software source code,
+sometimes "interesting" changes of behavior in the software are
+introduced in some commits.
+
+In fact people are specially interested in commits that introduce a
+"bad" behavior, called a bug or a regression. They are interested in
+these commits because a commit (hopefully) contains a very small set
+of source code changes. And it's much easier to understand and
+properly fix a problem when you only need to check a very small set of
+changes, than when you don't know where look in the first place.
+
+So to help people find commits that introduce a "bad" behavior, the
+"git bisect" set of commands was invented. And it follows of course
+that in "git bisect" parlance, commits where the "interesting
+behavior" is present are called "bad" commits, while other commits are
+called "good" commits. And a commit that introduce the behavior we are
+interested in is called a "first bad commit". Note that there could be
+more than one "first bad commit" in the commit space we are searching.
+
+So "git bisect" is designed to help find a "first bad commit". And to
+be as efficient as possible, it tries to perform a binary search.
+
+
+Fighting regressions overview
+-----------------------------
+
+Regressions: a big problem
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Regressions are a big problem in the software industry. But it's
+difficult to put some real numbers behind that claim.
+
+There are some numbers about bugs in general, like a NIST study in
+2002 <<1>> that said:
+
+_____________
+Software bugs, or errors, are so prevalent and so detrimental that
+they cost the U.S. economy an estimated $59.5 billion annually, or
+about 0.6 percent of the gross domestic product, according to a newly
+released study commissioned by the Department of Commerce's National
+Institute of Standards and Technology (NIST). At the national level,
+over half of the costs are borne by software users and the remainder
+by software developers/vendors.  The study also found that, although
+all errors cannot be removed, more than a third of these costs, or an
+estimated $22.2 billion, could be eliminated by an improved testing
+infrastructure that enables earlier and more effective identification
+and removal of software defects. These are the savings associated with
+finding an increased percentage (but not 100 percent) of errors closer
+to the development stages in which they are introduced. Currently,
+over half of all errors are not found until "downstream" in the
+development process or during post-sale software use.
+_____________
+
+And then:
+
+_____________
+Software developers already spend approximately 80 percent of
+development costs on identifying and correcting defects, and yet few
+products of any type other than software are shipped with such high
+levels of errors.
+_____________
+
+Eventually the conclusion started with:
+
+_____________
+The path to higher software quality is significantly improved software
+testing.
+_____________
+
+There are other estimates saying that 80% of the cost related to
+software is about maintenance <<2>>.
+
+Though, according to Wikipedia <<3>>:
+
+_____________
+A common perception of maintenance is that it is merely fixing
+bugs. However, studies and surveys over the years have indicated that
+the majority, over 80%, of the maintenance effort is used for
+non-corrective actions (Pigosky 1997). This perception is perpetuated
+by users submitting problem reports that in reality are functionality
+enhancements to the system.
+_____________
+
+But we can guess that improving on existing software is very costly
+because you have to watch out for regressions. At least this would
+make the above studies consistent among themselves.
+
+Of course some kind of software is developed, then used during some
+time without being improved on much, and then finally thrown away. In
+this case, of course, regressions may not be a big problem. But on the
+other hand, there is a lot of big software that is continually
+developed and maintained during years or even tens of years by a lot
+of people. And as there are often many people who depend (sometimes
+critically) on such software, regressions are a really big problem.
+
+One such software is the linux kernel. And if we look at the linux
+kernel, we can see that a lot of time and effort is spent to fight
+regressions. The release cycle start with a 2 weeks long merge
+window. Then the first release candidate (rc) version is tagged. And
+after that about 7 or 8 more rc versions will appear with around one
+week between each of them, before the final release.
+
+The time between the first rc release and the final release is
+supposed to be used to test rc versions and fight bugs and especially
+regressions. And this time is more than 80% of the release cycle
+time. But this is not the end of the fight yet, as of course it
+continues after the release.
+
+And then this is what Ingo Molnar (a well known linux kernel
+developer) says about his use of git bisect:
+
+_____________
+I most actively use it during the merge window (when a lot of trees
+get merged upstream and when the influx of bugs is the highest) - and
+yes, there have been cases that i used it multiple times a day. My
+average is roughly once a day.
+_____________
+
+So regressions are fought all the time by developers, and indeed it is
+well known that bugs should be fixed as soon as possible, so as soon
+as they are found. That's why it is interesting to have good tools for
+this purpose.
+
+Other tools to fight regressions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+So what are the tools used to fight regressions? They are nearly the
+same as those used to fight regular bugs. The only specific tools are
+test suites and tools similar as "git bisect".
+
+Test suites are very nice. But when they are used alone, they are
+supposed to be used so that all the tests are checked after each
+commit. This means that they are not very efficient, because many
+tests are run for no interesting result, and they suffer from
+combinational explosion.
+
+In fact the problem is that big software often has many different
+configuration options and that each test case should pass for each
+configuration after each commit. So if you have for each release: N
+configurations, M commits and T test cases, you should perform:
+
+-------------
+N * M * T tests
+-------------
+
+where N, M and T are all growing with the size your software.
+
+So very soon it will not be possible to completely test everything.
+
+And if some bugs slip through your test suite, then you can add a test
+to your test suite. But if you want to use your new improved test
+suite to find where the bug slipped in, then you will either have to
+emulate a bisection process or you will perhaps bluntly test each
+commit backward starting from the "bad" commit you have which may be
+very wasteful.
+
+"git bisect" overview
+---------------------
+
+Starting a bisection
+~~~~~~~~~~~~~~~~~~~~
+
+The first "git bisect" subcommand to use is "git bisect start" to
+start the search. Then bounds must be set to limit the commit
+space. This is done usually by giving one "bad" and at least one
+"good" commit. They can be passed in the initial call to "git bisect
+start" like this:
+
+-------------
+$ git bisect start [BAD [GOOD...]]
+-------------
+
+or they can be set using:
+
+-------------
+$ git bisect bad [COMMIT]
+-------------
+
+and:
+
+-------------
+$ git bisect good [COMMIT...]
+-------------
+
+where BAD, GOOD and COMMIT are all names that can be resolved to a
+commit.
+
+Then "git bisect" will checkout a commit of its choosing and ask the
+user to test it, like this:
+
+-------------
+$ git bisect start v2.6.27 v2.6.25
+Bisecting: 10928 revisions left to test after this (roughly 14 steps)
+[2ec65f8b89ea003c27ff7723525a2ee335a2b393] x86: clean up using max_low_pfn on 32-bit
+-------------
+
+Note that the example that we will use is really a toy example, we
+will be looking for the first commit that has a version like
+"2.6.26-something", that is the commit that has a "SUBLEVEL = 26" line
+in the top level Makefile. This is a toy example because there are
+better ways to find this commit with git than using "git bisect" (for
+example "git blame" or "git log -S<string>").
+
+Driving a bisection manually
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+At this point there are basically 2 ways to drive the search. It can
+be driven manually by the user or it can be driven automatically by a
+script or a command.
+
+If the user is driving it, then at each step of the search, the user
+will have to test the current commit and say if it is "good" or "bad"
+using the "git bisect good" or "git bisect bad" commands respectively
+that have been described above. For example:
+
+-------------
+$ git bisect bad
+Bisecting: 5480 revisions left to test after this (roughly 13 steps)
+[66c0b394f08fd89236515c1c84485ea712a157be] KVM: kill file->f_count abuse in kvm
+-------------
+
+And after a few more steps like that, "git bisect" will eventually
+find a first bad commit:
+
+-------------
+$ git bisect bad
+2ddcca36c8bcfa251724fe342c8327451988be0d is the first bad commit
+commit 2ddcca36c8bcfa251724fe342c8327451988be0d
+Author: Linus Torvalds <torvalds@linux-foundation.org>
+Date:   Sat May 3 11:59:44 2008 -0700
+
+    Linux 2.6.26-rc1
+
+:100644 100644 5cf8258195331a4dbdddff08b8d68642638eea57 4492984efc09ab72ff6219a7bc21fb6a957c4cd5 M      Makefile
+-------------
+
+At this point we can see what the commit does, check it out (if it's
+not already checked out) or tinker with it, for example:
+
+-------------
+$ git show HEAD
+commit 2ddcca36c8bcfa251724fe342c8327451988be0d
+Author: Linus Torvalds <torvalds@linux-foundation.org>
+Date:   Sat May 3 11:59:44 2008 -0700
+
+    Linux 2.6.26-rc1
+
+diff --git a/Makefile b/Makefile
+index 5cf8258..4492984 100644
+--- a/Makefile
++++ b/Makefile
+@@ -1,7 +1,7 @@
+ VERSION = 2
+ PATCHLEVEL = 6
+-SUBLEVEL = 25
+-EXTRAVERSION =
++SUBLEVEL = 26
++EXTRAVERSION = -rc1
+ NAME = Funky Weasel is Jiggy wit it
+
+ # *DOCUMENTATION*
+-------------
+
+And when we are finished we can use "git bisect reset" to go back to
+the branch we were in before we started bisecting:
+
+-------------
+$ git bisect reset
+Checking out files: 100% (21549/21549), done.
+Previous HEAD position was 2ddcca3... Linux 2.6.26-rc1
+Switched to branch 'master'
+-------------
+
+Driving a bisection automatically
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The other way to drive the bisection process is to tell "git bisect"
+to launch a script or command at each bisection step to know if the
+current commit is "good" or "bad". To do that, we use the "git bisect
+run" command. For example:
+
+-------------
+$ git bisect start v2.6.27 v2.6.25
+Bisecting: 10928 revisions left to test after this (roughly 14 steps)
+[2ec65f8b89ea003c27ff7723525a2ee335a2b393] x86: clean up using max_low_pfn on 32-bit
+$
+$ git bisect run grep '^SUBLEVEL = 25' Makefile
+running grep ^SUBLEVEL = 25 Makefile
+Bisecting: 5480 revisions left to test after this (roughly 13 steps)
+[66c0b394f08fd89236515c1c84485ea712a157be] KVM: kill file->f_count abuse in kvm
+running grep ^SUBLEVEL = 25 Makefile
+SUBLEVEL = 25
+Bisecting: 2740 revisions left to test after this (roughly 12 steps)
+[671294719628f1671faefd4882764886f8ad08cb] V4L/DVB(7879): Adding cx18 Support for mxl5005s
+...
+...
+running grep ^SUBLEVEL = 25 Makefile
+Bisecting: 0 revisions left to test after this (roughly 0 steps)
+[2ddcca36c8bcfa251724fe342c8327451988be0d] Linux 2.6.26-rc1
+running grep ^SUBLEVEL = 25 Makefile
+2ddcca36c8bcfa251724fe342c8327451988be0d is the first bad commit
+commit 2ddcca36c8bcfa251724fe342c8327451988be0d
+Author: Linus Torvalds <torvalds@linux-foundation.org>
+Date:   Sat May 3 11:59:44 2008 -0700
+
+    Linux 2.6.26-rc1
+
+:100644 100644 5cf8258195331a4dbdddff08b8d68642638eea57 4492984efc09ab72ff6219a7bc21fb6a957c4cd5 M      Makefile
+bisect run success
+-------------
+
+In this example, we passed "grep '^SUBLEVEL = 25' Makefile" as
+parameter to "git bisect run". This means that at each step, the grep
+command we passed will be launched. And if it exits with code 0 (that
+means success) then git bisect will mark the current state as
+"good". If it exits with code 1 (or any code between 1 and 127
+included, except the special code 125), then the current state will be
+marked as "bad".
+
+Exit code between 128 and 255 are special to "git bisect run". They
+make it stop immediately the bisection process. This is useful for
+example if the command passed takes too long to complete, because you
+can kill it with a signal and it will stop the bisection process.
+
+It can also be useful in scripts passed to "git bisect run" to "exit
+255" if some very abnormal situation is detected.
+
+Avoiding untestable commits
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Sometimes it happens that the current state cannot be tested, for
+example if it does not compile because there was a bug preventing it
+at that time. This is what the special exit code 125 is for. It tells
+"git bisect run" that the current commit should be marked as
+untestable and that another one should be chosen and checked out.
+
+If the bisection process is driven manually, you can use "git bisect
+skip" to do the same thing. (In fact the special exit code 125 makes
+"git bisect run" use "git bisect skip" in the background.)
+
+Or if you want more control, you can inspect the current state using
+for example "git bisect visualize". It will launch gitk (or "git log"
+if the DISPLAY environment variable is not set) to help you find a
+better bisection point.
+
+Either way, if you have a string of untestable commits, it might
+happen that the regression you are looking for has been introduced by
+one of these untestable commits. In this case it's not possible to
+tell for sure which commit introduced the regression.
+
+So if you used "git bisect skip" (or the run script exited with
+special code 125) you could get a result like this:
+
+-------------
+There are only 'skip'ped commits left to test.
+The first bad commit could be any of:
+15722f2fa328eaba97022898a305ffc8172db6b1
+78e86cf3e850bd755bb71831f42e200626fbd1e0
+e15b73ad3db9b48d7d1ade32f8cd23a751fe0ace
+070eab2303024706f2924822bfec8b9847e4ac1b
+We cannot bisect more!
+-------------
+
+Saving a log and replaying it
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you want to show other people your bisection process, you can get a
+log using for example:
+
+-------------
+$ git bisect log > bisect_log.txt
+-------------
+
+And it is possible to replay it using:
+
+-------------
+$ git bisect replay bisect_log.txt
+-------------
+
+
+"git bisect" details
+--------------------
+
+Bisection algorithm
+~~~~~~~~~~~~~~~~~~~
+
+As the Git commits form a directed acyclic graph (DAG), finding the
+best bisection commit to test at each step is not so simple. Anyway
+Linus found and implemented a "truly stupid" algorithm, later improved
+by Junio Hamano, that works quite well.
+
+So the algorithm used by "git bisect" to find the best bisection
+commit when there are no skipped commits is the following:
+
+1) keep only the commits that:
+
+a) are ancestor of the "bad" commit (including the "bad" commit itself),
+b) are not ancestor of a "good" commit (excluding the "good" commits).
+
+This means that we get rid of the uninteresting commits in the DAG.
+
+For example if we start with a graph like this:
+
+-------------
+G-Y-G-W-W-W-X-X-X-X
+          \ /
+           W-W-B
+          /
+Y---G-W---W
+ \ /   \
+Y-Y     X-X-X-X
+
+-> time goes this way ->
+-------------
+
+where B is the "bad" commit, "G" are "good" commits and W, X, and Y
+are other commits, we will get the following graph after this first
+step:
+
+-------------
+W-W-W
+     \
+      W-W-B
+     /
+W---W
+-------------
+
+So only the W and B commits will be kept. Because commits X and Y will
+have been removed by rules a) and b) respectively, and because commits
+G are removed by rule b) too.
+
+Note for git users, that it is equivalent as keeping only the commit
+given by:
+
+-------------
+git rev-list BAD --not GOOD1 GOOD2...
+-------------
+
+Also note that we don't require the commits that are kept to be
+descendants of a "good" commit. So in the following example, commits W
+and Z will be kept:
+
+-------------
+G-W-W-W-B
+   /
+Z-Z
+-------------
+
+2) starting from the "good" ends of the graph, associate to each
+commit the number of ancestors it has plus one
+
+For example with the following graph where H is the "bad" commit and A
+and D are some parents of some "good" commits:
+
+-------------
+A-B-C
+     \
+      F-G-H
+     /
+D---E
+-------------
+
+this will give:
+
+-------------
+1 2 3
+A-B-C
+     \6 7 8
+      F-G-H
+1   2/
+D---E
+-------------
+
+3) associate to each commit: min(X, N - X)
+
+where X is the value associated to the commit in step 2) and N is the
+total number of commits in the graph.
+
+In the above example we have N = 8, so this will give:
+
+-------------
+1 2 3
+A-B-C
+     \2 1 0
+      F-G-H
+1   2/
+D---E
+-------------
+
+4) the best bisection point is the commit with the highest associated
+number
+
+So in the above example the best bisection point is commit C.
+
+5) note that some shortcuts are implemented to speed up the algorithm
+
+As we know N from the beginning, we know that min(X, N - X) can't be
+greater than N/2. So during steps 2) and 3), if we would associate N/2
+to a commit, then we know this is the best bisection point. So in this
+case we can just stop processing any other commit and return the
+current commit.
+
+Bisection algorithm debugging
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+For any commit graph, you can see the number associated with each
+commit using "git rev-list --bisect-all".
+
+For example, for the above graph, a command like:
+
+-------------
+$ git rev-list --bisect-all BAD --not GOOD1 GOOD2
+-------------
+
+would output something like:
+
+-------------
+e15b73ad3db9b48d7d1ade32f8cd23a751fe0ace (dist=3)
+15722f2fa328eaba97022898a305ffc8172db6b1 (dist=2)
+78e86cf3e850bd755bb71831f42e200626fbd1e0 (dist=2)
+a1939d9a142de972094af4dde9a544e577ddef0e (dist=2)
+070eab2303024706f2924822bfec8b9847e4ac1b (dist=1)
+a3864d4f32a3bf5ed177ddef598490a08760b70d (dist=1)
+a41baa717dd74f1180abf55e9341bc7a0bb9d556 (dist=1)
+9e622a6dad403b71c40979743bb9d5be17b16bd6 (dist=0)
+-------------
+
+Bisection algorithm discussed
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+First let's define "best bisection point". We will say that a commit X
+is a best bisection point or a best bisection commit if knowing its
+state ("good" or "bad") gives as much information as possible whether
+the state of the commit happens to be "good" or "bad".
+
+This means that the best bisection commits are the commits where the
+following function is maximum:
+
+-------------
+f(X) = min(information_if_good(X), information_if_bad(X))
+-------------
+
+where information_if_good(X) is the information we get if X is good
+and information_if_bad(X) is the information we get if X is bad.
+
+Now we will suppose that there is only one "first bad commit". This
+means that all its descendants are "bad" and all the other commits are
+"good". And we will suppose that all commits have an equal probability
+of being good or bad, or of being the first bad commit, so knowing the
+state of c commits gives always the same amount of information
+wherever these c commits are on the graph and whatever c is. (So we
+suppose that these commits being for example on a branch or near a
+good or a bad commit does not give more or less information).
+
+Let's also suppose that we have a cleaned up graph like one after step
+1) in the bisection algorithm above. This means that we can measure
+the information we get in terms of number of commit we can remove from
+the graph..
+
+And let's take a commit X in the graph.
+
+If X is found to be "good", then we know that its ancestors are all
+"good", so we want to say that:
+
+-------------
+information_if_good(X) = number_of_ancestors(X)  (TRUE)
+-------------
+
+And this is true because at step 1) b) we remove the ancestors of the
+"good" commits.
+
+If X is found to be "bad", then we know that its descendants are all
+"bad", so we want to say that:
+
+-------------
+information_if_bad(X) = number_of_descendants(X)  (WRONG)
+-------------
+
+But this is wrong because at step 1) a) we keep only the ancestors of
+the bad commit. So we get more information when a commit is marked as
+"bad", because we also know that the ancestors of the previous "bad"
+commit that are not ancestors of the new "bad" commit are not the
+first bad commit. We don't know if they are good or bad, but we know
+that they are not the first bad commit because they are not ancestor
+of the new "bad" commit.
+
+So when a commit is marked as "bad" we know we can remove all the
+commits in the graph except those that are ancestors of the new "bad"
+commit. This means that:
+
+-------------
+information_if_bad(X) = N - number_of_ancestors(X)  (TRUE)
+-------------
+
+where N is the number of commits in the (cleaned up) graph.
+
+So in the end this means that to find the best bisection commits we
+should maximize the function:
+
+-------------
+f(X) = min(number_of_ancestors(X), N - number_of_ancestors(X))
+-------------
+
+And this is nice because at step 2) we compute number_of_ancestors(X)
+and so at step 3) we compute f(X).
+
+Let's take the following graph as an example:
+
+-------------
+           G-H-I-J
+          /       \
+A-B-C-D-E-F         O
+          \       /
+           K-L-M-N
+-------------
+
+If we compute the following non optimal function on it:
+
+-------------
+g(X) = min(number_of_ancestors(X), number_of_descendants(X))
+-------------
+
+we get:
+
+-------------
+           4 3 2 1
+           G-H-I-J
+1 2 3 4 5 6/       \0
+A-B-C-D-E-F         O
+          \       /
+           K-L-M-N
+           4 3 2 1
+-------------
+
+but with the algorithm used by git bisect we get:
+
+-------------
+           7 7 6 5
+           G-H-I-J
+1 2 3 4 5 6/       \0
+A-B-C-D-E-F         O
+          \       /
+           K-L-M-N
+           7 7 6 5
+-------------
+
+So we chose G, H, K or L as the best bisection point, which is better
+than F. Because if for example L is bad, then we will know not only
+that L, M and N are bad but also that G, H, I and J are not the first
+bad commit (since we suppose that there is only one first bad commit
+and it must be an ancestor of L).
+
+So the current algorithm seems to be the best possible given what we
+initially supposed.
+
+Skip algorithm
+~~~~~~~~~~~~~~
+
+When some commits have been skipped (using "git bisect skip"), then
+the bisection algorithm is the same for step 1) to 3). But then we use
+roughly the following steps:
+
+6) sort the commit by decreasing associated value
+
+7) if the first commit has not been skipped, we can return it and stop
+here
+
+8) otherwise filter out all the skipped commits in the sorted list
+
+9) use a pseudo random number generator (PRNG) to generate a random
+number between 0 and 1
+
+10) multiply this random number with its square root to bias it toward
+0
+
+11) multiply the result by the number of commits in the filtered list
+to get an index into this list
+
+12) return the commit at the computed index
+
+Skip algorithm discussed
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+After step 7) (in the skip algorithm), we could check if the second
+commit has been skipped and return it if it is not the case. And in
+fact that was the algorithm we used from when "git bisect skip" was
+developed in git version 1.5.4 (released on February 1st 2008) until
+git version 1.6.4 (released July 29th 2009).
+
+But Ingo Molnar and H. Peter Anvin (another well known linux kernel
+developer) both complained that sometimes the best bisection points
+all happened to be in an area where all the commits are
+untestable. And in this case the user was asked to test many
+untestable commits, which could be very inefficient.
+
+Indeed untestable commits are often untestable because a breakage was
+introduced at one time, and that breakage was fixed only after many
+other commits were introduced.
+
+This breakage is of course most of the time unrelated to the breakage
+we are trying to locate in the commit graph. But it prevents us to
+know if the interesting "bad behavior" is present or not.
+
+So it is a fact that commits near an untestable commit have a high
+probability of being untestable themselves. And the best bisection
+commits are often found together too (due to the bisection algorithm).
+
+This is why it is a bad idea to just chose the next best unskipped
+bisection commit when the first one has been skipped.
+
+We found that most commits on the graph may give quite a lot of
+information when they are tested. And the commits that will not on
+average give a lot of information are the one near the good and bad
+commits.
+
+So using a PRNG with a bias to favor commits away from the good and
+bad commits looked like a good choice.
+
+One obvious improvement to this algorithm would be to look for a
+commit that has an associated value near the one of the best bisection
+commit, and that is on another branch, before using the PRNG. Because
+if such a commit exists, then it is not very likely to be untestable
+too, so it will probably give more information than a nearly randomly
+chosen one.
+
+Checking merge bases
+~~~~~~~~~~~~~~~~~~~~
+
+There is another tweak in the bisection algorithm that has not been
+described in the "bisection algorithm" above.
+
+We supposed in the previous examples that the "good" commits were
+ancestors of the "bad" commit. But this is not a requirement of "git
+bisect".
+
+Of course the "bad" commit cannot be an ancestor of a "good" commit,
+because the ancestors of the good commits are supposed to be
+"good". And all the "good" commits must be related to the bad commit.
+They cannot be on a branch that has no link with the branch of the
+"bad" commit. But it is possible for a good commit to be related to a
+bad commit and yet not be neither one of its ancestor nor one of its
+descendants.
+
+For example, there can be a "main" branch, and a "dev" branch that was
+forked of the main branch at a commit named "D" like this:
+
+-------------
+A-B-C-D-E-F-G  <--main
+       \
+       H-I-J  <--dev
+-------------
+
+The commit "D" is called a "merge base" for branch "main" and "dev"
+because it's the best common ancestor for these branches for a merge.
+
+Now let's suppose that commit J is bad and commit G is good and that
+we apply the bisection algorithm like it has been previously
+described.
+
+As described in step 1) b) of the bisection algorithm, we remove all
+the ancestors of the good commits because they are supposed to be good
+too.
+
+So we would be left with only:
+
+-------------
+H-I-J
+-------------
+
+But what happens if the first bad commit is "B" and if it has been
+fixed in the "main" branch by commit "F"?
+
+The result of such a bisection would be that we would find that H is
+the first bad commit, when in fact it's B. So that would be wrong!
+
+And yes it's can happen in practice that people working on one branch
+are not aware that people working on another branch fixed a bug! It
+could also happen that F fixed more than one bug or that it is a
+revert of some big development effort that was not ready to be
+released.
+
+In fact development teams often maintain both a development branch and
+a maintenance branch, and it would be quite easy for them if "git
+bisect" just worked when they want to bisect a regression on the
+development branch that is not on the maintenance branch. They should
+be able to start bisecting using:
+
+-------------
+$ git bisect start dev main
+-------------
+
+To enable that additional nice feature, when a bisection is started
+and when some good commits are not ancestors of the bad commit, we
+first compute the merge bases between the bad and the good commits and
+we chose these merge bases as the first commits that will be checked
+out and tested.
+
+If it happens that one merge base is bad, then the bisection process
+is stopped with a message like:
+
+-------------
+The merge base BBBBBB is bad.
+This means the bug has been fixed between BBBBBB and [GGGGGG,...].
+-------------
+
+where BBBBBB is the sha1 hash of the bad merge base and [GGGGGG,...]
+is a comma separated list of the sha1 of the good commits.
+
+If some of the merge bases are skipped, then the bisection process
+continues, but the following message is printed for each skipped merge
+base:
+
+-------------
+Warning: the merge base between BBBBBB and [GGGGGG,...] must be skipped.
+So we cannot be sure the first bad commit is between MMMMMM and BBBBBB.
+We continue anyway.
+-------------
+
+where BBBBBB is the sha1 hash of the bad commit, MMMMMM is the sha1
+hash of the merge base that is skipped and [GGGGGG,...]  is a comma
+separated list of the sha1 of the good commits.
+
+So if there is no bad merge base, the bisection process continues as
+usual after this step.
+
+Best bisecting practices
+------------------------
+
+Using test suites and git bisect together
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you both have a test suite and use git bisect, then it becomes less
+important to check that all tests pass after each commit. Though of
+course it is probably a good idea to have some checks to avoid
+breaking too many things because it could make bisecting other bugs
+more difficult.
+
+You can focus your efforts to check at a few points (for example rc
+and beta releases) that all the T test cases pass for all the N
+configurations. And when some tests don't pass you can use "git
+bisect" (or better "git bisect run"). So you should perform roughly:
+
+-------------
+c * N * T + b * M * log2(M) tests
+-------------
+
+where c is the number of rounds of test (so a small constant) and b is
+the ratio of bug per commit (hopefully a small constant too).
+
+So of course it's much better as it's O(N \* T) vs O(N \* T \* M) if
+you would test everything after each commit.
+
+This means that test suites are good to prevent some bugs from being
+committed and they are also quite good to tell you that you have some
+bugs. But they are not so good to tell you where some bugs have been
+introduced. To tell you that efficiently, git bisect is needed.
+
+The other nice thing with test suites, is that when you have one, you
+already know how to test for bad behavior. So you can use this
+knowledge to create a new test case for "git bisect" when it appears
+that there is a regression. So it will be easier to bisect the bug and
+fix it. And then you can add the test case you just created to your
+test suite.
+
+So if you know how to create test cases and how to bisect, you will be
+subject to a virtuous circle:
+
+more tests => easier to create tests => easier to bisect => more tests
+
+So test suites and "git bisect" are complementary tools that are very
+powerful and efficient when used together.
+
+Bisecting build failures
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can very easily automatically bisect broken builds using something
+like:
+
+-------------
+$ git bisect start BAD GOOD
+$ git bisect run make
+-------------
+
+Passing sh -c "some commands" to "git bisect run"
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+For example:
+
+-------------
+$ git bisect run sh -c "make || exit 125; ./my_app | grep 'good output'"
+-------------
+
+On the other hand if you do this often, then it can be worth having
+scripts to avoid too much typing.
+
+Finding performance regressions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Here is an example script that comes slightly modified from a real
+world script used by Junio Hamano <<4>>.
+
+This script can be passed to "git bisect run" to find the commit that
+introduced a performance regression:
+
+-------------
+#!/bin/sh
+
+# Build errors are not what I am interested in.
+make my_app || exit 255
+
+# We are checking if it stops in a reasonable amount of time, so
+# let it run in the background...
+
+./my_app >log 2>&1 &
+
+# ... and grab its process ID.
+pid=$!
+
+# ... and then wait for sufficiently long.
+sleep $NORMAL_TIME
+
+# ... and then see if the process is still there.
+if kill -0 $pid
+then
+       # It is still running -- that is bad.
+       kill $pid; sleep 1; kill $pid;
+       exit 1
+else
+       # It has already finished (the $pid process was no more),
+       # and we are happy.
+       exit 0
+fi
+-------------
+
+Following general best practices
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+It is obviously a good idea not to have commits with changes that
+knowingly break things, even if some other commits later fix the
+breakage.
+
+It is also a good idea when using any VCS to have only one small
+logical change in each commit.
+
+The smaller the changes in your commit, the most effective "git
+bisect" will be. And you will probably need "git bisect" less in the
+first place, as small changes are easier to review even if they are
+only reviewed by the commiter.
+
+Another good idea is to have good commit messages. They can be very
+helpful to understand why some changes were made.
+
+These general best practices are very helpful if you bisect often.
+
+Avoiding bug prone merges
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+First merges by themselves can introduce some regressions even when
+the merge needs no source code conflict resolution. This is because a
+semantic change can happen in one branch while the other branch is not
+aware of it.
+
+For example one branch can change the semantic of a function while the
+other branch add more calls to the same function.
+
+This is made much worse if many files have to be fixed to resolve
+conflicts. That's why such merges are called "evil merges". They can
+make regressions very difficult to track down. It can even be
+misleading to know the first bad commit if it happens to be such a
+merge, because people might think that the bug comes from bad conflict
+resolution when it comes from a semantic change in one branch.
+
+Anyway "git rebase" can be used to linearize history. This can be used
+either to avoid merging in the first place. Or it can be used to
+bisect on a linear history instead of the non linear one, as this
+should give more information in case of a semantic change in one
+branch.
+
+Merges can be also made simpler by using smaller branches or by using
+many topic branches instead of only long version related branches.
+
+And testing can be done more often in special integration branches
+like linux-next for the linux kernel.
+
+Adapting your work-flow
+~~~~~~~~~~~~~~~~~~~~~~~
+
+A special work-flow to process regressions can give great results.
+
+Here is an example of a work-flow used by Andreas Ericsson:
+
+* write, in the test suite, a test script that exposes the regression
+* use "git bisect run" to find the commit that introduced it
+* fix the bug that is often made obvious by the previous step
+* commit both the fix and the test script (and if needed more tests)
+
+And here is what Andreas said about this work-flow <<5>>:
+
+_____________
+To give some hard figures, we used to have an average report-to-fix
+cycle of 142.6 hours (according to our somewhat weird bug-tracker
+which just measures wall-clock time). Since we moved to git, we've
+lowered that to 16.2 hours. Primarily because we can stay on top of
+the bug fixing now, and because everyone's jockeying to get to fix
+bugs (we're quite proud of how lazy we are to let git find the bugs
+for us). Each new release results in ~40% fewer bugs (almost certainly
+due to how we now feel about writing tests).
+_____________
+
+Clearly this work-flow uses the virtuous circle between test suites
+and "git bisect". In fact it makes it the standard procedure to deal
+with regression.
+
+In other messages Andreas says that they also use the "best practices"
+described above: small logical commits, topic branches, no evil
+merge,... These practices all improve the bisectability of the commit
+graph, by making it easier and more useful to bisect.
+
+So a good work-flow should be designed around the above points. That
+is making bisecting easier, more useful and standard.
+
+Involving QA people and if possible end users
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+One nice about "git bisect" is that it is not only a developer
+tool. It can effectively be used by QA people or even end users (if
+they have access to the source code or if they can get access to all
+the builds).
+
+There was a discussion at one point on the linux kernel mailing list
+of whether it was ok to always ask end user to bisect, and very good
+points were made to support the point of view that it is ok.
+
+For example David Miller wrote <<6>>:
+
+_____________
+What people don't get is that this is a situation where the "end node
+principle" applies. When you have limited resources (here: developers)
+you don't push the bulk of the burden upon them. Instead you push
+things out to the resource you have a lot of, the end nodes (here:
+users), so that the situation actually scales.
+_____________
+
+This means that it is often "cheaper" if QA people or end users can do
+it.
+
+What is interesting too is that end users that are reporting bugs (or
+QA people that reproduced a bug) have access to the environment where
+the bug happens. So they can often more easily reproduce a
+regression. And if they can bisect, then more information will be
+extracted from the environment where the bug happens, which means that
+it will be easier to understand and then fix the bug.
+
+For open source projects it can be a good way to get more useful
+contributions from end users, and to introduce them to QA and
+development activities.
+
+Using complex scripts
+~~~~~~~~~~~~~~~~~~~~~
+
+In some cases like for kernel development it can be worth developing
+complex scripts to be able to fully automate bisecting.
+
+Here is what Ingo Molnar says about that <<7>>:
+
+_____________
+i have a fully automated bootup-hang bisection script. It is based on
+"git-bisect run". I run the script, it builds and boots kernels fully
+automatically, and when the bootup fails (the script notices that via
+the serial log, which it continuously watches - or via a timeout, if
+the system does not come up within 10 minutes it's a "bad" kernel),
+the script raises my attention via a beep and i power cycle the test
+box. (yeah, i should make use of a managed power outlet to 100%
+automate it)
+_____________
+
+Combining test suites, git bisect and other systems together
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We have seen that test suites an git bisect are very powerful when
+used together. It can be even more powerful if you can combine them
+with other systems.
+
+For example some test suites could be run automatically at night with
+some unusual (or even random) configurations. And if a regression is
+found by a test suite, then "git bisect" can be automatically
+launched, and its result can be emailed to the author of the first bad
+commit found by "git bisect", and perhaps other people too. And a new
+entry in the bug tracking system could be automatically created too.
+
+
+The future of bisecting
+-----------------------
+
+"git replace"
+~~~~~~~~~~~~~
+
+We saw earlier that "git bisect skip" is now using a PRNG to try to
+avoid areas in the commit graph where commits are untestable. The
+problem is that sometimes the first bad commit will be in an
+untestable area.
+
+To simplify the discussion we will suppose that the untestable area is
+a simple string of commits and that it was created by a breakage
+introduced by one commit (let's call it BBC for bisect breaking
+commit) and later fixed by another one (let's call it BFC for bisect
+fixing commit).
+
+For example:
+
+-------------
+...-Y-BBC-X1-X2-X3-X4-X5-X6-BFC-Z-...
+-------------
+
+where we know that Y is good and BFC is bad, and where BBC and X1 to
+X6 are untestable.
+
+In this case if you are bisecting manually, what you can do is create
+a special branch that starts just before the BBC. The first commit in
+this branch should be the BBC with the BFC squashed into it. And the
+other commits in the branch should be the commits between BBC and BFC
+rebased on the first commit of the branch and then the commit after
+BFC also rebased on.
+
+For example:
+
+-------------
+      (BBC+BFC)-X1'-X2'-X3'-X4'-X5'-X6'-Z'
+     /
+...-Y-BBC-X1-X2-X3-X4-X5-X6-BFC-Z-...
+-------------
+
+where commits quoted with ' have been rebased.
+
+You can easily create such a branch with Git using interactive rebase.
+
+For example using:
+
+-------------
+$ git rebase -i Y Z
+-------------
+
+and then moving BFC after BBC and squashing it.
+
+After that you can start bisecting as usual in the new branch and you
+should eventually find the first bad commit.
+
+For example:
+
+-------------
+$ git bisect start Z' Y
+-------------
+
+If you are using "git bisect run", you can use the same manual fix up
+as above, and then start another "git bisect run" in the special
+branch. Or as the "git bisect" man page says, the script passed to
+"git bisect run" can apply a patch before it compiles and test the
+software <<8>>. The patch should turn a current untestable commits
+into a testable one. So the testing will result in "good" or "bad" and
+"git bisect" will be able to find the first bad commit. And the script
+should not forget to remove the patch once the testing is done before
+exiting from the script.
+
+(Note that instead of a patch you can use "git cherry-pick BFC" to
+apply the fix, and in this case you should use "git reset --hard
+HEAD^" to revert the cherry-pick after testing and before returning
+from the script.)
+
+But the above ways to work around untestable areas are a little bit
+clunky. Using special branches is nice because these branches can be
+shared by developers like usual branches, but the risk is that people
+will get many such branches. And it disrupts the normal "git bisect"
+work-flow. So, if you want to use "git bisect run" completely
+automatically, you have to add special code in your script to restart
+bisection in the special branches.
+
+Anyway one can notice in the above special branch example that the Z'
+and Z commits should point to the same source code state (the same
+"tree" in git parlance). That's because Z' result from applying the
+same changes as Z just in a slightly different order.
+
+So if we could just "replace" Z by Z' when we bisect, then we would
+not need to add anything to a script. It would just work for anyone in
+the project sharing the special branches and the replacements.
+
+With the example above that would give:
+
+-------------
+      (BBC+BFC)-X1'-X2'-X3'-X4'-X5'-X6'-Z'-...
+     /
+...-Y-BBC-X1-X2-X3-X4-X5-X6-BFC-Z
+-------------
+
+That's why the "git replace" command was created. Technically it
+stores replacements "refs" in the "refs/replace/" hierarchy. These
+"refs" are like branches (that are stored in "refs/heads/") or tags
+(that are stored in "refs/tags"), and that means that they can
+automatically be shared like branches or tags among developers.
+
+"git replace" is a very powerful mechanism. It can be used to fix
+commits in already released history, for example to change the commit
+message or the author. And it can also be used instead of git "grafts"
+to link a repository with another old repository.
+
+In fact it's this last feature that "sold" it to the git community, so
+it is now in the "master" branch of git's git repository and it should
+be released in git 1.6.5 in October or November 2009.
+
+One problem with "git replace" is that currently it stores all the
+replacements refs in "refs/replace/", but it would be perhaps better
+if the replacement refs that are useful only for bisecting would be in
+"refs/replace/bisect/". This way the replacement refs could be used
+only for bisecting, while other refs directly in "refs/replace/" would
+be used nearly all the time.
+
+Bisecting sporadic bugs
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Another possible improvement to "git bisect" would be to optionally
+add some redundancy to the tests performed so that it would be more
+reliable when tracking sporadic bugs.
+
+This has been requested by some kernel developers because some bugs
+called sporadic bugs do not appear in all the kernel builds because
+they are very dependent on the compiler output.
+
+The idea is that every 3 test for example, "git bisect" could ask the
+user to test a commit that has already been found to be "good" or
+"bad" (because one of its descendants or one of its ancestors has been
+found to be "good" or "bad" respectively). If it happens that a commit
+has been previously incorrectly classified then the bisection can be
+aborted early, hopefully before too many mistakes have been made. Then
+the user will have to look at what happened and then restart the
+bisection using a fixed bisect log.
+
+There is already a project called BBChop created by Ealdwulf Wuffinga
+on Github that does something like that using Bayesian Search Theory
+<<9>>:
+
+_____________
+BBChop is like 'git bisect' (or equivalent), but works when your bug
+is intermittent. That is, it works in the presence of false negatives
+(when a version happens to work this time even though it contains the
+bug). It assumes that there are no false positives (in principle, the
+same approach would work, but adding it may be non-trivial).
+_____________
+
+But BBChop is independent of any VCS and it would be easier for Git
+users to have something integrated in Git.
+
+Conclusion
+----------
+
+We have seen that regressions are an important problem, and that "git
+bisect" has nice features that complement very well practices and
+other tools, especially test suites, that are generally used to fight
+regressions. But it might be needed to change some work-flows and
+(bad) habits to get the most out of it.
+
+Some improvements to the algorithms inside "git bisect" are possible
+and some new features could help in some cases, but overall "git
+bisect" works already very well, is used a lot, and is already very
+useful. To back up that last claim, let's give the final word to Ingo
+Molnar when he was asked by the author how much time does he think
+"git bisect" saves him when he uses it:
+
+_____________
+a _lot_.
+
+About ten years ago did i do my first 'bisection' of a Linux patch
+queue. That was prior the Git (and even prior the BitKeeper) days. I
+literally days spent sorting out patches, creating what in essence
+were standalone commits that i guessed to be related to that bug.
+
+It was a tool of absolute last resort. I'd rather spend days looking
+at printk output than do a manual 'patch bisection'.
+
+With Git bisect it's a breeze: in the best case i can get a ~15 step
+kernel bisection done in 20-30 minutes, in an automated way. Even with
+manual help or when bisecting multiple, overlapping bugs, it's rarely
+more than an hour.
+
+In fact it's invaluable because there are bugs i would never even
+_try_ to debug if it wasn't for git bisect. In the past there were bug
+patterns that were immediately hopeless for me to debug - at best i
+could send the crash/bug signature to lkml and hope that someone else
+can think of something.
+
+And even if a bisection fails today it tells us something valuable
+about the bug: that it's non-deterministic - timing or kernel image
+layout dependent.
+
+So git bisect is unconditional goodness - and feel free to quote that
+;-)
+_____________
+
+Acknowledgements
+----------------
+
+Many thanks to Junio Hamano for his help in reviewing this paper, for
+reviewing the patches I sent to the git mailing list, for discussing
+some ideas and helping me improve them, for improving "git bisect" a
+lot and for his awesome work in maintaining and developing Git.
+
+Many thanks to Ingo Molnar for giving me very useful information that
+appears in this paper, for commenting on this paper, for his
+suggestions to improve "git bisect" and for evangelizing "git bisect"
+on the linux kernel mailing lists.
+
+Many thanks to Linus Torvalds for inventing, developing and
+evangelizing "git bisect", Git and Linux.
+
+Many thanks to the many other great people who helped one way or
+another when I worked on git, especially to Andreas Ericsson, Johannes
+Schindelin, H. Peter Anvin, Daniel Barkalow, Bill Lear, John Hawley,
+Shawn O. Pierce, Jeff King, Sam Vilain, Jon Seymour.
+
+Many thanks to the Linux-Kongress program committee for choosing the
+author to given a talk and for publishing this paper.
+
+References
+----------
+
+- [[[1]]] http://www.nist.gov/public_affairs/releases/n02-10.htm['Software Errors Cost U.S. Economy $59.5 Billion Annually'. Nist News Release.]
+- [[[2]]] http://java.sun.com/docs/codeconv/html/CodeConventions.doc.html#16712['Code Conventions for the Java Programming Language'. Sun Microsystems.]
+- [[[3]]] http://en.wikipedia.org/wiki/Software_maintenance['Software maintenance'. Wikipedia.]
+- [[[4]]] http://article.gmane.org/gmane.comp.version-control.git/45195/[Junio C Hamano. 'Automated bisect success story'. Gmane.]
+- [[[5]]] http://lwn.net/Articles/317154/[Christian Couder. 'Fully automated bisecting with "git bisect run"'. LWN.net.]
+- [[[6]]] http://lwn.net/Articles/277872/[Jonathan Corbet. 'Bisection divides users and developers'. LWN.net.]
+- [[[7]]] http://article.gmane.org/gmane.linux.scsi/36652/[Ingo Molnar. 'Re: BUG 2.6.23-rc3 can't see sd partitions on Alpha'. Gmane.]
+- [[[8]]] http://www.kernel.org/pub/software/scm/git/docs/git-bisect.html[Junio C Hamano and the git-list. 'git-bisect(1) Manual Page'. Linux Kernel Archives.]
+- [[[9]]] http://github.com/Ealdwulf/bbchop[Ealdwulf. 'bbchop'. GitHub.]
index d2ffae0c10176a412202d381a9c7beb2b21979ef..c39d957c3a3a432f5e685d44066c145f03b96365 100644 (file)
@@ -330,6 +330,11 @@ Documentation
 -------------
 Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 
+SEE ALSO
+--------
+link:git-bisect-lk2009.html[Fighting regressions with git bisect],
+linkgit:git-blame[1].
+
 GIT
 ---
 Part of the linkgit:git[1] suite
index aee7e4a8c9396225e6d82d6a85618128c4170ce0..c3a066e60cb40d893287f813a4a007c4db42b1bb 100644 (file)
@@ -24,7 +24,7 @@ ssh, rsync, http) cannot be used.  This command provides support for
 'git-fetch' and 'git-pull' to operate by packaging objects and references
 in an archive at the originating machine, then importing those into
 another repository using 'git-fetch' and 'git-pull'
-after moving the archive by some means (i.e., by sneakernet).  As no
+after moving the archive by some means (e.g., by sneakernet).  As no
 direct connection between the repositories exists, the user must specify a
 basis for the bundle that is held by the destination repository: the
 bundle assumes that all objects in the basis are already in the
index 7e7d9fcf508eed3cbc984fec3aabc5d351943a32..7ccd742a87db1541184868b794fa4ef597d04de8 100644 (file)
@@ -11,7 +11,7 @@ SYNOPSIS
 [verse]
 'git clone' [--template=<template_directory>]
          [-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror]
-         [-o <name>] [-u <upload-pack>] [--reference <repository>]
+         [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
          [--depth <depth>] [--recursive] [--] <repository> [<directory>]
 
 DESCRIPTION
index 3ea80c820fb404d7b00436f9820fe8701f056ebf..c97c151ae1fdf53a09c5d3796ea6f6fb98b57b03 100644 (file)
@@ -9,7 +9,7 @@ SYNOPSIS
 --------
 [verse]
 'git commit' [-a | --interactive] [-s] [-v] [-u<mode>] [--amend] [--dry-run]
-          [(-c | -C) <commit>] [-F <file> | -m <msg>]
+          [(-c | -C) <commit>] [-F <file> | -m <msg>] [--reset-author]
           [--allow-empty] [--no-verify] [-e] [--author=<author>]
           [--cleanup=<mode>] [--] [[-i | -o ]<file>...]
 
@@ -69,6 +69,25 @@ OPTIONS
        Like '-C', but with '-c' the editor is invoked, so that
        the user can further edit the commit message.
 
+--reset-author::
+       When used with -C/-c/--amend options, declare that the
+       authorship of the resulting commit now belongs of the committer.
+       This also renews the author timestamp.
+
+--short::
+       When doing a dry-run, give the output in the short-format. See
+       linkgit:git-status[1] for details. Implies `--dry-run`.
+
+--porcelain::
+       When doing a dry-run, give the output in a porcelain-ready
+       format. See linkgit:git-status[1] for details. Implies
+       `--dry-run`.
+
+-z::
+       When showing `short` or `porcelain` status output, terminate
+       entries in the status output with NUL, instead of LF. If no
+       format is given, implies the `--porcelain` output format.
+
 -F <file>::
 --file=<file>::
        Take the commit message from the given file.  Use '-' to
index 288032c7b88f2f43a2965d5bdbffc8472f0ca281..e6d364f53cd14a1738a210e131bc7799fb746abb 100644 (file)
@@ -311,8 +311,8 @@ change to the project.
 ....
        'commit' SP <ref> LF
        mark?
-       ('author' SP <name> SP LT <email> GT SP <when> LF)?
-       'committer' SP <name> SP LT <email> GT SP <when> LF
+       ('author' (SP <name>)? SP LT <email> GT SP <when> LF)?
+       'committer' (SP <name>)? SP LT <email> GT SP <when> LF
        data
        ('from' SP <committish> LF)?
        ('merge' SP <committish> LF)?
@@ -657,7 +657,7 @@ lightweight (non-annotated) tags see the `reset` command below.
 ....
        'tag' SP <name> LF
        'from' SP <committish> LF
-       'tagger' SP <name> SP LT <email> GT SP <when> LF
+       'tagger' (SP <name>)? SP LT <email> GT SP <when> LF
        data
 ....
 
index 996c3fcc6cde0b7ce1acf6bfb3815bac4dd6176c..b81ac98cf0d95ab961b228e514c8fdabc152a21e 100644 (file)
@@ -8,7 +8,7 @@ git-mailinfo - Extracts patch and authorship from a single e-mail message
 
 SYNOPSIS
 --------
-'git mailinfo' [-k] [-u | --encoding=<encoding> | -n] [--scissors] <msg> <patch>
+'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n] [--scissors] <msg> <patch>
 
 
 DESCRIPTION
@@ -32,6 +32,11 @@ OPTIONS
        munging, and is most useful when used to read back
        'git-format-patch -k' output.
 
+-b::
+       When -k is not in effect, all leading strings bracketed with '['
+       and ']' pairs are stripped.  This option limits the stripping to
+       only the pairs whose bracketed string contains the word "PATCH".
+
 -u::
        The commit log message, author name and author email are
        taken from the e-mail, and after minimally decoding MIME
index 8beb42dbb9952059650d51d2e7a5ca2efe9591fb..5cfdc0cfc553dc01f6f1c7cb185e79449ee0b0a8 100644 (file)
@@ -79,6 +79,17 @@ style string if it contains an LF.
 +
 Supported if the helper has the "push" capability.
 
+'import' <name>::
+       Produces a fast-import stream which imports the current value
+       of the named ref. It may additionally import other refs as
+       needed to construct the history efficiently. The script writes
+       to a helper-specific private namespace. The value of the named
+       ref should be written to a location in this namespace derived
+       by applying the refspecs from the "refspec" capability to the
+       name of the ref.
++
+Supported if the helper has the "import" capability.
+
 If a fatal error occurs, the program writes the error message to
 stderr and exits. The caller should expect that a suitable error
 message has been printed if the child closes the connection without
@@ -99,6 +110,19 @@ CAPABILITIES
 'push'::
        This helper supports the 'push' command.
 
+'import'::
+       This helper supports the 'import' command.
+
+'refspec' 'spec'::
+       When using the import command, expect the source ref to have
+       been written to the destination ref. The earliest applicable
+       refspec takes precedence. For example
+       "refs/heads/*:refs/svn/origin/branches/*" means that, after an
+       "import refs/heads/name", the script has written to
+       refs/svn/origin/branches/name. If this capability is used at
+       all, it must cover all refs reported by the list command; if
+       it is not used, it is effectively "*:*"
+
 REF LIST ATTRIBUTES
 -------------------
 
@@ -107,6 +131,10 @@ REF LIST ATTRIBUTES
        commands.  A helper might chose to acquire the ref list by
        opening a different type of connection to the destination.
 
+'unchanged'::
+       This ref is unchanged since the last import or fetch, although
+       the helper cannot necessarily determine what value that produced.
+
 OPTIONS
 -------
 'option verbosity' <N>::
index c85d7f4385b91ce296c0247e3c9b93059abff19d..ced35b2f532dde3580f162a0c23b642002a0e508 100644 (file)
@@ -84,7 +84,7 @@ See the CONFIGURATION section for 'sendemail.multiedit'.
 --in-reply-to=<identifier>::
        Specify the contents of the first In-Reply-To header.
        Subsequent emails will refer to the previous email
-       instead of this if --chain-reply-to is set (the default)
+       instead of this if --chain-reply-to is set.
        Only necessary if --compose is also set.  If --compose
        is not set, this will be prompted for.
 
@@ -108,9 +108,10 @@ Sending
 --envelope-sender=<address>::
        Specify the envelope sender used to send the emails.
        This is useful if your default address is not the address that is
-       subscribed to a list. If you use the sendmail binary, you must have
-       suitable privileges for the -f parameter. Default is the value of
-       the 'sendemail.envelopesender' configuration variable; if that is
+       subscribed to a list. In order to use the 'From' address, set the
+       value to "auto". If you use the sendmail binary, you must have
+       suitable privileges for the -f parameter.  Default is the value of the
+       'sendemail.envelopesender' configuration variable; if that is
        unspecified, choosing the envelope sender is left to your MTA.
 
 --smtp-encryption=<encryption>::
@@ -171,8 +172,8 @@ Automating
        email sent.  If disabled with "--no-chain-reply-to", all emails after
        the first will be sent as replies to the first email sent.  When using
        this, it is recommended that the first file given be an overview of the
-       entire patch series. Default is the value of the 'sendemail.chainreplyto'
-       configuration value; if that is unspecified, default to --chain-reply-to.
+       entire patch series. Disabled by default, but the 'sendemail.chainreplyto'
+       configuration variable can be used to enable it.
 
 --identity=<identity>::
        A configuration identity. When given, causes values in the
index 84f60f3407499c40a8e0caadf9d40ed5e9b8386b..b3dfa42cc0211dbd8881f471ac287fa0b9cd5915 100644 (file)
@@ -8,7 +8,7 @@ git-status - Show the working tree status
 
 SYNOPSIS
 --------
-'git status' <options>...
+'git status' [<options>...] [--] [<pathspec>...]
 
 DESCRIPTION
 -----------
@@ -20,25 +20,90 @@ are what you _would_ commit by running `git commit`; the second and
 third are what you _could_ commit by running 'git-add' before running
 `git commit`.
 
-The command takes the same set of options as 'git-commit'; it
-shows what would be committed if the same options are given to
-'git-commit'.
-
-If there is no path that is different between the index file and
-the current HEAD commit (i.e., there is nothing to commit by running
-`git commit`), the command exits with non-zero status.
+OPTIONS
+-------
+
+-s::
+--short::
+       Give the output in the short-format.
+
+--porcelain::
+       Give the output in a stable, easy-to-parse format for scripts.
+       Currently this is identical to --short output, but is guaranteed
+       not to change in the future, making it safe for scripts.
+
+-u[<mode>]::
+--untracked-files[=<mode>]::
+       Show untracked files (Default: 'all').
++
+The mode parameter is optional, and is used to specify
+the handling of untracked files. The possible options are:
++
+--
+       - 'no'     - Show no untracked files
+       - 'normal' - Shows untracked files and directories
+       - 'all'    - Also shows individual files in untracked directories.
+--
++
+See linkgit:git-config[1] for configuration variable
+used to change the default for when the option is not
+specified.
+
+-z::
+       Terminate entries with NUL, instead of LF.  This implies
+       the `--porcelain` output format if no other format is given.
 
 
 OUTPUT
 ------
 The output from this command is designed to be used as a commit
 template comment, and all the output lines are prefixed with '#'.
+The default, long format, is designed to be human readable,
+verbose and descriptive.  They are subject to change in any time.
 
 The paths mentioned in the output, unlike many other git commands, are
 made relative to the current directory if you are working in a
 subdirectory (this is on purpose, to help cutting and pasting). See
 the status.relativePaths config option below.
 
+In short-format, the status of each path is shown as
+
+       XY PATH1 -> PATH2
+
+where `PATH1` is the path in the `HEAD`, and ` -> PATH2` part is
+shown only when `PATH1` corresponds to a different path in the
+index/worktree (i.e. renamed).
+
+For unmerged entries, `X` shows the status of stage #2 (i.e. ours) and `Y`
+shows the status of stage #3 (i.e. theirs).
+
+For entries that do not have conflicts, `X` shows the status of the index,
+and `Y` shows the status of the work tree.  For untracked paths, `XY` are
+`??`.
+
+    X          Y     Meaning
+    -------------------------------------------------
+              [MD]   not updated
+    M        [ MD]   updated in index
+    A        [ MD]   added to index
+    D        [ MD]   deleted from index
+    R        [ MD]   renamed in index
+    C        [ MD]   copied in index
+    [MARC]           index and work tree matches
+    [ MARC]     M    work tree changed since index
+    [ MARC]     D    deleted in work tree
+    -------------------------------------------------
+    D           D    unmerged, both deleted
+    A           U    unmerged, added by us
+    U           D    unmerged, deleted by them
+    U           A    unmerged, added by them
+    D           U    unmerged, deleted by us
+    A           A    unmerged, both added
+    U           U    unmerged, both modified
+    -------------------------------------------------
+    ?           ?    untracked
+    -------------------------------------------------
+
 
 CONFIGURATION
 -------------
@@ -53,9 +118,9 @@ paths shown are relative to the repository root, not to the current
 directory.
 
 If `status.submodulesummary` is set to a non zero number or true (identical
-to -1 or an unlimited number), the submodule summary will be enabled and a
-summary of commits for modified submodules will be shown (see --summary-limit
-option of linkgit:git-submodule[1]).
+to -1 or an unlimited number), the submodule summary will be enabled for
+the long format and a summary of commits for modified submodules will be
+shown (see --summary-limit option of linkgit:git-submodule[1]).
 
 SEE ALSO
 --------
@@ -63,8 +128,7 @@ linkgit:gitignore[5]
 
 Author
 ------
-Written by Linus Torvalds <torvalds@osdl.org> and
-Junio C Hamano <gitster@pobox.com>.
+Written by Junio C Hamano <gitster@pobox.com>.
 
 Documentation
 --------------
index 63f3b5c7425cd0b76a6931d92872424310f5fbce..b8e49dce4a19a4d7083459468f27c273c1d91fea 100644 (file)
@@ -20,8 +20,6 @@ The UI for the protocol is on the 'git-fetch-pack' side, and the
 program pair is meant to be used to pull updates from a remote
 repository.  For push operations, see 'git-send-pack'.
 
-After finishing the operation successfully, `post-upload-pack`
-hook is called (see linkgit:githooks[5]).
 
 OPTIONS
 -------
index 8e577cc4fe2fd445ef238efc812d12b58156fec4..352c23019f7dad59c735f88c0606cf056c84a578 100644 (file)
@@ -43,9 +43,18 @@ unreleased) version of git, that is available from 'master'
 branch of the `git.git` repository.
 Documentation for older releases are available here:
 
-* link:v1.6.5.3/git.html[documentation for release 1.6.5.3]
+* link:v1.6.6/git.html[documentation for release 1.6.6]
 
 * release notes for
+  link:RelNotes-1.6.6.txt[1.6.6].
+
+* link:v1.6.5.7/git.html[documentation for release 1.6.5.7]
+
+* release notes for
+  link:RelNotes-1.6.5.7.txt[1.6.5.7],
+  link:RelNotes-1.6.5.6.txt[1.6.5.6],
+  link:RelNotes-1.6.5.5.txt[1.6.5.5],
+  link:RelNotes-1.6.5.4.txt[1.6.5.4],
   link:RelNotes-1.6.5.3.txt[1.6.5.3],
   link:RelNotes-1.6.5.2.txt[1.6.5.2],
   link:RelNotes-1.6.5.1.txt[1.6.5.1],
index 1f472cea59a64023d91d25b09cc4d6f0aa39bc28..5a45e51890b1f4b13db3d8082da83731dc76ceac 100644 (file)
@@ -197,6 +197,25 @@ intent is that if someone unsets the filter driver definition,
 or does not have the appropriate filter program, the project
 should still be usable.
 
+For example, in .gitattributes, you would assign the `filter`
+attribute for paths.
+
+------------------------
+*.c    filter=indent
+------------------------
+
+Then you would define a "filter.indent.clean" and "filter.indent.smudge"
+configuration in your .git/config to specify a pair of commands to
+modify the contents of C programs when the source files are checked
+in ("clean" is run) and checked out (no change is made because the
+command is "cat").
+
+------------------------
+[filter "indent"]
+       clean = indent
+       smudge = cat
+------------------------
+
 
 Interaction between checkin/checkout attributes
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
index e237394397199cbb9b72f1f7989d0b51667e9a6d..f762dca440d264cc8c9ccf34ad217e174a9bdf77 100644 (file)
@@ -602,7 +602,7 @@ $ git tag -s <tagname>
 ----------------
 
 which will sign the current `HEAD` (but you can also give it another
-argument that specifies the thing to tag, i.e., you could have tagged the
+argument that specifies the thing to tag, e.g., you could have tagged the
 current `mybranch` point by using `git tag <tagname> mybranch`).
 
 You normally only do signed tags for major releases or things
index 4cc3d1387fbdb60a2134a2e399fa46140939fe2f..29eeae77ca8716a9b07362f164104e3afee4f192 100644 (file)
@@ -310,35 +310,6 @@ Both standard output and standard error output are forwarded to
 'git-send-pack' on the other end, so you can simply `echo` messages
 for the user.
 
-post-upload-pack
-----------------
-
-After upload-pack successfully finishes its operation, this hook is called
-for logging purposes.
-
-The hook is passed various pieces of information, one per line, from its
-standard input.  Currently the following items can be fed to the hook, but
-more types of information may be added in the future:
-
-want SHA-1::
-    40-byte hexadecimal object name the client asked to include in the
-    resulting pack.  Can occur one or more times in the input.
-
-have SHA-1::
-    40-byte hexadecimal object name the client asked to exclude from
-    the resulting pack, claiming to have them already.  Can occur zero
-    or more times in the input.
-
-time float::
-    Number of seconds spent for creating the packfile.
-
-size decimal::
-    Size of the resulting packfile in bytes.
-
-kind string:
-    Either "clone" (when the client did not give us any "have", and asked
-    for all our refs with "want"), or "fetch" (otherwise).
-
 pre-auto-gc
 ~~~~~~~~~~~
 
diff --git a/Documentation/manpage-base-url.xsl.in b/Documentation/manpage-base-url.xsl.in
new file mode 100644 (file)
index 0000000..e800904
--- /dev/null
@@ -0,0 +1,10 @@
+<!-- manpage-base-url.xsl:
+     special settings for manpages rendered from newer docbook -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+               version="1.0">
+
+<!-- set a base URL for relative links -->
+<xsl:param name="man.base.url.for.relative.links"
+       >@@MAN_BASE_URL@@</xsl:param>
+
+</xsl:stylesheet>
index 0683fb3a3d34e89c67c6e37f2ee96adcd2821ea0..53a9168ba7d8959e65c2442f6a078155d6f24c71 100644 (file)
@@ -144,6 +144,14 @@ insert an empty string unless we are traversing reflog entries (e.g., by
 `git log -g`). The `%d` placeholder will use the "short" decoration
 format if `--decorate` was not already provided on the command line.
 
+If you add a `{plus}` (plus sign) after '%' of a placeholder, a line-feed
+is inserted immediately before the expansion if and only if the
+placeholder expands to a non-empty string.
+
+If you add a `-` (minus sign) after '%' of a placeholder, line-feeds that
+immediately precede the expansion are deleted if and only if the
+placeholder expands to an empty string.
+
 * 'tformat:'
 +
 The 'tformat:' format works exactly like 'format:', except that it
index c784d3edcb2537b84bfb5db3da55faaf45995155..e5061e0677e05f8127b0808104269961ee21f830 100644 (file)
@@ -1,6 +1,52 @@
 hash API
 ========
 
-Talk about <hash.h>
+The hash API is a collection of simple hash table functions. Users are expected
+to implement their own hashing.
 
-(Linus)
+Data Structures
+---------------
+
+`struct hash_table`::
+
+       The hash table structure. The `array` member points to the hash table
+       entries. The `size` member counts the total number of valid and invalid
+       entries in the table. The `nr` member keeps track of the number of
+       valid entries.
+
+`struct hash_table_entry`::
+
+       An opaque structure representing an entry in the hash table. The `hash`
+       member is the entry's hash key and the `ptr` member is the entry's
+       value.
+
+Functions
+---------
+
+`init_hash`::
+
+       Initialize the hash table.
+
+`free_hash`::
+
+       Release memory associated with the hash table.
+
+`insert_hash`::
+
+       Insert a pointer into the hash table. If an entry with that hash
+       already exists, a pointer to the existing entry's value is returned.
+       Otherwise NULL is returned.  This allows callers to implement
+       chaining, etc.
+
+`lookup_hash`::
+
+       Lookup an entry in the hash table. If an entry with that hash exists
+       the entry's value is returned. Otherwise NULL is returned.
+
+`for_each_hash`::
+
+       Call a function for each entry in the hash table. The function is
+       expected to take the entry's value as its only argument and return an
+       int. If the function returns a negative int the loop is aborted
+       immediately.  Otherwise, the return value is accumulated and the sum
+       returned upon completion of the loop.
index 7438149249364ca8837811771b072b20990b3a5d..a0e0f850f83fe164dd7c1ca87d001fe485ba2ec2 100644 (file)
@@ -12,7 +12,7 @@ strbuf API actually relies on the string being free of NULs.
 
 strbufs has some invariants that are very important to keep in mind:
 
-. The `buf` member is never NULL, so you it can be used in any usual C
+. The `buf` member is never NULL, so it can be used in any usual C
 string operations safely. strbuf's _have_ to be initialized either by
 `strbuf_init()` or by `= STRBUF_INIT` before the invariants, though.
 +
@@ -55,7 +55,7 @@ Data structures
 
 * `struct strbuf`
 
-This is string buffer structure. The `len` member can be used to
+This is the string buffer structure. The `len` member can be used to
 determine the current length of the string, and `buf` member provides access to
 the string itself.
 
@@ -253,3 +253,9 @@ same behaviour as well.
        comments are considered contents to be removed or not.
 
 `launch_editor`::
+
+       Launch the user preferred editor to edit a file and fill the buffer
+       with the file's contents upon the user completing their editing. The
+       third argument can be used to set the environment which the editor is
+       run in. If the buffer is NULL the editor is launched as usual but the
+       file's contents are not read into the buffer upon completion.
index 710d361233e26a8cc2da94f87f8b436b5af3419b..a7d8c63427c91fa7c033bbf7dc5150e0e292bf25 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.6.5.GIT
+DEF_VER=v1.6.6.GIT
 
 LF='
 '
index b981364536835e704408cb7ddf76e42ef0183871..be1838953451f78443ab1a97f569428b2001fcfb 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -168,6 +168,8 @@ all::
 #
 # Define NO_PERL if you do not want Perl scripts or libraries at all.
 #
+# Define NO_PYTHON if you do not want Python scripts or libraries at all.
+#
 # Define NO_TCLTK if you do not want Tcl/Tk GUI.
 #
 # The TCL_PATH variable governs the location of the Tcl interpreter
@@ -205,6 +207,9 @@ all::
 #
 # Define NO_REGEX if you have no or inferior regex support in your C library.
 #
+# Define JSMIN to point to JavaScript minifier that functions as
+# a filter to have gitweb.js minified.
+#
 # Define DEFAULT_PAGER to a sensible pager command (defaults to "less") if
 # you want to use something different.  The value will be interpreted by the
 # shell at runtime when it is used.
@@ -274,6 +279,9 @@ lib = lib
 # DESTDIR=
 pathsep = :
 
+# JavaScript minifier invocation that can function as filter
+JSMIN =
+
 # default configuration for gitweb
 GITWEB_CONFIG = gitweb_config.perl
 GITWEB_CONFIG_SYSTEM = /etc/gitweb.conf
@@ -289,6 +297,11 @@ GITWEB_HOMETEXT = indextext.html
 GITWEB_CSS = gitweb.css
 GITWEB_LOGO = git-logo.png
 GITWEB_FAVICON = git-favicon.png
+ifdef JSMIN
+GITWEB_JS = gitweb.min.js
+else
+GITWEB_JS = gitweb.js
+endif
 GITWEB_SITE_HEADER =
 GITWEB_SITE_FOOTER =
 
@@ -330,6 +343,7 @@ LIB_H =
 LIB_OBJS =
 PROGRAMS =
 SCRIPT_PERL =
+SCRIPT_PYTHON =
 SCRIPT_SH =
 TEST_PROGRAMS =
 
@@ -368,6 +382,7 @@ SCRIPT_PERL += git-svn.perl
 
 SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
          $(patsubst %.perl,%,$(SCRIPT_PERL)) \
+         $(patsubst %.py,%,$(SCRIPT_PYTHON)) \
          git-instaweb
 
 # Empty...
@@ -432,8 +447,12 @@ endif
 ifndef PERL_PATH
        PERL_PATH = /usr/bin/perl
 endif
+ifndef PYTHON_PATH
+       PYTHON_PATH = /usr/bin/python
+endif
 
 export PERL_PATH
+export PYTHON_PATH
 
 LIB_FILE=libgit.a
 XDIFF_LIB=xdiff/lib.a
@@ -826,6 +845,7 @@ ifeq ($(uname_O),Cygwin)
 endif
 ifeq ($(uname_S),FreeBSD)
        NEEDS_LIBICONV = YesPlease
+       OLD_ICONV = YesPlease
        NO_MEMMEM = YesPlease
        BASIC_CFLAGS += -I/usr/local/include
        BASIC_LDFLAGS += -L/usr/local/lib
@@ -1344,6 +1364,10 @@ ifeq ($(PERL_PATH),)
 NO_PERL=NoThanks
 endif
 
+ifeq ($(PYTHON_PATH),)
+NO_PYTHON=NoThanks
+endif
+
 QUIET_SUBDIR0  = +$(MAKE) -C # space to separate -C and subdir
 QUIET_SUBDIR1  =
 
@@ -1391,6 +1415,7 @@ prefix_SQ = $(subst ','\'',$(prefix))
 
 SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
 PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
+PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
 TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
 
 LIBS = $(GITLIBS) $(EXTLIBS)
@@ -1437,6 +1462,9 @@ ifndef NO_TCLTK
 endif
 ifndef NO_PERL
        $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
+endif
+ifndef NO_PYTHON
+       $(QUIET_SUBDIR0)git_remote_helpers $(QUIET_SUBDIR1) PYTHON_PATH='$(PYTHON_PATH_SQ)' prefix='$(prefix_SQ)' all
 endif
        $(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1)
 
@@ -1507,8 +1535,13 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
        chmod +x $@+ && \
        mv $@+ $@
 
+ifdef JSMIN
+OTHER_PROGRAMS += gitweb/gitweb.cgi   gitweb/gitweb.min.js
+gitweb/gitweb.cgi: gitweb/gitweb.perl gitweb/gitweb.min.js
+else
 OTHER_PROGRAMS += gitweb/gitweb.cgi
 gitweb/gitweb.cgi: gitweb/gitweb.perl
+endif
        $(QUIET_GEN)$(RM) $@ $@+ && \
        sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \
            -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \
@@ -1527,13 +1560,14 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl
            -e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \
            -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_HEADER++|$(GITWEB_SITE_HEADER)|g' \
            -e 's|++GITWEB_SITE_FOOTER++|$(GITWEB_SITE_FOOTER)|g' \
            $< >$@+ && \
        chmod +x $@+ && \
        mv $@+ $@
 
-git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css
+git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css gitweb/gitweb.js
        $(QUIET_GEN)$(RM) $@ $@+ && \
        sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
            -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
@@ -1542,6 +1576,8 @@ git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css
            -e '/@@GITWEB_CGI@@/d' \
            -e '/@@GITWEB_CSS@@/r gitweb/gitweb.css' \
            -e '/@@GITWEB_CSS@@/d' \
+           -e '/@@GITWEB_JS@@/r gitweb/gitweb.js' \
+           -e '/@@GITWEB_JS@@/d' \
            -e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \
            $@.sh > $@+ && \
        chmod +x $@+ && \
@@ -1556,6 +1592,41 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)) git-instaweb: % : unimplemented.sh
        mv $@+ $@
 endif # NO_PERL
 
+
+ifdef JSMIN
+gitweb/gitweb.min.js: gitweb/gitweb.js
+       $(QUIET_GEN)$(JSMIN) <$< >$@
+endif # JSMIN
+
+ifndef NO_PYTHON
+$(patsubst %.py,%,$(SCRIPT_PYTHON)): GIT-CFLAGS
+$(patsubst %.py,%,$(SCRIPT_PYTHON)): % : %.py
+       $(QUIET_GEN)$(RM) $@ $@+ && \
+       INSTLIBDIR=`MAKEFLAGS= $(MAKE) -C git_remote_helpers -s \
+               --no-print-directory prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' \
+               instlibdir` && \
+       sed -e '1{' \
+           -e '        s|#!.*python|#!$(PYTHON_PATH_SQ)|' \
+           -e '}' \
+           -e 's|^import sys.*|&; \\\
+                  import os; \\\
+                  sys.path[0] = os.environ.has_key("GITPYTHONLIB") and \\\
+                                os.environ["GITPYTHONLIB"] or \\\
+                                "@@INSTLIBDIR@@"|' \
+           -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \
+           $@.py >$@+ && \
+       chmod +x $@+ && \
+       mv $@+ $@
+else # NO_PYTHON
+$(patsubst %.py,%,$(SCRIPT_PYTHON)): % : unimplemented.sh
+       $(QUIET_GEN)$(RM) $@ $@+ && \
+       sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
+           -e 's|@@REASON@@|NO_PYTHON=$(NO_PYTHON)|g' \
+           unimplemented.sh >$@+ && \
+       chmod +x $@+ && \
+       mv $@+ $@
+endif # NO_PYTHON
+
 configure: configure.ac
        $(QUIET_GEN)$(RM) $@ $<+ && \
        sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
@@ -1573,7 +1644,7 @@ git.o git.spec \
        $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $<
 %.s: %.c GIT-CFLAGS
        $(QUIET_CC)$(CC) -S $(ALL_CFLAGS) $<
-%.o: %.S
+%.o: %.S GIT-CFLAGS
        $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $<
 
 exec_cmd.o: exec_cmd.c GIT-CFLAGS
@@ -1682,6 +1753,7 @@ GIT-BUILD-OPTIONS: .FORCE-GIT-BUILD-OPTIONS
        @echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@
        @echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@
        @echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@
+       @echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@
 
 ### Detect Tck/Tk interpreter path changes
 ifndef NO_TCLTK
@@ -1794,6 +1866,9 @@ install: all
 ifndef NO_PERL
        $(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
 endif
+ifndef NO_PYTHON
+       $(MAKE) -C git_remote_helpers prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
+endif
 ifndef NO_TCLTK
        $(MAKE) -C gitk-git install
        $(MAKE) -C git-gui gitexecdir='$(gitexec_instdir_SQ)' install
@@ -1911,6 +1986,9 @@ clean:
 ifndef NO_PERL
        $(RM) gitweb/gitweb.cgi
        $(MAKE) -C perl clean
+endif
+ifndef NO_PYTHON
+       $(MAKE) -C git_remote_helpers clean
 endif
        $(MAKE) -C templates/ clean
        $(MAKE) -C t/ clean
index 5274ceed4e7ecd0210ad8338a0b3570e3648b05e..7b9bde663bd5b2b8eec6b7e91c1de275f6afa460 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes-1.6.6.txt
\ No newline at end of file
+Documentation/RelNotes-1.7.0.txt
\ No newline at end of file
index dc18db8af96b093d7cb16eb88d3f738f98dc30f6..f1a1f84aa04102378ffe37b8e5b617a6fffeb2fa 100644 (file)
--- a/bisect.c
+++ b/bisect.c
@@ -813,11 +813,11 @@ static void handle_skipped_merge_base(const unsigned char *mb)
        char *bad_hex = sha1_to_hex(current_bad_sha1);
        char *good_hex = join_sha1_array_hex(&good_revs, ' ');
 
-       fprintf(stderr, "Warning: the merge base between %s and [%s] "
+       warning("the merge base between %s and [%s] "
                "must be skipped.\n"
                "So we cannot be sure the first bad commit is "
                "between %s and %s.\n"
-               "We continue anyway.\n",
+               "We continue anyway.",
                bad_hex, good_hex, mb_hex, bad_hex);
        free(good_hex);
 }
index 12351e9dd5c481fddc8db07c00a211b4620250bd..446d6bff30ed47a5cf308310e3f28c5b30648a2c 100644 (file)
@@ -106,13 +106,17 @@ int cmd_archive(int argc, const char **argv, const char *prefix)
        if (format) {
                sprintf(fmt_opt, "--format=%s", format);
                /*
-                * This is safe because either --format and/or --output must
-                * have been given on the original command line if we get to
-                * this point, and parse_options() must have eaten at least
-                * one argument, i.e. we have enough room to append to argv[].
+                * We have enough room in argv[] to muck it in place,
+                * because either --format and/or --output must have
+                * been given on the original command line if we get
+                * to this point, and parse_options() must have eaten
+                * it, i.e. we can add back one element to the array.
+                * But argv[] may contain "--"; we should make it the
+                * first option.
                 */
-               argv[argc++] = fmt_opt;
-               argv[argc] = NULL;
+               memmove(argv + 2, argv + 1, sizeof(*argv) * argc);
+               argv[1] = fmt_opt;
+               argv[++argc] = NULL;
        }
 
        if (remote)
index d4e25a595dadba43223e84b20759ab49343aa33a..6408ec8ee6805fc42eb5e5dc52be6463d62128bf 100644 (file)
@@ -1305,6 +1305,7 @@ static void get_ac_line(const char *inbuf, const char *what,
        error_out:
                /* Ugh */
                *tz = "(unknown)";
+               strcpy(person, *tz);
                strcpy(mail, *tz);
                *time = 0;
                return;
@@ -1314,20 +1315,26 @@ static void get_ac_line(const char *inbuf, const char *what,
        tmp = person;
        tmp += len;
        *tmp = 0;
-       while (*tmp != ' ')
+       while (person < tmp && *tmp != ' ')
                tmp--;
+       if (tmp <= person)
+               goto error_out;
        *tz = tmp+1;
        tzlen = (person+len)-(tmp+1);
 
        *tmp = 0;
-       while (*tmp != ' ')
+       while (person < tmp && *tmp != ' ')
                tmp--;
+       if (tmp <= person)
+               goto error_out;
        *time = strtoul(tmp, NULL, 10);
        timepos = tmp;
 
        *tmp = 0;
-       while (*tmp != ' ')
+       while (person < tmp && *tmp != ' ')
                tmp--;
+       if (tmp <= person)
+               return;
        mailpos = tmp + 1;
        *tmp = 0;
        maillen = timepos - tmp;
index 05e876e28554fec546ee4256bf3576dcb8017047..ddc9f2dab7f3b55e4cd0cffbf303a3ad108fd431 100644 (file)
@@ -65,7 +65,7 @@ static int parse_branch_color_slot(const char *var, int ofs)
                return BRANCH_COLOR_LOCAL;
        if (!strcasecmp(var+ofs, "current"))
                return BRANCH_COLOR_CURRENT;
-       die("bad config variable '%s'", var);
+       return -1;
 }
 
 static int git_branch_config(const char *var, const char *value, void *cb)
@@ -76,6 +76,8 @@ static int git_branch_config(const char *var, const char *value, void *cb)
        }
        if (!prefixcmp(var, "color.branch.")) {
                int slot = parse_branch_color_slot(var, 13);
+               if (slot < 0)
+                       return 0;
                if (!value)
                        return config_error_nonbool(var);
                color_parse(value, var, branch_colors[slot]);
@@ -636,10 +638,12 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                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 (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,
                              force_create, reflog, track);
-       else
+       else
                usage_with_options(builtin_branch_usage, options);
 
        return 0;
index caf3025031d83dc860b22c7798dd5f40b6b89ed2..5df8b0f72c8bb5dfa7fa7df1edfec90405031f47 100644 (file)
@@ -362,9 +362,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        const char *repo_name, *repo, *work_tree, *git_dir;
        char *path, *dir;
        int dest_exists;
-       const struct ref *refs, *remote_head, *mapped_refs;
+       const struct ref *refs, *remote_head;
        const struct ref *remote_head_points_at;
        const struct ref *our_head_points_at;
+       struct ref *mapped_refs;
        struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
        struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
        struct transport *transport = NULL;
index 09d28405ecde6112b799a423775e961ccdfd0789..3dfcd77c45253f3fb8a0c50ed1cff44c365dce90 100644 (file)
@@ -24,6 +24,7 @@
 #include "string-list.h"
 #include "rerere.h"
 #include "unpack-trees.h"
+#include "quote.h"
 
 static const char * const builtin_commit_usage[] = {
        "git commit [options] [--] <filepattern>...",
@@ -35,7 +36,7 @@ static const char * const builtin_status_usage[] = {
        NULL
 };
 
-static unsigned char head_sha1[20], merge_head_sha1[20];
+static unsigned char head_sha1[20];
 static char *use_message_buffer;
 static const char commit_editmsg[] = "COMMIT_EDITMSG";
 static struct lock_file index_lock; /* real index */
@@ -51,7 +52,7 @@ static const char *template_file;
 static char *edit_message, *use_message;
 static char *author_name, *author_email, *author_date;
 static int all, edit_flag, also, interactive, only, amend, signoff;
-static int quiet, verbose, no_verify, allow_empty, dry_run;
+static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
 static char *untracked_files_arg;
 /*
  * The default commit message cleanup mode will remove the lines
@@ -71,6 +72,13 @@ static int use_editor = 1, initial_commit, in_merge;
 static const char *only_include_assumed;
 static struct strbuf message;
 
+static int null_termination;
+static enum {
+       STATUS_FORMAT_LONG,
+       STATUS_FORMAT_SHORT,
+       STATUS_FORMAT_PORCELAIN,
+} status_format = STATUS_FORMAT_LONG;
+
 static int opt_parse_m(const struct option *opt, const char *arg, int unset)
 {
        struct strbuf *buf = opt->value;
@@ -86,16 +94,19 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset)
 static struct option builtin_commit_options[] = {
        OPT__QUIET(&quiet),
        OPT__VERBOSE(&verbose),
-       OPT_GROUP("Commit message options"),
 
+       OPT_GROUP("Commit message options"),
        OPT_FILENAME('F', "file", &logfile, "read log from file"),
        OPT_STRING(0, "author", &force_author, "AUTHOR", "override author for commit"),
        OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m),
-       OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit "),
+       OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit"),
        OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"),
+       OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"),
        OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
        OPT_FILENAME('t', "template", &template_file, "use specified template file"),
        OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"),
+       OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
+       /* end commit message options */
 
        OPT_GROUP("Commit contents options"),
        OPT_BOOLEAN('a', "all", &all, "commit all changed files"),
@@ -104,10 +115,16 @@ static struct option builtin_commit_options[] = {
        OPT_BOOLEAN('o', "only", &only, "commit only specified files"),
        OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"),
        OPT_BOOLEAN(0, "dry-run", &dry_run, "show what would be committed"),
+       OPT_SET_INT(0, "short", &status_format, "show status concisely",
+                   STATUS_FORMAT_SHORT),
+       OPT_SET_INT(0, "porcelain", &status_format,
+                   "show porcelain output format", STATUS_FORMAT_PORCELAIN),
+       OPT_BOOLEAN('z', "null", &null_termination,
+                   "terminate entries with NUL"),
        OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
        { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
        OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"),
-       OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
+       /* end commit contents options */
 
        OPT_END()
 };
@@ -305,7 +322,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
         */
        commit_style = COMMIT_PARTIAL;
 
-       if (file_exists(git_path("MERGE_HEAD")))
+       if (in_merge)
                die("cannot do a partial commit during a merge.");
 
        memset(&partial, 0, sizeof(partial));
@@ -346,6 +363,8 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
 static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn,
                      struct wt_status *s)
 {
+       unsigned char sha1[20];
+
        if (s->relative_paths)
                s->prefix = prefix;
 
@@ -357,8 +376,21 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int
        s->index_file = index_file;
        s->fp = fp;
        s->nowarn = nowarn;
+       s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0;
 
-       wt_status_print(s);
+       wt_status_collect(s);
+
+       switch (status_format) {
+       case STATUS_FORMAT_SHORT:
+               wt_shortstatus_print(s, null_termination);
+               break;
+       case STATUS_FORMAT_PORCELAIN:
+               wt_porcelain_print(s, null_termination);
+               break;
+       case STATUS_FORMAT_LONG:
+               wt_status_print(s);
+               break;
+       }
 
        return s->commitable;
 }
@@ -381,7 +413,7 @@ static void determine_author_info(void)
        email = getenv("GIT_AUTHOR_EMAIL");
        date = getenv("GIT_AUTHOR_DATE");
 
-       if (use_message) {
+       if (use_message && !renew_authorship) {
                const char *a, *lb, *rb, *eol;
 
                a = strstr(use_message_buffer, "\nauthor ");
@@ -734,6 +766,21 @@ static const char *find_author_by_nickname(const char *name)
        die("No existing author found with '%s'", name);
 }
 
+
+static void handle_untracked_files_arg(struct wt_status *s)
+{
+       if (!untracked_files_arg)
+               ; /* default already initialized */
+       else if (!strcmp(untracked_files_arg, "no"))
+               s->show_untracked_files = SHOW_NO_UNTRACKED_FILES;
+       else if (!strcmp(untracked_files_arg, "normal"))
+               s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
+       else if (!strcmp(untracked_files_arg, "all"))
+               s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
+       else
+               die("Invalid untracked files mode '%s'", untracked_files_arg);
+}
+
 static int parse_and_validate_options(int argc, const char *argv[],
                                      const char * const usage[],
                                      const char *prefix,
@@ -747,6 +794,9 @@ static int parse_and_validate_options(int argc, const char *argv[],
        if (force_author && !strchr(force_author, '>'))
                force_author = find_author_by_nickname(force_author);
 
+       if (force_author && renew_authorship)
+               die("Using both --reset-author and --author does not make sense");
+
        if (logfile || message.len || use_message)
                use_editor = 0;
        if (edit_flag)
@@ -757,9 +807,6 @@ static int parse_and_validate_options(int argc, const char *argv[],
        if (get_sha1("HEAD", head_sha1))
                initial_commit = 1;
 
-       if (!get_sha1("MERGE_HEAD", merge_head_sha1))
-               in_merge = 1;
-
        /* Sanity check options */
        if (amend && initial_commit)
                die("You have nothing to amend.");
@@ -780,6 +827,8 @@ static int parse_and_validate_options(int argc, const char *argv[],
                use_message = edit_message;
        if (amend && !use_message)
                use_message = "HEAD";
+       if (!use_message && renew_authorship)
+               die("--reset-author can be used only with -C, -c or --amend.");
        if (use_message) {
                unsigned char sha1[20];
                static char utf8[] = "UTF-8";
@@ -837,22 +886,18 @@ static int parse_and_validate_options(int argc, const char *argv[],
        else
                die("Invalid cleanup mode %s", cleanup_arg);
 
-       if (!untracked_files_arg)
-               ; /* default already initialized */
-       else if (!strcmp(untracked_files_arg, "no"))
-               s->show_untracked_files = SHOW_NO_UNTRACKED_FILES;
-       else if (!strcmp(untracked_files_arg, "normal"))
-               s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
-       else if (!strcmp(untracked_files_arg, "all"))
-               s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
-       else
-               die("Invalid untracked files mode '%s'", untracked_files_arg);
+       handle_untracked_files_arg(s);
 
        if (all && argc > 0)
                die("Paths with -a does not make sense.");
        else if (interactive && argc > 0)
                die("Paths with --interactive does not make sense.");
 
+       if (null_termination && status_format == STATUS_FORMAT_LONG)
+               status_format = STATUS_FORMAT_PORCELAIN;
+       if (status_format != STATUS_FORMAT_LONG)
+               dry_run = 1;
+
        return argc;
 }
 
@@ -884,7 +929,7 @@ static int parse_status_slot(const char *var, int offset)
                return WT_STATUS_NOBRANCH;
        if (!strcasecmp(var+offset, "unmerged"))
                return WT_STATUS_UNMERGED;
-       die("bad config variable '%s'", var);
+       return -1;
 }
 
 static int git_status_config(const char *k, const char *v, void *cb)
@@ -904,6 +949,8 @@ static int git_status_config(const char *k, const char *v, void *cb)
        }
        if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) {
                int slot = parse_status_slot(k, 13);
+               if (slot < 0)
+                       return 0;
                if (!v)
                        return config_error_nonbool(k);
                color_parse(v, k, s->color_palette[slot]);
@@ -932,17 +979,63 @@ static int git_status_config(const char *k, const char *v, void *cb)
 int cmd_status(int argc, const char **argv, const char *prefix)
 {
        struct wt_status s;
+       unsigned char sha1[20];
+       static struct option builtin_status_options[] = {
+               OPT__VERBOSE(&verbose),
+               OPT_SET_INT('s', "short", &status_format,
+                           "show status concisely", STATUS_FORMAT_SHORT),
+               OPT_SET_INT(0, "porcelain", &status_format,
+                           "show porcelain output format",
+                           STATUS_FORMAT_PORCELAIN),
+               OPT_BOOLEAN('z', "null", &null_termination,
+                           "terminate entries with NUL"),
+               { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg,
+                 "mode",
+                 "show untracked files, optional modes: all, normal, no. (Default: all)",
+                 PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
+               OPT_END(),
+       };
+
+       if (null_termination && status_format == STATUS_FORMAT_LONG)
+               status_format = STATUS_FORMAT_PORCELAIN;
 
        wt_status_prepare(&s);
        git_config(git_status_config, &s);
+       in_merge = file_exists(git_path("MERGE_HEAD"));
+       argc = parse_options(argc, argv, prefix,
+                            builtin_status_options,
+                            builtin_status_usage, 0);
+       handle_untracked_files_arg(&s);
+
+       if (*argv)
+               s.pathspec = get_pathspec(prefix, argv);
+
+       read_cache();
+       refresh_cache(REFRESH_QUIET|REFRESH_UNMERGED);
+       s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0;
+       s.in_merge = in_merge;
+       wt_status_collect(&s);
+
+       if (s.relative_paths)
+               s.prefix = prefix;
        if (s.use_color == -1)
                s.use_color = git_use_color_default;
        if (diff_use_color_default == -1)
                diff_use_color_default = git_use_color_default;
 
-       argc = parse_and_validate_options(argc, argv, builtin_status_usage,
-                                         prefix, &s);
-       return dry_run_commit(argc, argv, prefix, &s);
+       switch (status_format) {
+       case STATUS_FORMAT_SHORT:
+               wt_shortstatus_print(&s, null_termination);
+               break;
+       case STATUS_FORMAT_PORCELAIN:
+               wt_porcelain_print(&s, null_termination);
+               break;
+       case STATUS_FORMAT_LONG:
+               s.verbose = verbose;
+               wt_status_print(&s);
+               break;
+       }
+       return 0;
 }
 
 static void print_summary(const char *prefix, const unsigned char *sha1)
@@ -1018,10 +1111,11 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 
        wt_status_prepare(&s);
        git_config(git_commit_config, &s);
+       in_merge = file_exists(git_path("MERGE_HEAD"));
+       s.in_merge = in_merge;
 
        if (s.use_color == -1)
                s.use_color = git_use_color_default;
-
        argc = parse_and_validate_options(argc, argv, builtin_commit_usage,
                                          prefix, &s);
        if (dry_run) {
index 1b0b6c84ea4279df56b82c5a406adbea59e58513..2bdd8ebde1002e852055385396ef053f26c91ae8 100644 (file)
@@ -11,7 +11,7 @@
 
 static void count_objects(DIR *d, char *path, int len, int verbose,
                          unsigned long *loose,
-                         unsigned long *loose_size,
+                         off_t *loose_size,
                          unsigned long *packed_loose,
                          unsigned long *garbage)
 {
@@ -77,7 +77,7 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix)
        int len = strlen(objdir);
        char *path = xmalloc(len + 50);
        unsigned long loose = 0, packed = 0, packed_loose = 0, garbage = 0;
-       unsigned long loose_size = 0;
+       off_t loose_size = 0;
        struct option opts[] = {
                OPT__VERBOSE(&verbose),
                OPT_END(),
@@ -103,7 +103,7 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix)
        if (verbose) {
                struct packed_git *p;
                unsigned long num_pack = 0;
-               unsigned long size_pack = 0;
+               off_t size_pack = 0;
                if (!packed_git)
                        prepare_packed_git();
                for (p = packed_git; p; p = p->next) {
@@ -116,15 +116,15 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix)
                        num_pack++;
                }
                printf("count: %lu\n", loose);
-               printf("size: %lu\n", loose_size / 1024);
+               printf("size: %lu\n", (unsigned long) (loose_size / 1024));
                printf("in-pack: %lu\n", packed);
                printf("packs: %lu\n", num_pack);
-               printf("size-pack: %lu\n", size_pack / 1024);
+               printf("size-pack: %lu\n", (unsigned long) (size_pack / 1024));
                printf("prune-packable: %lu\n", packed_loose);
                printf("garbage: %lu\n", garbage);
        }
        else
                printf("%lu objects, %lu kilobytes\n",
-                      loose, loose_size / 1024);
+                      loose, (unsigned long) (loose_size / 1024));
        return 0;
 }
index 5b7db616dcf5cc3bb178b2c4dbb989322c6b2374..8654fa7a2dbe2c1ca76a493a4322447de5219b99 100644 (file)
@@ -322,7 +322,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
        if (!fp)
                return error("cannot open %s: %s\n", filename, strerror(errno));
 
-       url = transport_anonymize_url(raw_url);
+       if (raw_url)
+               url = transport_anonymize_url(raw_url);
+       else
+               url = xstrdup("foreign");
        for (rm = ref_map; rm; rm = rm->next) {
                struct ref *ref = NULL;
 
@@ -819,7 +822,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv)
        if (!remote)
                die("Where do you want to fetch from today?");
 
-       transport = transport_get(remote, remote->url[0]);
+       transport = transport_get(remote, NULL);
        if (verbosity >= 2)
                transport->verbose = verbosity <= 3 ? verbosity : 3;
        if (verbosity < 0)
index ca08519d9d6290f3cda4ee92e29e4acbf8325d0c..09ad4b04f9fa860a32580717e61acc942de23388 100644 (file)
@@ -427,9 +427,6 @@ int cmd_help(int argc, const char **argv, const char *prefix)
                return 0;
        }
 
-       setup_git_directory_gently(&nongit);
-       git_config(git_help_config, NULL);
-
        if (!argv[0]) {
                printf("usage: %s\n\n", git_usage_string);
                list_common_cmds_help();
@@ -437,6 +434,9 @@ int cmd_help(int argc, const char **argv, const char *prefix)
                return 0;
        }
 
+       setup_git_directory_gently(&nongit);
+       git_config(git_help_config, NULL);
+
        alias = alias_lookup(argv[0]);
        if (alias && !is_git_command(argv[0])) {
                printf("`git %s' is aliased to `%s'\n", argv[0], alias);
index 33fa6ea6c855df904cfcf30f0b413cb8bca61fa9..41b6df490f7c52edbee880afa9070c1511cdf442 100644 (file)
@@ -567,7 +567,7 @@ static int reopen_stdout(struct commit *commit, struct rev_info *rev)
 
        get_patch_filename(commit, rev->nr, fmt_patch_suffix, &filename);
 
-       if (!DIFF_OPT_TST(&rev->diffopt, QUIET))
+       if (!DIFF_OPT_TST(&rev->diffopt, QUICK))
                fprintf(realstdout, "%s\n", filename.buf + outdir_offset);
 
        if (freopen(filename.buf, "w", stdout) == NULL)
@@ -976,7 +976,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
         */
        argc = parse_options(argc, argv, prefix, builtin_format_patch_options,
                             builtin_format_patch_usage,
-                            PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN);
+                            PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
+                            PARSE_OPT_KEEP_DASHDASH);
 
        if (do_signoff) {
                const char *committer;
index b5bad0c184fc1ebc49759f211781ecd4031fd027..70f5622d9d49aae4080b38ee4487cc6e403a9d2c 100644 (file)
@@ -89,7 +89,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
        remote = remote_get(dest);
        if (!remote->url_nr)
                die("remote %s has no configured URL", dest);
-       transport = transport_get(remote, remote->url[0]);
+       transport = transport_get(remote, NULL);
        if (uploadpack != NULL)
                transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack);
 
index 3c4f0753fe157960e206f50fbedb7f63d1a7f47d..a50ac2256cdbacd76ed44a50804212be07f949db 100644 (file)
@@ -10,6 +10,7 @@
 static FILE *cmitmsg, *patchfile, *fin, *fout;
 
 static int keep_subject;
+static int keep_non_patch_brackets_in_subject;
 static const char *metainfo_charset;
 static struct strbuf line = STRBUF_INIT;
 static struct strbuf name = STRBUF_INIT;
@@ -221,35 +222,41 @@ static int is_multipart_boundary(const struct strbuf *line)
 
 static void cleanup_subject(struct strbuf *subject)
 {
-       char *pos;
-       size_t remove;
-       while (subject->len) {
-               switch (*subject->buf) {
+       size_t at = 0;
+
+       while (at < subject->len) {
+               char *pos;
+               size_t remove;
+
+               switch (subject->buf[at]) {
                case 'r': case 'R':
-                       if (subject->len <= 3)
+                       if (subject->len <= at + 3)
                                break;
-                       if (!memcmp(subject->buf + 1, "e:", 2)) {
-                               strbuf_remove(subject, 0, 3);
+                       if (!memcmp(subject->buf + at + 1, "e:", 2)) {
+                               strbuf_remove(subject, at, 3);
                                continue;
                        }
+                       at++;
                        break;
                case ' ': case '\t': case ':':
-                       strbuf_remove(subject, 0, 1);
+                       strbuf_remove(subject, at, 1);
                        continue;
                case '[':
-                       if ((pos = strchr(subject->buf, ']'))) {
-                               remove = pos - subject->buf;
-                               if (remove <= (subject->len - remove) * 2) {
-                                       strbuf_remove(subject, 0, remove + 1);
-                                       continue;
-                               }
-                       } else
-                               strbuf_remove(subject, 0, 1);
-                       break;
+                       pos = strchr(subject->buf + at, ']');
+                       if (!pos)
+                               break;
+                       remove = pos - subject->buf + at + 1;
+                       if (!keep_non_patch_brackets_in_subject ||
+                           (7 <= remove &&
+                            memmem(subject->buf + at, remove, "PATCH", 5)))
+                               strbuf_remove(subject, at, remove);
+                       else
+                               at += remove;
+                       continue;
                }
-               strbuf_trim(subject);
-               return;
+               break;
        }
+       strbuf_trim(subject);
 }
 
 static void cleanup_space(struct strbuf *sb)
@@ -1014,7 +1021,7 @@ static int git_mailinfo_config(const char *var, const char *value, void *unused)
 }
 
 static const char mailinfo_usage[] =
-       "git mailinfo [-k] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] msg patch < mail >info";
+       "git mailinfo [-k|-b] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] msg patch < mail >info";
 
 int cmd_mailinfo(int argc, const char **argv, const char *prefix)
 {
@@ -1031,6 +1038,8 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)
        while (1 < argc && argv[1][0] == '-') {
                if (!strcmp(argv[1], "-k"))
                        keep_subject = 1;
+               else if (!strcmp(argv[1], "-b"))
+                       keep_non_patch_brackets_in_subject = 1;
                else if (!strcmp(argv[1], "-u"))
                        metainfo_charset = def_charset;
                else if (!strcmp(argv[1], "-n"))
index 57eedd447d0f899a258998bb3039905a5a49babb..f1c84d759dd44f661fb76741d528e43702dfc901 100644 (file)
@@ -71,7 +71,7 @@ static int option_parse_message(const struct option *opt,
        if (unset)
                strbuf_setlen(buf, 0);
        else if (arg) {
-               strbuf_addf(buf, "%s\n\n", arg);
+               strbuf_addf(buf, "%s%s", buf->len ? "\n\n" : "", arg);
                have_message = 1;
        } else
                return error("switch `m' requires a value");
@@ -107,8 +107,8 @@ static struct strategy *get_strategy(const char *name)
                                        found = 1;
                        if (!found)
                                add_cmdname(&not_strategies, ent->name, ent->len);
-                       exclude_cmds(&main_cmds, &not_strategies);
                }
+               exclude_cmds(&main_cmds, &not_strategies);
        }
        if (!is_in_cmdlist(&main_cmds, name) && !is_in_cmdlist(&other_cmds, name)) {
                fprintf(stderr, "Could not find merge strategy '%s'.\n", name);
@@ -656,6 +656,7 @@ static int checkout_fast_forward(unsigned char *head, unsigned char *remote)
        opts.verbose_update = 1;
        opts.merge = 1;
        opts.fn = twoway_merge;
+       opts.msgs = get_porcelain_error_msgs();
 
        trees[nr_trees] = parse_tree_indirect(head);
        if (!trees[nr_trees++])
@@ -798,7 +799,7 @@ static int suggest_conflicts(void)
 static struct commit *is_old_style_invocation(int argc, const char **argv)
 {
        struct commit *second_token = NULL;
-       if (argc > 1) {
+       if (argc > 2) {
                unsigned char second_sha1[20];
 
                if (get_sha1(argv[1], second_sha1))
@@ -936,11 +937,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                 * codepath so we discard the error in this
                 * loop.
                 */
-               for (i = 0; i < argc; i++)
-                       merge_name(argv[i], &msg);
-               fmt_merge_msg(option_log, &msg, &merge_msg);
-               if (merge_msg.len)
-                       strbuf_setlen(&merge_msg, merge_msg.len-1);
+               if (!have_message) {
+                       for (i = 0; i < argc; i++)
+                               merge_name(argv[i], &msg);
+                       fmt_merge_msg(option_log, &msg, &merge_msg);
+                       if (merge_msg.len)
+                               strbuf_setlen(&merge_msg, merge_msg.len-1);
+               }
        }
 
        if (head_invalid || !argc)
index f633d81424f5e41cb85ac660dbdc9f4913852673..82471869a0b677202fb5585e2fca880d16478af8 100644 (file)
@@ -169,9 +169,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
                                 * check both source and destination
                                 */
                                if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
-                                       fprintf(stderr, "Warning: %s;"
-                                                       " will overwrite!\n",
-                                                       bad);
+                                       warning("%s; will overwrite!", bad);
                                        bad = NULL;
                                } else
                                        bad = "Cannot overwrite";
index 356d7c1fd3a6fae9558e93eb4f240242a60da368..dcfb53f1884d2648993214b4378afbd1f83760b0 100644 (file)
@@ -87,6 +87,37 @@ static void setup_default_push_refspecs(void)
        }
 }
 
+static int push_with_options(struct transport *transport, int flags)
+{
+       int err;
+       int nonfastforward;
+       if (receivepack)
+               transport_set_option(transport,
+                                    TRANS_OPT_RECEIVEPACK, receivepack);
+       if (thin)
+               transport_set_option(transport, TRANS_OPT_THIN, "yes");
+
+       if (flags & TRANSPORT_PUSH_VERBOSE)
+               fprintf(stderr, "Pushing to %s\n", transport->url);
+       err = transport_push(transport, refspec_nr, refspec, flags,
+                            &nonfastforward);
+       if (err != 0)
+               error("failed to push some refs to '%s'", transport->url);
+
+       err |= transport_disconnect(transport);
+
+       if (!err)
+               return 0;
+
+       if (nonfastforward && advice_push_nonfastforward) {
+               printf("To prevent you from losing history, non-fast-forward updates were rejected\n"
+                      "Merge the remote changes before pushing again.  See the 'non-fast-forward'\n"
+                      "section of 'git push --help' for details.\n");
+       }
+
+       return 1;
+}
+
 static int do_push(const char *repo, int flags)
 {
        int i, errs;
@@ -135,33 +166,19 @@ static int do_push(const char *repo, int flags)
                url = remote->url;
                url_nr = remote->url_nr;
        }
-       for (i = 0; i < url_nr; i++) {
-               struct transport *transport =
-                       transport_get(remote, url[i]);
-               int err;
-               int nonfastforward;
-               if (receivepack)
-                       transport_set_option(transport,
-                                            TRANS_OPT_RECEIVEPACK, receivepack);
-               if (thin)
-                       transport_set_option(transport, TRANS_OPT_THIN, "yes");
-
-               if (flags & TRANSPORT_PUSH_VERBOSE)
-                       fprintf(stderr, "Pushing to %s\n", url[i]);
-               err = transport_push(transport, refspec_nr, refspec, flags,
-                                    &nonfastforward);
-               err |= transport_disconnect(transport);
-
-               if (!err)
-                       continue;
-
-               error("failed to push some refs to '%s'", url[i]);
-               if (nonfastforward && advice_push_nonfastforward) {
-                       printf("To prevent you from losing history, non-fast-forward updates were rejected\n"
-                              "Merge the remote changes before pushing again.  See the 'non-fast-forward'\n"
-                              "section of 'git push --help' for details.\n");
+       if (url_nr) {
+               for (i = 0; i < url_nr; i++) {
+                       struct transport *transport =
+                               transport_get(remote, url[i]);
+                       if (push_with_options(transport, flags))
+                               errs++;
                }
-               errs++;
+       } else {
+               struct transport *transport =
+                       transport_get(remote, NULL);
+
+               if (push_with_options(transport, flags))
+                       errs++;
        }
        return !!errs;
 }
index 78c0e69cdc9fe2d21e83947932cb353c0553452e..4320c93e700a08911e42e3e949656af67b675244 100644 (file)
@@ -204,59 +204,47 @@ static int is_ref_checked_out(const char *ref)
        return !strcmp(head_name, ref);
 }
 
-static char *warn_unconfigured_deny_msg[] = {
-       "Updating the currently checked out branch may cause confusion,",
-       "as the index and work tree do not reflect changes that are in HEAD.",
-       "As a result, you may see the changes you just pushed into it",
-       "reverted when you run 'git diff' over there, and you may want",
-       "to run 'git reset --hard' before starting to work to recover.",
+static char *refuse_unconfigured_deny_msg[] = {
+       "By default, updating the current branch in a non-bare repository",
+       "is denied, because it will make the index and work tree inconsistent",
+       "with what you pushed, and will require 'git reset --hard' to match",
+       "the work tree to HEAD.",
        "",
        "You can set 'receive.denyCurrentBranch' configuration variable to",
-       "'refuse' in the remote repository to forbid pushing into its",
-       "current branch."
+       "'ignore' or 'warn' in the remote repository to allow pushing into",
+       "its current branch; however, this is not recommended unless you",
+       "arranged to update its work tree to match what you pushed in some",
+       "other way.",
        "",
-       "To allow pushing into the current branch, you can set it to 'ignore';",
-       "but this is not recommended unless you arranged to update its work",
-       "tree to match what you pushed in some other way.",
-       "",
-       "To squelch this message, you can set it to 'warn'.",
-       "",
-       "Note that the default will change in a future version of git",
-       "to refuse updating the current branch unless you have the",
-       "configuration variable set to either 'ignore' or 'warn'."
+       "To squelch this message and still keep the default behaviour, set",
+       "'receive.denyCurrentBranch' configuration variable to 'refuse'."
 };
 
-static void warn_unconfigured_deny(void)
+static void refuse_unconfigured_deny(void)
 {
        int i;
-       for (i = 0; i < ARRAY_SIZE(warn_unconfigured_deny_msg); i++)
-               warning("%s", warn_unconfigured_deny_msg[i]);
+       for (i = 0; i < ARRAY_SIZE(refuse_unconfigured_deny_msg); i++)
+               error("%s", refuse_unconfigured_deny_msg[i]);
 }
 
-static char *warn_unconfigured_deny_delete_current_msg[] = {
-       "Deleting the current branch can cause confusion by making the next",
-       "'git clone' not check out any file.",
+static char *refuse_unconfigured_deny_delete_current_msg[] = {
+       "By default, deleting the current branch is denied, because the next",
+       "'git clone' won't result in any file checked out, causing confusion.",
        "",
        "You can set 'receive.denyDeleteCurrent' configuration variable to",
-       "'refuse' in the remote repository to disallow deleting the current",
-       "branch.",
-       "",
-       "You can set it to 'ignore' to allow such a delete without a warning.",
+       "'warn' or 'ignore' in the remote repository to allow deleting the",
+       "current branch, with or without a warning message.",
        "",
-       "To make this warning message less loud, you can set it to 'warn'.",
-       "",
-       "Note that the default will change in a future version of git",
-       "to refuse deleting the current branch unless you have the",
-       "configuration variable set to either 'ignore' or 'warn'."
+       "To squelch this message, you can set it to 'refuse'."
 };
 
-static void warn_unconfigured_deny_delete_current(void)
+static void refuse_unconfigured_deny_delete_current(void)
 {
        int i;
        for (i = 0;
-            i < ARRAY_SIZE(warn_unconfigured_deny_delete_current_msg);
+            i < ARRAY_SIZE(refuse_unconfigured_deny_delete_current_msg);
             i++)
-               warning("%s", warn_unconfigured_deny_delete_current_msg[i]);
+               error("%s", refuse_unconfigured_deny_delete_current_msg[i]);
 }
 
 static const char *update(struct command *cmd)
@@ -276,14 +264,14 @@ static const char *update(struct command *cmd)
                switch (deny_current_branch) {
                case DENY_IGNORE:
                        break;
-               case DENY_UNCONFIGURED:
                case DENY_WARN:
                        warning("updating the current branch");
-                       if (deny_current_branch == DENY_UNCONFIGURED)
-                               warn_unconfigured_deny();
                        break;
                case DENY_REFUSE:
+               case DENY_UNCONFIGURED:
                        error("refusing to update checked out branch: %s", name);
+                       if (deny_current_branch == DENY_UNCONFIGURED)
+                               refuse_unconfigured_deny();
                        return "branch is currently checked out";
                }
        }
@@ -305,12 +293,12 @@ static const char *update(struct command *cmd)
                        case DENY_IGNORE:
                                break;
                        case DENY_WARN:
-                       case DENY_UNCONFIGURED:
-                               if (deny_delete_current == DENY_UNCONFIGURED)
-                                       warn_unconfigured_deny_delete_current();
                                warning("deleting the current branch");
                                break;
                        case DENY_REFUSE:
+                       case DENY_UNCONFIGURED:
+                               if (deny_delete_current == DENY_UNCONFIGURED)
+                                       refuse_unconfigured_deny_delete_current();
                                error("refusing to delete the current branch: %s", name);
                                return "deletion of the current branch prohibited";
                        }
index 79166262182a1cb51839e20b8abcef8f6752b59f..a5019397ff840204963e4e4b23d2ca16de1a332a 100644 (file)
@@ -272,7 +272,9 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat
                        die("Could not get fetch map for refspec %s",
                                states->remote->fetch_refspec[i]);
 
-       states->new.strdup_strings = states->tracked.strdup_strings = 1;
+       states->new.strdup_strings = 1;
+       states->tracked.strdup_strings = 1;
+       states->stale.strdup_strings = 1;
        for (ref = fetch_map; ref; ref = ref->next) {
                unsigned char sha1[20];
                if (!ref->peer_ref || read_ref(ref->peer_ref->name, sha1))
@@ -768,7 +770,7 @@ static void clear_push_info(void *util, const char *string)
 static void free_remote_ref_states(struct ref_states *states)
 {
        string_list_clear(&states->new, 0);
-       string_list_clear(&states->stale, 0);
+       string_list_clear(&states->stale, 1);
        string_list_clear(&states->tracked, 0);
        string_list_clear(&states->heads, 0);
        string_list_clear_func(&states->push, clear_push_info);
index 343d6cde48266d63c0aa6826b973e55b48820745..2be9ffb77b38ae687537f3599c9d11e3fbc026a9 100644 (file)
@@ -48,6 +48,8 @@ static void garbage_collect(struct string_list *rr)
 
        git_config(git_rerere_gc_config, NULL);
        dir = opendir(git_path("rr-cache"));
+       if (!dir)
+               die_errno("unable to open rr-cache directory");
        while ((e = readdir(dir))) {
                if (is_dot_or_dotdot(e->d_name))
                        continue;
index 73e60223db844ee2445aff0b2253f0f21e8a5bef..11d1c6e4d6d2ec0580cae3725f6b3a093e68aa67 100644 (file)
@@ -286,8 +286,10 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
        if (reset_type == NONE)
                reset_type = MIXED; /* by default */
 
-       if (reset_type == HARD && is_bare_repository())
-               die("hard reset makes no sense in a bare repository");
+       if ((reset_type == HARD || reset_type == MERGE)
+           && !is_inside_work_tree())
+               die("%s reset requires a work tree",
+                   reset_type_names[reset_type]);
 
        /* Soft reset does not touch the index file nor the working tree
         * at all, but requires them in a good order.  Other resets reset
index 91b604289dd59101cb6477fa49dd9c342e5b39e5..cd97ded4d249b3beb5179150a283e8be43b9ba44 100644 (file)
@@ -322,7 +322,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
        if (revs.bisect)
                bisect_list = 1;
 
-       quiet = DIFF_OPT_TST(&revs.diffopt, QUIET);
+       quiet = DIFF_OPT_TST(&revs.diffopt, QUICK);
        for (i = 1 ; i < argc; i++) {
                const char *arg = argv[i];
 
index 8aa63c7857fb3a704826bf223ddefb3e40f0eaf7..b3b055f68ce59b6b91ef6949bd8c4bd0bed68b55 100644 (file)
@@ -139,8 +139,12 @@ static void read_from_stdin(struct shortlog *log)
 void shortlog_add_commit(struct shortlog *log, struct commit *commit)
 {
        const char *author = NULL, *buffer;
+       struct strbuf buf = STRBUF_INIT;
+       struct strbuf ufbuf = STRBUF_INIT;
+       struct pretty_print_context ctx = {0};
 
-       buffer = commit->buffer;
+       pretty_print_commit(CMIT_FMT_RAW, commit, &buf, &ctx);
+       buffer = buf.buf;
        while (*buffer && *buffer != '\n') {
                const char *eol = strchr(buffer, '\n');
 
@@ -157,20 +161,19 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit)
                die("Missing author: %s",
                    sha1_to_hex(commit->object.sha1));
        if (log->user_format) {
-               struct strbuf buf = STRBUF_INIT;
                struct pretty_print_context ctx = {0};
                ctx.abbrev = DEFAULT_ABBREV;
                ctx.subject = "";
                ctx.after_subject = "";
                ctx.date_mode = DATE_NORMAL;
-               pretty_print_commit(CMIT_FMT_USERFORMAT, commit, &buf, &ctx);
-               insert_one_record(log, author, buf.buf);
-               strbuf_release(&buf);
-               return;
-       }
-       if (*buffer)
+               pretty_print_commit(CMIT_FMT_USERFORMAT, commit, &ufbuf, &ctx);
+               buffer = ufbuf.buf;
+       } else if (*buffer) {
                buffer++;
+       }
        insert_one_record(log, author, !*buffer ? "<none>" : buffer);
+       strbuf_release(&ufbuf);
+       strbuf_release(&buf);
 }
 
 static void get_from_rev(struct rev_info *rev, struct shortlog *log)
index 5b63af1eeb71907f23117bc552fa7a4019f09722..61626912e3bca2571b41fd1256067470dc170cc1 100644 (file)
@@ -524,6 +524,7 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent,
        int i;
        unsigned long lno = 0;
        const char *c_frag = diff_get_color(use_color, DIFF_FRAGINFO);
+       const char *c_func = diff_get_color(use_color, DIFF_FUNCINFO);
        const char *c_new = diff_get_color(use_color, DIFF_FILE_NEW);
        const char *c_old = diff_get_color(use_color, DIFF_FILE_OLD);
        const char *c_plain = diff_get_color(use_color, DIFF_PLAIN);
@@ -588,7 +589,9 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent,
                                    comment_end = i;
                        }
                        if (comment_end)
-                               putchar(' ');
+                               printf("%s%s %s%s", c_reset,
+                                                   c_plain, c_reset,
+                                                   c_func);
                        for (i = 0; i < comment_end; i++)
                                putchar(hunk_comment[i]);
                }
index cc5d48b3851cda643b3790ff326c04fdf1333d1c..95bf18cf06b7c77789e68bd8ab0a312395fb5d86 100644 (file)
@@ -49,6 +49,7 @@ git-grep                                mainporcelain common
 git-gui                                 mainporcelain
 git-hash-object                         plumbingmanipulators
 git-help                               ancillaryinterrogators
+git-http-backend                        synchingrepositories
 git-http-fetch                          synchelpers
 git-http-push                           synchelpers
 git-imap-send                           foreignscminterface
index 4625b8672bf5b0dcc977737c2c5086c5c1aa54a6..78345ebb60240e6642f319467defe3e07d4aa263 100644 (file)
@@ -276,6 +276,9 @@ GIT_ARG_SET_PATH(shell)
 # Define PERL_PATH to provide path to Perl.
 GIT_ARG_SET_PATH(perl)
 #
+# Define PYTHON_PATH to provide path to Python.
+GIT_ARG_SET_PATH(python)
+#
 # Define ZLIB_PATH to provide path to zlib.
 GIT_ARG_SET_PATH(zlib)
 #
index 11bf17a86cf472a8939ad965d2552660061fd240..3a6498c04ba3382df04b964dcc45bfe60ecc6df1 100755 (executable)
@@ -163,11 +163,8 @@ __git_ps1 ()
                        fi
                fi
 
-               if [ -n "${1-}" ]; then
-                       printf "$1" "$c${b##refs/heads/}$w$i$s$u$r"
-               else
-                       printf " (%s)" "$c${b##refs/heads/}$w$i$s$u$r"
-               fi
+               local f="$w$i$s$u"
+               printf "${1:- (%s)}" "$c${b##refs/heads/}${f:+ $f}$r"
        fi
 }
 
@@ -417,7 +414,17 @@ __git_complete_remote_or_refspec ()
        while [ $c -lt $COMP_CWORD ]; do
                i="${COMP_WORDS[c]}"
                case "$i" in
-               --all|--mirror) [ "$cmd" = "push" ] && no_complete_refspec=1 ;;
+               --mirror) [ "$cmd" = "push" ] && no_complete_refspec=1 ;;
+               --all)
+                       case "$cmd" in
+                       push) no_complete_refspec=1 ;;
+                       fetch)
+                               COMPREPLY=()
+                               return
+                               ;;
+                       *) ;;
+                       esac
+                       ;;
                -*) ;;
                *) remote="$i"; break ;;
                esac
@@ -896,11 +903,31 @@ _git_commit ()
 
        local cur="${COMP_WORDS[COMP_CWORD]}"
        case "$cur" in
+       --cleanup=*)
+               __gitcomp "default strip verbatim whitespace
+                       " "" "${cur##--cleanup=}"
+               return
+               ;;
+       --reuse-message=*)
+               __gitcomp "$(__git_refs)" "" "${cur##--reuse-message=}"
+               return
+               ;;
+       --reedit-message=*)
+               __gitcomp "$(__git_refs)" "" "${cur##--reedit-message=}"
+               return
+               ;;
+       --untracked-files=*)
+               __gitcomp "all no normal" "" "${cur##--untracked-files=}"
+               return
+               ;;
        --*)
                __gitcomp "
                        --all --author= --signoff --verify --no-verify
                        --edit --amend --include --only --interactive
-                       --dry-run
+                       --dry-run --reuse-message= --reedit-message=
+                       --reset-author --file= --message= --template=
+                       --cleanup= --untracked-files --untracked-files=
+                       --verbose --quiet
                        "
                return
        esac
@@ -982,7 +1009,7 @@ _git_difftool ()
 
 __git_fetch_options="
        --quiet --verbose --append --upload-pack --force --keep --depth=
-       --tags --no-tags
+       --tags --no-tags --all --prune --dry-run
 "
 
 _git_fetch ()
@@ -1992,7 +2019,7 @@ _git_svn ()
                init fetch clone rebase dcommit log find-rev
                set-tree commit-diff info create-ignore propget
                proplist show-ignore show-externals branch tag blame
-               migrate
+               migrate mkdirs reset gc
                "
        local subcommand="$(__git_find_on_cmdline "$subcommands")"
        if [ -z "$subcommand" ]; then
@@ -2039,7 +2066,7 @@ _git_svn ()
                        __gitcomp "--stdin $cmt_opts $fc_opts"
                        ;;
                create-ignore,--*|propget,--*|proplist,--*|show-ignore,--*|\
-               show-externals,--*)
+               show-externals,--*|mkdirs,--*)
                        __gitcomp "--revision="
                        ;;
                log,--*)
@@ -2076,6 +2103,9 @@ _git_svn ()
                                --no-auth-cache --username=
                                "
                        ;;
+               reset,--*)
+                       __gitcomp "--revision= --parent"
+                       ;;
                *)
                        COMPREPLY=()
                        ;;
index adf1c5fdee2bc836f03eaa89160573973bb46a67..349f612b615fc152e9d272900b1ac5177f4a025c 100644 (file)
@@ -73,7 +73,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
                struct cache_entry *ce = active_cache[i];
                int changed;
 
-               if (DIFF_OPT_TST(&revs->diffopt, QUIET) &&
+               if (DIFF_OPT_TST(&revs->diffopt, QUICK) &&
                        DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES))
                        break;
 
@@ -507,7 +507,7 @@ int index_differs_from(const char *def, int diff_flags)
 
        init_revisions(&rev, NULL);
        setup_revisions(0, NULL, &rev, def);
-       DIFF_OPT_SET(&rev.diffopt, QUIET);
+       DIFF_OPT_SET(&rev.diffopt, QUICK);
        DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS);
        rev.diffopt.flags |= diff_flags;
        run_diff_index(&rev, 1);
diff --git a/diff.c b/diff.c
index 0d7f5ea4a87d2152638f36b30d6366406d33bfa9..aad4b3977a2960bac1391b4b3ec8afe814308902 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -39,6 +39,7 @@ static char diff_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_GREEN,        /* NEW */
        GIT_COLOR_YELLOW,       /* COMMIT */
        GIT_COLOR_BG_RED,       /* WHITESPACE */
+       GIT_COLOR_NORMAL,       /* FUNCINFO */
 };
 
 static void diff_filespec_load_driver(struct diff_filespec *one);
@@ -60,7 +61,9 @@ static int parse_diff_color_slot(const char *var, int ofs)
                return DIFF_COMMIT;
        if (!strcasecmp(var+ofs, "whitespace"))
                return DIFF_WHITESPACE;
-       die("bad config variable '%s'", var);
+       if (!strcasecmp(var+ofs, "func"))
+               return DIFF_FUNCINFO;
+       return -1;
 }
 
 static int git_config_rename(const char *var, const char *value)
@@ -119,6 +122,8 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)
 
        if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) {
                int slot = parse_diff_color_slot(var, 11);
+               if (slot < 0)
+                       return 0;
                if (!value)
                        return config_error_nonbool(var);
                color_parse(value, var, diff_colors[slot]);
@@ -189,6 +194,7 @@ struct emit_callback {
        struct diff_words_data *diff_words;
        int *found_changesp;
        FILE *file;
+       struct strbuf *header;
 };
 
 static int count_lines(const char *data, int size)
@@ -295,12 +301,13 @@ static void emit_line_0(FILE *file, const char *set, const char *reset,
                nofirst = 0;
        }
 
-       fputs(set, file);
-
-       if (!nofirst)
-               fputc(first, file);
-       fwrite(line, len, 1, file);
-       fputs(reset, file);
+       if (len || !nofirst) {
+               fputs(set, file);
+               if (!nofirst)
+                       fputc(first, file);
+               fwrite(line, len, 1, file);
+               fputs(reset, file);
+       }
        if (has_trailing_carriage_return)
                fputc('\r', file);
        if (has_trailing_newline)
@@ -344,6 +351,42 @@ static void emit_add_line(const char *reset,
        }
 }
 
+static void emit_hunk_header(struct emit_callback *ecbdata,
+                            const char *line, int len)
+{
+       const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
+       const char *frag = diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO);
+       const char *func = diff_get_color(ecbdata->color_diff, DIFF_FUNCINFO);
+       const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
+       static const char atat[2] = { '@', '@' };
+       const char *cp, *ep;
+
+       /*
+        * As a hunk header must begin with "@@ -<old>, +<new> @@",
+        * it always is at least 10 bytes long.
+        */
+       if (len < 10 ||
+           memcmp(line, atat, 2) ||
+           !(ep = memmem(line + 2, len - 2, atat, 2))) {
+               emit_line(ecbdata->file, plain, reset, line, len);
+               return;
+       }
+       ep += 2; /* skip over @@ */
+
+       /* The hunk header in fraginfo color */
+       emit_line(ecbdata->file, frag, reset, line, ep - line);
+
+       /* blank before the func header */
+       for (cp = ep; ep - line < len; ep++)
+               if (*ep != ' ' && *ep != '\t')
+                       break;
+       if (ep != cp)
+               emit_line(ecbdata->file, plain, reset, cp, ep - cp);
+
+       if (ep < line + len)
+               emit_line(ecbdata->file, func, reset, ep, line + len - ep);
+}
+
 static struct diff_tempfile *claim_diff_tempfile(void) {
        int i;
        for (i = 0; i < ARRAY_SIZE(diff_temp); i++)
@@ -755,6 +798,11 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
        const char *plain = diff_get_color(ecbdata->color_diff, DIFF_PLAIN);
        const char *reset = diff_get_color(ecbdata->color_diff, DIFF_RESET);
 
+       if (ecbdata->header) {
+               fprintf(ecbdata->file, "%s", ecbdata->header->buf);
+               strbuf_reset(ecbdata->header);
+               ecbdata->header = NULL;
+       }
        *(ecbdata->found_changesp) = 1;
 
        if (ecbdata->label_path[0]) {
@@ -781,9 +829,7 @@ static void fn_out_consume(void *priv, char *line, unsigned long len)
                        diff_words_flush(ecbdata);
                len = sane_truncate_line(ecbdata, line, len);
                find_lno(line, ecbdata);
-               emit_line(ecbdata->file,
-                         diff_get_color(ecbdata->color_diff, DIFF_FRAGINFO),
-                         reset, line, len);
+               emit_hunk_header(ecbdata, line, len);
                if (line[len-1] != '\n')
                        putc('\n', ecbdata->file);
                return;
@@ -1561,6 +1607,7 @@ static void builtin_diff(const char *name_a,
        const char *reset = diff_get_color_opt(o, DIFF_RESET);
        const char *a_prefix, *b_prefix;
        const char *textconv_one = NULL, *textconv_two = NULL;
+       struct strbuf header = STRBUF_INIT;
 
        if (DIFF_OPT_TST(o, SUBMODULE_LOG) &&
                        (!one->mode || S_ISGITLINK(one->mode)) &&
@@ -1595,25 +1642,26 @@ static void builtin_diff(const char *name_a,
        b_two = quote_two(b_prefix, name_b + (*name_b == '/'));
        lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
        lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
-       fprintf(o->file, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset);
+       strbuf_addf(&header, "%sdiff --git %s %s%s\n", set, a_one, b_two, reset);
        if (lbl[0][0] == '/') {
                /* /dev/null */
-               fprintf(o->file, "%snew file mode %06o%s\n", set, two->mode, reset);
+               strbuf_addf(&header, "%snew file mode %06o%s\n", set, two->mode, reset);
                if (xfrm_msg && xfrm_msg[0])
-                       fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset);
+                       strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset);
        }
        else if (lbl[1][0] == '/') {
-               fprintf(o->file, "%sdeleted file mode %06o%s\n", set, one->mode, reset);
+               strbuf_addf(&header, "%sdeleted file mode %06o%s\n", set, one->mode, reset);
                if (xfrm_msg && xfrm_msg[0])
-                       fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset);
+                       strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset);
        }
        else {
                if (one->mode != two->mode) {
-                       fprintf(o->file, "%sold mode %06o%s\n", set, one->mode, reset);
-                       fprintf(o->file, "%snew mode %06o%s\n", set, two->mode, reset);
+                       strbuf_addf(&header, "%sold mode %06o%s\n", set, one->mode, reset);
+                       strbuf_addf(&header, "%snew mode %06o%s\n", set, two->mode, reset);
                }
                if (xfrm_msg && xfrm_msg[0])
-                       fprintf(o->file, "%s%s%s\n", set, xfrm_msg, reset);
+                       strbuf_addf(&header, "%s%s%s\n", set, xfrm_msg, reset);
+
                /*
                 * we do not run diff between different kind
                 * of objects.
@@ -1623,6 +1671,8 @@ static void builtin_diff(const char *name_a,
                if (complete_rewrite &&
                    (textconv_one || !diff_filespec_is_binary(one)) &&
                    (textconv_two || !diff_filespec_is_binary(two))) {
+                       fprintf(o->file, "%s", header.buf);
+                       strbuf_reset(&header);
                        emit_rewrite_diff(name_a, name_b, one, two,
                                                textconv_one, textconv_two, o);
                        o->found_changes = 1;
@@ -1640,6 +1690,8 @@ static void builtin_diff(const char *name_a,
                if (mf1.size == mf2.size &&
                    !memcmp(mf1.ptr, mf2.ptr, mf1.size))
                        goto free_ab_and_return;
+               fprintf(o->file, "%s", header.buf);
+               strbuf_reset(&header);
                if (DIFF_OPT_TST(o, BINARY))
                        emit_binary_diff(o->file, &mf1, &mf2);
                else
@@ -1656,6 +1708,11 @@ static void builtin_diff(const char *name_a,
                struct emit_callback ecbdata;
                const struct userdiff_funcname *pe;
 
+               if (!DIFF_XDL_TST(o, WHITESPACE_FLAGS)) {
+                       fprintf(o->file, "%s", header.buf);
+                       strbuf_reset(&header);
+               }
+
                if (textconv_one) {
                        size_t size;
                        mf1.ptr = run_textconv(textconv_one, one, &size);
@@ -1685,6 +1742,7 @@ static void builtin_diff(const char *name_a,
                if (ecbdata.ws_rule & WS_BLANK_AT_EOF)
                        check_blank_at_eof(&mf1, &mf2, &ecbdata);
                ecbdata.file = o->file;
+               ecbdata.header = header.len ? &header : NULL;
                xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
                xecfg.ctxlen = o->context;
                xecfg.interhunkctxlen = o->interhunkcontext;
@@ -1729,6 +1787,7 @@ static void builtin_diff(const char *name_a,
        }
 
  free_ab_and_return:
+       strbuf_release(&header);
        diff_free_filespec_data(one);
        diff_free_filespec_data(two);
        free(a_one);
@@ -2511,6 +2570,20 @@ int diff_setup_done(struct diff_options *options)
        if (count > 1)
                die("--name-only, --name-status, --check and -s are mutually exclusive");
 
+       /*
+        * Most of the time we can say "there are changes"
+        * only by checking if there are changed paths, but
+        * --ignore-whitespace* options force us to look
+        * inside contents.
+        */
+
+       if (DIFF_XDL_TST(options, IGNORE_WHITESPACE) ||
+           DIFF_XDL_TST(options, IGNORE_WHITESPACE_CHANGE) ||
+           DIFF_XDL_TST(options, IGNORE_WHITESPACE_AT_EOL))
+               DIFF_OPT_SET(options, DIFF_FROM_CONTENTS);
+       else
+               DIFF_OPT_CLR(options, DIFF_FROM_CONTENTS);
+
        if (DIFF_OPT_TST(options, FIND_COPIES_HARDER))
                options->detect_rename = DIFF_DETECT_COPY;
 
@@ -2571,7 +2644,7 @@ int diff_setup_done(struct diff_options *options)
         * to have found.  It does not make sense not to return with
         * exit code in such a case either.
         */
-       if (DIFF_OPT_TST(options, QUIET)) {
+       if (DIFF_OPT_TST(options, QUICK)) {
                options->output_format = DIFF_FORMAT_NO_OUTPUT;
                DIFF_OPT_SET(options, EXIT_WITH_STATUS);
        }
@@ -2762,7 +2835,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        else if (!strcmp(arg, "--exit-code"))
                DIFF_OPT_SET(options, EXIT_WITH_STATUS);
        else if (!strcmp(arg, "--quiet"))
-               DIFF_OPT_SET(options, QUIET);
+               DIFF_OPT_SET(options, QUICK);
        else if (!strcmp(arg, "--ext-diff"))
                DIFF_OPT_SET(options, ALLOW_EXTERNAL);
        else if (!strcmp(arg, "--no-ext-diff"))
@@ -3469,6 +3542,18 @@ free_queue:
        q->nr = q->alloc = 0;
        if (options->close_file)
                fclose(options->file);
+
+       /*
+        * Report the content-level differences with HAS_CHANGES;
+        * diff_addremove/diff_change does not set the bit when
+        * DIFF_FROM_CONTENTS is in effect (e.g. with -w).
+        */
+       if (DIFF_OPT_TST(options, DIFF_FROM_CONTENTS)) {
+               if (options->found_changes)
+                       DIFF_OPT_SET(options, HAS_CHANGES);
+               else
+                       DIFF_OPT_CLR(options, HAS_CHANGES);
+       }
 }
 
 static void diffcore_apply_filter(const char *filter)
@@ -3605,7 +3690,7 @@ void diffcore_std(struct diff_options *options)
        diff_resolve_rename_copy();
        diffcore_apply_filter(options->filter);
 
-       if (diff_queued_diff.nr)
+       if (diff_queued_diff.nr && !DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
                DIFF_OPT_SET(options, HAS_CHANGES);
        else
                DIFF_OPT_CLR(options, HAS_CHANGES);
@@ -3665,7 +3750,8 @@ void diff_addremove(struct diff_options *options,
                fill_filespec(two, sha1, mode);
 
        diff_queue(&diff_queued_diff, one, two);
-       DIFF_OPT_SET(options, HAS_CHANGES);
+       if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
+               DIFF_OPT_SET(options, HAS_CHANGES);
 }
 
 void diff_change(struct diff_options *options,
@@ -3697,7 +3783,8 @@ void diff_change(struct diff_options *options,
        fill_filespec(two, new_sha1, new_mode);
 
        diff_queue(&diff_queued_diff, one, two);
-       DIFF_OPT_SET(options, HAS_CHANGES);
+       if (!DIFF_OPT_TST(options, DIFF_FROM_CONTENTS))
+               DIFF_OPT_SET(options, HAS_CHANGES);
 }
 
 void diff_unmerge(struct diff_options *options,
@@ -3736,11 +3823,13 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec,
        if (start_command(&child) != 0 ||
            strbuf_read(&buf, child.out, 0) < 0 ||
            finish_command(&child) != 0) {
+               close(child.out);
                strbuf_release(&buf);
                remove_tempfile();
                error("error running textconv command '%s'", pgm);
                return NULL;
        }
+       close(child.out);
        remove_tempfile();
 
        return strbuf_detach(&buf, outsize);
diff --git a/diff.h b/diff.h
index 2740421cfe9985a57f61855dc6372e8402231e0e..6f6d0ed01d9212289784cf7250358320f64b72cc 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -55,7 +55,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
 #define DIFF_OPT_COLOR_DIFF          (1 <<  8)
 #define DIFF_OPT_COLOR_DIFF_WORDS    (1 <<  9)
 #define DIFF_OPT_HAS_CHANGES         (1 << 10)
-#define DIFF_OPT_QUIET               (1 << 11)
+#define DIFF_OPT_QUICK               (1 << 11)
 #define DIFF_OPT_NO_INDEX            (1 << 12)
 #define DIFF_OPT_ALLOW_EXTERNAL      (1 << 13)
 #define DIFF_OPT_EXIT_WITH_STATUS    (1 << 14)
@@ -66,7 +66,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
 #define DIFF_OPT_DIRSTAT_CUMULATIVE  (1 << 19)
 #define DIFF_OPT_DIRSTAT_BY_FILE     (1 << 20)
 #define DIFF_OPT_ALLOW_TEXTCONV      (1 << 21)
-
+#define DIFF_OPT_DIFF_FROM_CONTENTS  (1 << 22)
 #define DIFF_OPT_SUBMODULE_LOG       (1 << 23)
 
 #define DIFF_OPT_TST(opts, flag)    ((opts)->flags & DIFF_OPT_##flag)
@@ -130,6 +130,7 @@ enum color_diff {
        DIFF_FILE_NEW = 5,
        DIFF_COMMIT = 6,
        DIFF_WHITESPACE = 7,
+       DIFF_FUNCINFO = 8,
 };
 const char *diff_get_color(int diff_use_color, enum color_diff ix);
 #define diff_get_color_opt(o, ix) \
index e670f8512558c38d9a9d6e754cfc609b042b1195..7cf431d261f9a35679ead7c8acda15aecdb8720d 100644 (file)
@@ -201,10 +201,15 @@ int diffcore_count_changes(struct diff_filespec *src,
                while (d->cnt) {
                        if (d->hashval >= s->hashval)
                                break;
+                       la += d->cnt;
                        d++;
                }
                src_cnt = s->cnt;
-               dst_cnt = d->hashval == s->hashval ? d->cnt : 0;
+               dst_cnt = 0;
+               if (d->cnt && d->hashval == s->hashval) {
+                       dst_cnt = d->cnt;
+                       d++;
+               }
                if (src_cnt < dst_cnt) {
                        la += dst_cnt - src_cnt;
                        sc += src_cnt;
@@ -213,6 +218,10 @@ int diffcore_count_changes(struct diff_filespec *src,
                        sc += dst_cnt;
                s++;
        }
+       while (d->cnt) {
+               la += d->cnt;
+               d++;
+       }
 
        if (!src_count_p)
                free(src_count);
index dd3c99d60d0ccf2506b08632c35c5ffc2bbc3255..cd8704987168be08bf488ffd191f57f2f80aea22 100644 (file)
@@ -19,8 +19,8 @@ Format of STDIN stream:
 
   new_commit ::= 'commit' sp ref_str lf
     mark?
-    ('author' sp name sp '<' email '>' sp when lf)?
-    'committer' sp name sp '<' email '>' sp when lf
+    ('author' (sp name)? sp '<' email '>' sp when lf)?
+    'committer' (sp name)? sp '<' email '>' sp when lf
     commit_msg
     ('from' sp committish lf)?
     ('merge' sp committish lf)*
@@ -47,7 +47,7 @@ Format of STDIN stream:
 
   new_tag ::= 'tag' sp tag_str lf
     'from' sp committish lf
-    ('tagger' sp name sp '<' email '>' sp when lf)?
+    ('tagger' (sp name)? sp '<' email '>' sp when lf)?
     tag_msg;
   tag_msg ::= data;
 
index f813ffdaa1526aa68b4ed8e7d4d04b48e270496a..cd43c3491260cb2aa51f0d19fd18ab66e4ad8217 100755 (executable)
@@ -1216,7 +1216,11 @@ sub patch_update_file {
        if (@{$mode->{TEXT}}) {
                unshift @hunk, $mode;
        }
-       if (@{$deletion->{TEXT}} && !@hunk) {
+       if (@{$deletion->{TEXT}}) {
+               foreach my $hunk (@hunk) {
+                       push @{$deletion->{TEXT}}, @{$hunk->{TEXT}};
+                       push @{$deletion->{DISPLAY}}, @{$hunk->{DISPLAY}};
+               }
                @hunk = ($deletion);
        }
 
index 512891d217fc34ed5ff9992ed0b3dc7ff23f0bb0..4838cdb9ede8765b37ab663aa59fb1b011a30b4c 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -577,11 +577,12 @@ do
                        git cat-file commit "$commit" |
                        sed -e '1,/^$/d' >"$dotest/msg-clean"
                else
-                       SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$dotest/info")"
-                       case "$keep_subject" in -k)  SUBJECT="[PATCH] $SUBJECT" ;; esac
-
-                       (printf '%s\n\n' "$SUBJECT"; cat "$dotest/msg") |
-                               git stripspace > "$dotest/msg-clean"
+                       {
+                               sed -n '/^Subject/ s/Subject: //p' "$dotest/info"
+                               echo
+                               cat "$dotest/msg"
+                       } |
+                       git stripspace > "$dotest/msg-clean"
                fi
                ;;
        esac
index 037a1f2c21958252052f4c1cf0d8a8507574ca95..718277a651f1fbc8e100e7eb657d87699b1059cb 100755 (executable)
@@ -2052,6 +2052,11 @@ proc do_quit {{rc {1}}} {
        }
 
        set ret_code $rc
+
+       # Briefly enable send again, working around Tk bug
+       # http://sourceforge.net/tracker/?func=detail&atid=112997&aid=1821174&group_id=12997
+       tk appname [appname]
+
        destroy .
 }
 
index 1f3b08f9efff873631d73e128ee97c1b9dad1822..8525b79aaa8fbc2d76cd82694d5042398be968a0 100644 (file)
@@ -1245,6 +1245,18 @@ method _open_tooltip {cur_w} {
 
        $tooltip_t conf -state disabled
        _position_tooltip $this
+
+       # On MacOS raising a window causes it to acquire focus.
+       # Tk 8.5 on MacOS seems to properly support wm transient,
+       # so we can safely counter the effect there.
+       if {$::have_tk85 && [is_MacOSX]} {
+               update
+               if {$w eq {}} {
+                       raise .
+               } else {
+                       raise $w
+               }
+       }
 }
 
 method _position_tooltip {} {
@@ -1268,7 +1280,9 @@ method _position_tooltip {} {
        append g $pos_y
 
        wm geometry $tooltip_wm $g
-       raise $tooltip_wm
+       if {![is_MacOSX]} {
+               raise $tooltip_wm
+       }
 }
 
 method _hide_tooltip {} {
index a18ac8b4308d8263a0688058524282b72bafe77a..d4e0bed0b6b3ca0f8e45435df8a62fd447e47c86 100644 (file)
@@ -89,27 +89,26 @@ proc do_fsck_objects {} {
 }
 
 proc hint_gc {} {
-       set object_limit 8
+       set ndirs 1
+       set limit 8
        if {[is_Windows]} {
-               set object_limit 1
+               set ndirs 4
+               set limit 1
        }
 
-       set objects_current [llength [glob \
-               -directory [gitdir objects 42] \
+       set count [llength [glob \
                -nocomplain \
-               -tails \
                -- \
-               *]]
+               [gitdir objects 4\[0-[expr {$ndirs-1}]\]/*]]]
 
-       if {$objects_current >= $object_limit} {
-               set objects_current [expr {$objects_current * 250}]
-               set object_limit    [expr {$object_limit    * 250}]
+       if {$count >= $limit * $ndirs} {
+               set objects_current [expr {$count * 256/$ndirs}]
                if {[ask_popup \
                        [mc "This repository currently has approximately %i loose objects.
 
-To maintain optimal performance it is strongly recommended that you compress the database when more than %i loose objects exist.
+To maintain optimal performance it is strongly recommended that you compress the database.
 
-Compress the database now?" $objects_current $object_limit]] eq yes} {
+Compress the database now?" $objects_current]] eq yes} {
                        do_gc
                }
        }
index 31e09474880c8d22122334f649cfcc126bf2c82e..241642062e7d4b8ad267f10fc6123bab52dfc744 100644 (file)
@@ -250,6 +250,8 @@ method _write_url        {args} { set urltype url    }
 method _write_check_head {args} { set checktype head }
 
 method _write_head_list {args} {
+       global current_branch
+
        $head_m delete 0 end
        foreach abr $head_list {
                $head_m insert end radiobutton \
@@ -258,7 +260,11 @@ method _write_head_list {args} {
                        -variable @check_head
        }
        if {[lsearch -exact -sorted $head_list $check_head] < 0} {
-               set check_head {}
+               if {[lsearch -exact -sorted $head_list $current_branch] < 0} {
+                       set check_head {}
+               } else {
+                       set check_head $current_branch
+               }
        }
 }
 
index 341930ca9d74867cf334952fc47bb3afe95552b7..b8e6456208d8cccb669eb125cfb2ac677443db82 100755 (executable)
@@ -394,8 +394,15 @@ gitweb_css () {
 EOFGITWEB
 }
 
+gitweb_js () {
+       cat > "$1" <<\EOFGITWEB
+@@GITWEB_JS@@
+EOFGITWEB
+}
+
 gitweb_cgi "$GIT_DIR/gitweb/gitweb.cgi"
 gitweb_css "$GIT_DIR/gitweb/gitweb.css"
+gitweb_js  "$GIT_DIR/gitweb/gitweb.js"
 
 case "$httpd" in
 *lighttpd*)
index bfeb4a0ff65fabaa49b212411ee2451cbf656f3d..9e69ada413bd81948591a9e99287f97499573673 100755 (executable)
@@ -91,45 +91,63 @@ error_on_no_merge_candidates () {
                esac
        done
 
+       if test true = "$rebase"
+       then
+               op_type=rebase
+               op_prep=against
+       else
+               op_type=merge
+               op_prep=with
+       fi
+
        curr_branch=${curr_branch#refs/heads/}
        upstream=$(git config "branch.$curr_branch.merge")
        remote=$(git config "branch.$curr_branch.remote")
 
        if [ $# -gt 1 ]; then
-               echo "There are no candidates for merging in the refs that you just fetched."
+               if [ "$rebase" = true ]; then
+                       printf "There is no candidate for rebasing against "
+               else
+                       printf "There are no candidates for merging "
+               fi
+               echo "among the refs that you just fetched."
                echo "Generally this means that you provided a wildcard refspec which had no"
                echo "matches on the remote end."
        elif [ $# -gt 0 ] && [ "$1" != "$remote" ]; then
                echo "You asked to pull from the remote '$1', but did not specify"
-               echo "a branch to merge. Because this is not the default configured remote"
+               echo "a branch. Because this is not the default configured remote"
                echo "for your current branch, you must specify a branch on the command line."
        elif [ -z "$curr_branch" ]; then
                echo "You are not currently on a branch, so I cannot use any"
                echo "'branch.<branchname>.merge' in your configuration file."
-               echo "Please specify which branch you want to merge on the command"
+               echo "Please specify which remote branch you want to use on the command"
                echo "line and try again (e.g. 'git pull <repository> <refspec>')."
                echo "See git-pull(1) for details."
        elif [ -z "$upstream" ]; then
                echo "You asked me to pull without telling me which branch you"
-               echo "want to merge with, and 'branch.${curr_branch}.merge' in"
-               echo "your configuration file does not tell me either.  Please"
-               echo "specify which branch you want to merge on the command line and"
+               echo "want to $op_type $op_prep, and 'branch.${curr_branch}.merge' in"
+               echo "your configuration file does not tell me, either. Please"
+               echo "specify which branch you want to use on the command line and"
                echo "try again (e.g. 'git pull <repository> <refspec>')."
                echo "See git-pull(1) for details."
                echo
-               echo "If you often merge with the same branch, you may want to"
-               echo "configure the following variables in your configuration"
-               echo "file:"
+               echo "If you often $op_type $op_prep the same branch, you may want to"
+               echo "use something like the following in your configuration file:"
+               echo
+               echo "    [branch \"${curr_branch}\"]"
+               echo "    remote = <nickname>"
+               echo "    merge = <remote-ref>"
+               test rebase = "$op_type" &&
+                       echo "    rebase = true"
                echo
-               echo "    branch.${curr_branch}.remote = <nickname>"
-               echo "    branch.${curr_branch}.merge = <remote-ref>"
-               echo "    remote.<nickname>.url = <url>"
-               echo "    remote.<nickname>.fetch = <refspec>"
+               echo "    [remote \"<nickname>\"]"
+               echo "    url = <url>"
+               echo "    fetch = <refspec>"
                echo
                echo "See git-config(1) for details."
        else
-               echo "Your configuration specifies to merge the ref '${upstream#refs/heads/}' from the"
-               echo "remote, but no such ref was fetched."
+               echo "Your configuration specifies to $op_type $op_prep the ref '${upstream#refs/heads/}'"
+               echo "from the remote, but no such ref was fetched."
        fi
        exit 1
 }
index 0bd3bf78b8563f29a24dbc7dcc4ef308b3381157..d52932878c05af1aeb714c0cf4d48dbbcddc19ee 100755 (executable)
@@ -779,7 +779,7 @@ EOF
 
                cp "$TODO" "$TODO".backup
                git_editor "$TODO" ||
-                       die "Could not execute editor"
+                       die_abort "Could not execute editor"
 
                has_action "$TODO" ||
                        die_abort "Nothing to do"
index 4f5da4ecf253d79dd558e3c962c194fe3da46296..e05455f74c7e23c28cae41b68fa80df87c633ce9 100755 (executable)
@@ -71,7 +71,7 @@ git send-email [options] <file | directory | rev-list options >
     --suppress-cc           <str>  * author, self, sob, cc, cccmd, body, bodycc, all.
     --[no-]signed-off-by-cc        * Send to Signed-off-by: addresses. Default on.
     --[no-]suppress-from           * Send to self. Default off.
-    --[no-]chain-reply-to          * Chain In-Reply-To: fields. Default on.
+    --[no-]chain-reply-to          * Chain In-Reply-To: fields. Default off.
     --[no-]thread                  * Use In-Reply-To: field. Default on.
 
   Administering:
@@ -187,9 +187,11 @@ my ($identity, $aliasfiletype, @alias_files, @smtp_host_parts);
 my ($validate, $confirm);
 my (@suppress_cc);
 
+my $not_set_by_user = "true but not set by the user";
+
 my %config_bool_settings = (
     "thread" => [\$thread, 1],
-    "chainreplyto" => [\$chain_reply_to, 1],
+    "chainreplyto" => [\$chain_reply_to, $not_set_by_user],
     "suppressfrom" => [\$suppress_from, undef],
     "signedoffbycc" => [\$signed_off_by_cc, undef],
     "signedoffcc" => [\$signed_off_by_cc, undef],      # Deprecated
@@ -214,6 +216,19 @@ my %config_settings = (
     "from" => \$sender,
 );
 
+# Help users prepare for 1.7.0
+sub chain_reply_to {
+       if (defined $chain_reply_to &&
+           $chain_reply_to eq $not_set_by_user) {
+               print STDERR
+                   "In git 1.7.0, the default has changed to --no-chain-reply-to\n" .
+                   "Set sendemail.chainreplyto configuration variable to true if\n" .
+                   "you want to keep --chain-reply-to as your default.\n";
+               $chain_reply_to = 0;
+       }
+       return $chain_reply_to;
+}
+
 # Handle Uncouth Termination
 sub signal_handler {
 
@@ -862,7 +877,9 @@ X-Mailer: git-send-email $gitversion
 
        my @sendmail_parameters = ('-i', @recipients);
        my $raw_from = $sanitized_sender;
-       $raw_from = $envelope_sender if (defined $envelope_sender);
+       if (defined $envelope_sender && $envelope_sender ne "auto") {
+               $raw_from = $envelope_sender;
+       }
        $raw_from = extract_valid_address($raw_from);
        unshift (@sendmail_parameters,
                        '-f', $raw_from) if(defined $envelope_sender);
@@ -1157,7 +1174,7 @@ foreach my $t (@files) {
 
        # set up for the next message
        if ($thread && $message_was_sent &&
-               ($chain_reply_to || !defined $reply_to || length($reply_to) == 0)) {
+               (chain_reply_to() || !defined $reply_to || length($reply_to) == 0)) {
                $reply_to = $message_id;
                if (length $references > 0) {
                        $references .= "\n $message_id";
index f796c2fe24f0eaec3de45bc5bf14f7dd2e29f4bd..3a0685f1893098e8f5c877f509183c8434e7c028 100755 (executable)
@@ -7,7 +7,7 @@ USAGE="list [<options>]
    or: $dashless drop [-q|--quiet] [<stash>]
    or: $dashless ( pop | apply ) [--index] [-q|--quiet] [<stash>]
    or: $dashless branch <branchname> [<stash>]
-   or: $dashless [save [-k|--keep-index] [-q|--quiet] [<message>]]
+   or: $dashless [save [--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]]
    or: $dashless clear"
 
 SUBDIRECTORY_OK=Yes
index 957d44e63004cef131fca5c7dad75e7049d15842..650c9e5f02ead07351629d6572e82c3a9ac7ef92 100755 (executable)
@@ -392,9 +392,11 @@ sub cmd_clone {
                $path = $url;
        }
        $path = basename($url) if !defined $path || !length $path;
+       my $authors_absolute = $_authors ? File::Spec->rel2abs($_authors) : "";
        cmd_init($url, $path);
+       command_oneline('config', 'svn.authorsfile', $authors_absolute)
+           if $_authors;
        Git::SVN::fetch_all($Git::SVN::default_repo_id);
-       command_oneline('config', 'svn.authorsfile', $_authors) if $_authors;
 }
 
 sub cmd_init {
@@ -661,7 +663,8 @@ sub cmd_branch {
        }
        $head ||= 'HEAD';
 
-       my ($src, $rev, undef, $gs) = working_head_info($head);
+       my (undef, $rev, undef, $gs) = working_head_info($head);
+       my $src = $gs->full_url;
 
        my $remote = Git::SVN::read_all_remotes()->{$gs->{repo_id}};
        my $allglobs = $remote->{ $_tag ? 'tags' : 'branches' };
@@ -1632,6 +1635,7 @@ use Carp qw/croak/;
 use File::Path qw/mkpath/;
 use File::Copy qw/copy/;
 use IPC::Open3;
+use Memoize;  # core since 5.8.0, Jul 2002
 
 my ($_gc_nr, $_gc_period);
 
@@ -1739,7 +1743,11 @@ sub fetch_all {
        my $ra = Git::SVN::Ra->new($url);
        my $uuid = $ra->get_uuid;
        my $head = $ra->get_latest_revnum;
-       $ra->get_log("", $head, 0, 1, 0, 1, sub { $head = $_[1] });
+
+       # ignore errors, $head revision may not even exist anymore
+       eval { $ra->get_log("", $head, 0, 1, 0, 1, sub { $head = $_[1] }) };
+       warn "W: $@\n" if $@;
+
        my $base = defined $fetch ? $head : 0;
 
        # read the max revs for wildcard expansion (branches/*, tags/*)
@@ -2444,12 +2452,6 @@ sub get_commit_parents {
                next if $seen{$p};
                $seen{$p} = 1;
                push @ret, $p;
-               # MAXPARENT is defined to 16 in commit-tree.c:
-               last if @ret >= 16;
-       }
-       if (@tmp) {
-               die "r$log_entry->{revision}: No room for parents:\n\t",
-                   join("\n\t", @tmp), "\n";
        }
        @ret;
 }
@@ -2738,20 +2740,44 @@ sub do_fetch {
 
 sub mkemptydirs {
        my ($self, $r) = @_;
+
+       sub scan {
+               my ($r, $empty_dirs, $line) = @_;
+               if (defined $r && $line =~ /^r(\d+)$/) {
+                       return 0 if $1 > $r;
+               } elsif ($line =~ /^  \+empty_dir: (.+)$/) {
+                       $empty_dirs->{$1} = 1;
+               } elsif ($line =~ /^  \-empty_dir: (.+)$/) {
+                       my @d = grep {m[^\Q$1\E(/|$)]} (keys %$empty_dirs);
+                       delete @$empty_dirs{@d};
+               }
+               1; # continue
+       };
+
        my %empty_dirs = ();
+       my $gz_file = "$self->{dir}/unhandled.log.gz";
+       if (-f $gz_file) {
+               if (!$can_compress) {
+                       warn "Compress::Zlib could not be found; ",
+                            "empty directories in $gz_file will not be read\n";
+               } else {
+                       my $gz = Compress::Zlib::gzopen($gz_file, "rb") or
+                               die "Unable to open $gz_file: $!\n";
+                       my $line;
+                       while ($gz->gzreadline($line) > 0) {
+                               scan($r, \%empty_dirs, $line) or last;
+                       }
+                       $gz->gzclose;
+               }
+       }
 
-       open my $fh, '<', "$self->{dir}/unhandled.log" or return;
-       binmode $fh or croak "binmode: $!";
-       while (<$fh>) {
-               if (defined $r && /^r(\d+)$/) {
-                       last if $1 > $r;
-               } elsif (/^  \+empty_dir: (.+)$/) {
-                       $empty_dirs{$1} = 1;
-               } elsif (/^  \-empty_dir: (.+)$/) {
-                       delete $empty_dirs{$1};
+       if (open my $fh, '<', "$self->{dir}/unhandled.log") {
+               binmode $fh or croak "binmode: $!";
+               while (<$fh>) {
+                       scan($r, \%empty_dirs, $_) or last;
                }
+               close $fh;
        }
-       close $fh;
 
        my $strip = qr/\A\Q$self->{path}\E(?:\/|$)/;
        foreach my $d (sort keys %empty_dirs) {
@@ -2940,10 +2966,14 @@ sub find_extra_svk_parents {
                        if ( my $commit = $gs->rev_map_get($rev, $uuid) ) {
                                # wahey!  we found it, but it might be
                                # an old one (!)
-                               push @known_parents, $commit;
+                               push @known_parents, [ $rev, $commit ];
                        }
                }
        }
+       # Ordering matters; highest-numbered commit merge tickets
+       # first, as they may account for later merge ticket additions
+       # or changes.
+       @known_parents = map {$_->[1]} sort {$b->[0] <=> $a->[0]} @known_parents;
        for my $parent ( @known_parents ) {
                my @cmd = ('rev-list', $parent, map { "^$_" } @$parents );
                my ($msg_fh, $ctx) = command_output_pipe(@cmd);
@@ -2960,6 +2990,111 @@ sub find_extra_svk_parents {
        }
 }
 
+sub lookup_svn_merge {
+       my $uuid = shift;
+       my $url = shift;
+       my $merge = shift;
+
+       my ($source, $revs) = split ":", $merge;
+       my $path = $source;
+       $path =~ s{^/}{};
+       my $gs = Git::SVN->find_by_url($url.$source, $url, $path);
+       if ( !$gs ) {
+               warn "Couldn't find revmap for $url$source\n";
+               return;
+       }
+       my @ranges = split ",", $revs;
+       my ($tip, $tip_commit);
+       my @merged_commit_ranges;
+       # find the tip
+       for my $range ( @ranges ) {
+               my ($bottom, $top) = split "-", $range;
+               $top ||= $bottom;
+               my $bottom_commit = $gs->find_rev_after( $bottom, 1, $top );
+               my $top_commit = $gs->find_rev_before( $top, 1, $bottom );
+
+               unless ($top_commit and $bottom_commit) {
+                       warn "W:unknown path/rev in svn:mergeinfo "
+                               ."dirprop: $source:$range\n";
+                       next;
+               }
+
+               push @merged_commit_ranges,
+                       "$bottom_commit^..$top_commit";
+
+               if ( !defined $tip or $top > $tip ) {
+                       $tip = $top;
+                       $tip_commit = $top_commit;
+               }
+       }
+       return ($tip_commit, @merged_commit_ranges);
+}
+
+sub _rev_list {
+       my ($msg_fh, $ctx) = command_output_pipe(
+               "rev-list", @_,
+              );
+       my @rv;
+       while ( <$msg_fh> ) {
+               chomp;
+               push @rv, $_;
+       }
+       command_close_pipe($msg_fh, $ctx);
+       @rv;
+}
+
+sub check_cherry_pick {
+       my $base = shift;
+       my $tip = shift;
+       my @ranges = @_;
+       my %commits = map { $_ => 1 }
+               _rev_list("--no-merges", $tip, "--not", $base);
+       for my $range ( @ranges ) {
+               delete @commits{_rev_list($range)};
+       }
+       return (keys %commits);
+}
+
+BEGIN {
+       memoize 'lookup_svn_merge';
+       memoize 'check_cherry_pick';
+}
+
+sub parents_exclude {
+       my $parents = shift;
+       my @commits = @_;
+       return unless @commits;
+
+       my @excluded;
+       my $excluded;
+       do {
+               my @cmd = ('rev-list', "-1", @commits, "--not", @$parents );
+               $excluded = command_oneline(@cmd);
+               if ( $excluded ) {
+                       my @new;
+                       my $found;
+                       for my $commit ( @commits ) {
+                               if ( $commit eq $excluded ) {
+                                       push @excluded, $commit;
+                                       $found++;
+                                       last;
+                               }
+                               else {
+                                       push @new, $commit;
+                               }
+                       }
+                       die "saw commit '$excluded' in rev-list output, "
+                               ."but we didn't ask for that commit (wanted: @commits --not @$parents)"
+                                       unless $found;
+                       @commits = @new;
+               }
+       }
+               while ($excluded and @commits);
+
+       return @excluded;
+}
+
+
 # note: this function should only be called if the various dirprops
 # have actually changed
 sub find_extra_svn_parents {
@@ -2972,82 +3107,73 @@ sub find_extra_svn_parents {
        # are now marked as merge, we can add the tip as a parent.
        my @merges = split "\n", $mergeinfo;
        my @merge_tips;
-       my @merged_commit_ranges;
        my $url = $self->rewrite_root || $self->{url};
+       my $uuid = $self->ra_uuid;
+       my %ranges;
        for my $merge ( @merges ) {
-               my ($source, $revs) = split ":", $merge;
-               my $path = $source;
-               $path =~ s{^/}{};
-               my $gs = Git::SVN->find_by_url($url.$source, $url, $path);
-               if ( !$gs ) {
-                       warn "Couldn't find revmap for $url$source\n";
-                       next;
-               }
-               my @ranges = split ",", $revs;
-               my ($tip, $tip_commit);
-               # find the tip
-               for my $range ( @ranges ) {
-                       my ($bottom, $top) = split "-", $range;
-                       $top ||= $bottom;
-                       my $bottom_commit =
-                               $gs->rev_map_get($bottom, $self->ra_uuid) ||
-                               $gs->rev_map_get($bottom+1, $self->ra_uuid);
-                       my $top_commit;
-                       for (; !$top_commit && $top >= $bottom; --$top) {
-                               $top_commit =
-                                       $gs->rev_map_get($top, $self->ra_uuid);
-                       }
-
-                       unless ($top_commit and $bottom_commit) {
-                               warn "W:unknown path/rev in svn:mergeinfo "
-                                       ."dirprop: $source:$range\n";
-                               next;
-                       }
-
-                       push @merged_commit_ranges,
-                               "$bottom_commit..$top_commit";
-
-                       if ( !defined $tip or $top > $tip ) {
-                               $tip = $top;
-                               $tip_commit = $top_commit;
-                       }
-               }
+               my ($tip_commit, @ranges) =
+                       lookup_svn_merge( $uuid, $url, $merge );
                unless (!$tip_commit or
                                grep { $_ eq $tip_commit } @$parents ) {
                        push @merge_tips, $tip_commit;
+                       $ranges{$tip_commit} = \@ranges;
                } else {
                        push @merge_tips, undef;
                }
        }
+
+       my %excluded = map { $_ => 1 }
+               parents_exclude($parents, grep { defined } @merge_tips);
+
+       # check merge tips for new parents
+       my @new_parents;
        for my $merge_tip ( @merge_tips ) {
                my $spec = shift @merges;
-               next unless $merge_tip;
-               my @cmd = ('rev-list', "-1", $merge_tip,
-                          "--not", @$parents );
-               my ($msg_fh, $ctx) = command_output_pipe(@cmd);
-               my $new;
-               while ( <$msg_fh> ) {
-                       $new=1;last;
-               }
-               command_close_pipe($msg_fh, $ctx);
-               if ( $new ) {
-                       push @cmd, @merged_commit_ranges;
-                       my ($msg_fh, $ctx) = command_output_pipe(@cmd);
-                       my $unmerged;
-                       while ( <$msg_fh> ) {
-                               $unmerged=1;last;
-                       }
-                       command_close_pipe($msg_fh, $ctx);
-                       if ( $unmerged ) {
-                               warn "W:svn cherry-pick ignored ($spec)\n";
-                       } else {
-                               warn
-                                 "Found merge parent (svn:mergeinfo prop): ",
-                                 $merge_tip, "\n";
-                               push @$parents, $merge_tip;
+               next unless $merge_tip and $excluded{$merge_tip};
+
+               my $ranges = $ranges{$merge_tip};
+
+               # check out 'new' tips
+               my $merge_base = command_oneline(
+                       "merge-base",
+                       @$parents, $merge_tip,
+                      );
+
+               # double check that there are no missing non-merge commits
+               my (@incomplete) = check_cherry_pick(
+                       $merge_base, $merge_tip,
+                       @$ranges,
+                      );
+
+               if ( @incomplete ) {
+                       warn "W:svn cherry-pick ignored ($spec) - missing "
+                               .@incomplete." commit(s) (eg $incomplete[0])\n";
+               } else {
+                       warn
+                               "Found merge parent (svn:mergeinfo prop): ",
+                                       $merge_tip, "\n";
+                       push @new_parents, $merge_tip;
+               }
+       }
+
+       # cater for merges which merge commits from multiple branches
+       if ( @new_parents > 1 ) {
+               for ( my $i = 0; $i <= $#new_parents; $i++ ) {
+                       for ( my $j = 0; $j <= $#new_parents; $j++ ) {
+                               next if $i == $j;
+                               next unless $new_parents[$i];
+                               next unless $new_parents[$j];
+                               my $revs = command_oneline(
+                                       "rev-list", "-1",
+                                       "$new_parents[$i]..$new_parents[$j]",
+                                      );
+                               if ( !$revs ) {
+                                       undef($new_parents[$i]);
+                               }
                        }
                }
        }
+       push @$parents, grep { defined } @new_parents;
 }
 
 sub make_log_entry {
@@ -3886,11 +4012,11 @@ sub delete_entry {
                }
                print "\tD\t$gpath/\n" unless $::_q;
                command_close_pipe($ls, $ctx);
-               $self->{empty}->{$path} = 0
        } else {
                $self->{gii}->remove($gpath);
                print "\tD\t$gpath\n" unless $::_q;
        }
+       $self->{empty}->{$path} = 0;
        undef;
 }
 
diff --git a/git_remote_helpers/.gitignore b/git_remote_helpers/.gitignore
new file mode 100644 (file)
index 0000000..2247d5f
--- /dev/null
@@ -0,0 +1,2 @@
+/build
+/dist
diff --git a/git_remote_helpers/Makefile b/git_remote_helpers/Makefile
new file mode 100644 (file)
index 0000000..c62dfd0
--- /dev/null
@@ -0,0 +1,35 @@
+#
+# Makefile for the git_remote_helpers python support modules
+#
+pysetupfile:=setup.py
+
+# Shell quote (do not use $(call) to accommodate ancient setups);
+DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
+
+ifndef PYTHON_PATH
+       PYTHON_PATH = /usr/bin/python
+endif
+ifndef prefix
+       prefix = $(HOME)
+endif
+ifndef V
+       QUIET = @
+       QUIETSETUP = --quiet
+endif
+
+PYLIBDIR=$(shell $(PYTHON_PATH) -c \
+        "import sys; \
+        print 'lib/python%i.%i/site-packages' % sys.version_info[:2]")
+
+all: $(pysetupfile)
+       $(QUIET)$(PYTHON_PATH) $(pysetupfile) $(QUIETSETUP) build
+
+install: $(pysetupfile)
+       $(PYTHON_PATH) $(pysetupfile) install --prefix $(DESTDIR_SQ)$(prefix)
+
+instlibdir: $(pysetupfile)
+       @echo "$(DESTDIR_SQ)$(prefix)/$(PYLIBDIR)"
+
+clean:
+       $(QUIET)$(PYTHON_PATH) $(pysetupfile) $(QUIETSETUP) clean -a
+       $(RM) *.pyo *.pyc
diff --git a/git_remote_helpers/__init__.py b/git_remote_helpers/__init__.py
new file mode 100644 (file)
index 0000000..00f69cb
--- /dev/null
@@ -0,0 +1,16 @@
+#!/usr/bin/env python
+
+"""Support library package for git remote helpers.
+
+Git remote helpers are helper commands that interfaces with a non-git
+repository to provide automatic import of non-git history into a Git
+repository.
+
+This package provides the support library needed by these helpers..
+The following modules are included:
+
+- git.git - Interaction with Git repositories
+
+- util - General utility functionality use by the other modules in
+         this package, and also used directly by the helpers.
+"""
diff --git a/git_remote_helpers/git/__init__.py b/git_remote_helpers/git/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/git_remote_helpers/git/git.py b/git_remote_helpers/git/git.py
new file mode 100644 (file)
index 0000000..a383e6c
--- /dev/null
@@ -0,0 +1,678 @@
+#!/usr/bin/env python
+
+"""Functionality for interacting with Git repositories.
+
+This module provides classes for interfacing with a Git repository.
+"""
+
+import os
+import re
+import time
+from binascii import hexlify
+from cStringIO import StringIO
+import unittest
+
+from git_remote_helpers.util import debug, error, die, start_command, run_command
+
+
+def get_git_dir ():
+    """Return the path to the GIT_DIR for this repo."""
+    args = ("git", "rev-parse", "--git-dir")
+    exit_code, output, errors = run_command(args)
+    if exit_code:
+        die("Failed to retrieve git dir")
+    assert not errors
+    return output.strip()
+
+
+def parse_git_config ():
+    """Return a dict containing the parsed version of 'git config -l'."""
+    exit_code, output, errors = run_command(("git", "config", "-z", "-l"))
+    if exit_code:
+        die("Failed to retrieve git configuration")
+    assert not errors
+    return dict([e.split('\n', 1) for e in output.split("\0") if e])
+
+
+def git_config_bool (value):
+    """Convert the given git config string value to True or False.
+
+    Raise ValueError if the given string was not recognized as a
+    boolean value.
+
+    """
+    norm_value = str(value).strip().lower()
+    if norm_value in ("true", "1", "yes", "on", ""):
+        return True
+    if norm_value in ("false", "0", "no", "off", "none"):
+        return False
+    raise ValueError("Failed to parse '%s' into a boolean value" % (value))
+
+
+def valid_git_ref (ref_name):
+    """Return True iff the given ref name is a valid git 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.
+    if ref_name.endswith('/') or \
+       ref_name.startswith('.') or \
+       ref_name.count('/.') or \
+       ref_name.count('..') or \
+       ref_name.endswith('.lock'):
+        return False
+    for c in ref_name:
+        if ord(c) < 0x20 or ord(c) == 0x7f or c in " ~^:?*[":
+            return False
+    return True
+
+
+class GitObjectFetcher(object):
+
+    """Provide parsed access to 'git cat-file --batch'.
+
+    This provides a read-only interface to the Git object database.
+
+    """
+
+    def __init__ (self):
+        """Initiate a 'git cat-file --batch' session."""
+        self.queue = []  # List of object names to be submitted
+        self.in_transit = None  # Object name currently in transit
+
+        # 'git cat-file --batch' produces binary output which is likely
+        # to be corrupted by the default "rU"-mode pipe opened by
+        # start_command.  (Mode == "rU" does universal new-line
+        # conversion, which mangles carriage returns.) Therefore, we
+        # open an explicitly binary-safe pipe for transferring the
+        # output from 'git cat-file --batch'.
+        pipe_r_fd, pipe_w_fd = os.pipe()
+        pipe_r = os.fdopen(pipe_r_fd, "rb")
+        pipe_w = os.fdopen(pipe_w_fd, "wb")
+        self.proc = start_command(("git", "cat-file", "--batch"),
+                                  stdout = pipe_w)
+        self.f = pipe_r
+
+    def __del__ (self):
+        """Verify completed communication with 'git cat-file --batch'."""
+        assert not self.queue
+        assert self.in_transit is None
+        self.proc.stdin.close()
+        assert self.proc.wait() == 0  # Zero exit code
+        assert self.f.read() == ""  # No remaining output
+
+    def _submit_next_object (self):
+        """Submit queue items to the 'git cat-file --batch' process.
+
+        If there are items in the queue, and there is currently no item
+        currently in 'transit', then pop the first item off the queue,
+        and submit it.
+
+        """
+        if self.queue and self.in_transit is None:
+            self.in_transit = self.queue.pop(0)
+            print >> self.proc.stdin, self.in_transit[0]
+
+    def push (self, obj, callback):
+        """Push the given object name onto the queue.
+
+        The given callback function will at some point in the future
+        be called exactly once with the following arguments:
+        - self - this GitObjectFetcher instance
+        - obj  - the object name provided to push()
+        - sha1 - the SHA1 of the object, if 'None' obj is missing
+        - t    - the type of the object (tag/commit/tree/blob)
+        - size - the size of the object in bytes
+        - data - the object contents
+
+        """
+        self.queue.append((obj, callback))
+        self._submit_next_object()  # (Re)start queue processing
+
+    def process_next_entry (self):
+        """Read the next entry off the queue and invoke callback."""
+        obj, cb = self.in_transit
+        self.in_transit = None
+        header = self.f.readline()
+        if header == "%s missing\n" % (obj):
+            cb(self, obj, None, None, None, None)
+            return
+        sha1, t, size = header.split(" ")
+        assert len(sha1) == 40
+        assert t in ("tag", "commit", "tree", "blob")
+        assert size.endswith("\n")
+        size = int(size.strip())
+        data = self.f.read(size)
+        assert self.f.read(1) == "\n"
+        cb(self, obj, sha1, t, size, data)
+        self._submit_next_object()
+
+    def process (self):
+        """Process the current queue until empty."""
+        while self.in_transit is not None:
+            self.process_next_entry()
+
+    # High-level convenience methods:
+
+    def get_sha1 (self, objspec):
+        """Return the SHA1 of the object specified by 'objspec'.
+
+        Return None if 'objspec' does not specify an existing object.
+
+        """
+        class _ObjHandler(object):
+            """Helper class for getting the returned SHA1."""
+            def __init__ (self, parser):
+                self.parser = parser
+                self.sha1 = None
+
+            def __call__ (self, parser, obj, sha1, t, size, data):
+                # FIXME: Many unused arguments. Could this be cheaper?
+                assert parser == self.parser
+                self.sha1 = sha1
+
+        handler = _ObjHandler(self)
+        self.push(objspec, handler)
+        self.process()
+        return handler.sha1
+
+    def open_obj (self, objspec):
+        """Return a file object wrapping the contents of a named object.
+
+        The caller is responsible for calling .close() on the returned
+        file object.
+
+        Raise KeyError if 'objspec' does not exist in the repo.
+
+        """
+        class _ObjHandler(object):
+            """Helper class for parsing the returned git object."""
+            def __init__ (self, parser):
+                """Set up helper."""
+                self.parser = parser
+                self.contents = StringIO()
+                self.err = None
+
+            def __call__ (self, parser, obj, sha1, t, size, data):
+                """Git object callback (see GitObjectFetcher documentation)."""
+                assert parser == self.parser
+                if not sha1:  # Missing object
+                    self.err = "Missing object '%s'" % obj
+                else:
+                    assert size == len(data)
+                    self.contents.write(data)
+
+        handler = _ObjHandler(self)
+        self.push(objspec, handler)
+        self.process()
+        if handler.err:
+            raise KeyError(handler.err)
+        handler.contents.seek(0)
+        return handler.contents
+
+    def walk_tree (self, tree_objspec, callback, prefix = ""):
+        """Recursively walk the given Git tree object.
+
+        Recursively walk all subtrees of the given tree object, and
+        invoke the given callback passing three arguments:
+        (path, mode, data) with the path, permission bits, and contents
+        of all the blobs found in the entire tree structure.
+
+        """
+        class _ObjHandler(object):
+            """Helper class for walking a git tree structure."""
+            def __init__ (self, parser, cb, path, mode = None):
+                """Set up helper."""
+                self.parser = parser
+                self.cb = cb
+                self.path = path
+                self.mode = mode
+                self.err = None
+
+            def parse_tree (self, treedata):
+                """Parse tree object data, yield tree entries.
+
+                Each tree entry is a 3-tuple (mode, sha1, path)
+
+                self.path is prepended to all paths yielded
+                from this method.
+
+                """
+                while treedata:
+                    mode = int(treedata[:6], 10)
+                    # Turn 100xxx into xxx
+                    if mode > 100000:
+                        mode -= 100000
+                    assert treedata[6] == " "
+                    i = treedata.find("\0", 7)
+                    assert i > 0
+                    path = treedata[7:i]
+                    sha1 = hexlify(treedata[i + 1: i + 21])
+                    yield (mode, sha1, self.path + path)
+                    treedata = treedata[i + 21:]
+
+            def __call__ (self, parser, obj, sha1, t, size, data):
+                """Git object callback (see GitObjectFetcher documentation)."""
+                assert parser == self.parser
+                if not sha1:  # Missing object
+                    self.err = "Missing object '%s'" % (obj)
+                    return
+                assert size == len(data)
+                if t == "tree":
+                    if self.path:
+                        self.path += "/"
+                    # Recurse into all blobs and subtrees
+                    for m, s, p in self.parse_tree(data):
+                        parser.push(s,
+                                    self.__class__(self.parser, self.cb, p, m))
+                elif t == "blob":
+                    self.cb(self.path, self.mode, data)
+                else:
+                    raise ValueError("Unknown object type '%s'" % (t))
+
+        self.push(tree_objspec, _ObjHandler(self, callback, prefix))
+        self.process()
+
+
+class GitRefMap(object):
+
+    """Map Git ref names to the Git object names they currently point to.
+
+    Behaves like a dictionary of Git ref names -> Git object names.
+
+    """
+
+    def __init__ (self, obj_fetcher):
+        """Create a new Git ref -> object map."""
+        self.obj_fetcher = obj_fetcher
+        self._cache = {}  # dict: refname -> objname
+
+    def _load (self, ref):
+        """Retrieve the object currently bound to the given ref.
+
+        The name of the object pointed to by the given ref is stored
+        into this mapping, and also returned.
+
+        """
+        if ref not in self._cache:
+            self._cache[ref] = self.obj_fetcher.get_sha1(ref)
+        return self._cache[ref]
+
+    def __contains__ (self, refname):
+        """Return True if the given refname is present in this cache."""
+        return bool(self._load(refname))
+
+    def __getitem__ (self, refname):
+        """Return the git object name pointed to by the given refname."""
+        commit = self._load(refname)
+        if commit is None:
+            raise KeyError("Unknown ref '%s'" % (refname))
+        return commit
+
+    def get (self, refname, default = None):
+        """Return the git object name pointed to by the given refname."""
+        commit = self._load(refname)
+        if commit is None:
+            return default
+        return commit
+
+
+class GitFICommit(object):
+
+    """Encapsulate the data in a Git fast-import commit command."""
+
+    SHA1RE = re.compile(r'^[0-9a-f]{40}$')
+
+    @classmethod
+    def parse_mode (cls, mode):
+        """Verify the given git file mode, and return it as a string."""
+        assert mode in (644, 755, 100644, 100755, 120000)
+        return "%i" % (mode)
+
+    @classmethod
+    def parse_objname (cls, objname):
+        """Return the given object name (or mark number) as a string."""
+        if isinstance(objname, int):  # Object name is a mark number
+            assert objname > 0
+            return ":%i" % (objname)
+
+        # No existence check is done, only checks for valid format
+        assert cls.SHA1RE.match(objname)  # Object name is valid SHA1
+        return objname
+
+    @classmethod
+    def quote_path (cls, path):
+        """Return a quoted version of the given path."""
+        path = path.replace("\\", "\\\\")
+        path = path.replace("\n", "\\n")
+        path = path.replace('"', '\\"')
+        return '"%s"' % (path)
+
+    @classmethod
+    def parse_path (cls, path):
+        """Verify that the given path is valid, and quote it, if needed."""
+        assert not isinstance(path, int)  # Cannot be a mark number
+
+        # These checks verify the rules on the fast-import man page
+        assert not path.count("//")
+        assert not path.endswith("/")
+        assert not path.startswith("/")
+        assert not path.count("/./")
+        assert not path.count("/../")
+        assert not path.endswith("/.")
+        assert not path.endswith("/..")
+        assert not path.startswith("./")
+        assert not path.startswith("../")
+
+        if path.count('"') + path.count('\n') + path.count('\\'):
+            return cls.quote_path(path)
+        return path
+
+    def __init__ (self, name, email, timestamp, timezone, message):
+        """Create a new Git fast-import commit, with the given metadata."""
+        self.name = name
+        self.email = email
+        self.timestamp = timestamp
+        self.timezone = timezone
+        self.message = message
+        self.pathops = []  # List of path operations in this commit
+
+    def modify (self, mode, blobname, path):
+        """Add a file modification to this Git fast-import commit."""
+        self.pathops.append(("M",
+                             self.parse_mode(mode),
+                             self.parse_objname(blobname),
+                             self.parse_path(path)))
+
+    def delete (self, path):
+        """Add a file deletion to this Git fast-import commit."""
+        self.pathops.append(("D", self.parse_path(path)))
+
+    def copy (self, path, newpath):
+        """Add a file copy to this Git fast-import commit."""
+        self.pathops.append(("C",
+                             self.parse_path(path),
+                             self.parse_path(newpath)))
+
+    def rename (self, path, newpath):
+        """Add a file rename to this Git fast-import commit."""
+        self.pathops.append(("R",
+                             self.parse_path(path),
+                             self.parse_path(newpath)))
+
+    def note (self, blobname, commit):
+        """Add a note object to this Git fast-import commit."""
+        self.pathops.append(("N",
+                             self.parse_objname(blobname),
+                             self.parse_objname(commit)))
+
+    def deleteall (self):
+        """Delete all files in this Git fast-import commit."""
+        self.pathops.append("deleteall")
+
+
+class TestGitFICommit(unittest.TestCase):
+
+    """GitFICommit selftests."""
+
+    def test_basic (self):
+        """GitFICommit basic selftests."""
+
+        def expect_fail (method, data):
+            """Verify that the method(data) raises an AssertionError."""
+            try:
+                method(data)
+            except AssertionError:
+                return
+            raise AssertionError("Failed test for invalid data '%s(%s)'" %
+                                 (method.__name__, repr(data)))
+
+    def test_parse_mode (self):
+        """GitFICommit.parse_mode() selftests."""
+        self.assertEqual(GitFICommit.parse_mode(644), "644")
+        self.assertEqual(GitFICommit.parse_mode(755), "755")
+        self.assertEqual(GitFICommit.parse_mode(100644), "100644")
+        self.assertEqual(GitFICommit.parse_mode(100755), "100755")
+        self.assertEqual(GitFICommit.parse_mode(120000), "120000")
+        self.assertRaises(AssertionError, GitFICommit.parse_mode, 0)
+        self.assertRaises(AssertionError, GitFICommit.parse_mode, 123)
+        self.assertRaises(AssertionError, GitFICommit.parse_mode, 600)
+        self.assertRaises(AssertionError, GitFICommit.parse_mode, "644")
+        self.assertRaises(AssertionError, GitFICommit.parse_mode, "abc")
+
+    def test_parse_objname (self):
+        """GitFICommit.parse_objname() selftests."""
+        self.assertEqual(GitFICommit.parse_objname(1), ":1")
+        self.assertRaises(AssertionError, GitFICommit.parse_objname, 0)
+        self.assertRaises(AssertionError, GitFICommit.parse_objname, -1)
+        self.assertEqual(GitFICommit.parse_objname("0123456789" * 4),
+                         "0123456789" * 4)
+        self.assertEqual(GitFICommit.parse_objname("2468abcdef" * 4),
+                         "2468abcdef" * 4)
+        self.assertRaises(AssertionError, GitFICommit.parse_objname,
+                          "abcdefghij" * 4)
+
+    def test_parse_path (self):
+        """GitFICommit.parse_path() selftests."""
+        self.assertEqual(GitFICommit.parse_path("foo/bar"), "foo/bar")
+        self.assertEqual(GitFICommit.parse_path("path/with\n and \" in it"),
+                         '"path/with\\n and \\" in it"')
+        self.assertRaises(AssertionError, GitFICommit.parse_path, 1)
+        self.assertRaises(AssertionError, GitFICommit.parse_path, 0)
+        self.assertRaises(AssertionError, GitFICommit.parse_path, -1)
+        self.assertRaises(AssertionError, GitFICommit.parse_path, "foo//bar")
+        self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/bar/")
+        self.assertRaises(AssertionError, GitFICommit.parse_path, "/foo/bar")
+        self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/./bar")
+        self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/../bar")
+        self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/bar/.")
+        self.assertRaises(AssertionError, GitFICommit.parse_path, "foo/bar/..")
+        self.assertRaises(AssertionError, GitFICommit.parse_path, "./foo/bar")
+        self.assertRaises(AssertionError, GitFICommit.parse_path, "../foo/bar")
+
+
+class GitFastImport(object):
+
+    """Encapsulate communication with git fast-import."""
+
+    def __init__ (self, f, obj_fetcher, last_mark = 0):
+        """Set up self to communicate with a fast-import process through f."""
+        self.f = f  # File object where fast-import stream is written
+        self.obj_fetcher = obj_fetcher  # GitObjectFetcher instance
+        self.next_mark = last_mark + 1  # Next mark number
+        self.refs = set()  # Keep track of the refnames we've seen
+
+    def comment (self, s):
+        """Write the given comment in the fast-import stream."""
+        assert "\n" not in s, "Malformed comment: '%s'" % (s)
+        self.f.write("# %s\n" % (s))
+
+    def commit (self, ref, commitdata):
+        """Make a commit on the given ref, with the given GitFICommit.
+
+        Return the mark number identifying this commit.
+
+        """
+        self.f.write("""\
+commit %(ref)s
+mark :%(mark)i
+committer %(name)s <%(email)s> %(timestamp)i %(timezone)s
+data %(msgLength)i
+%(msg)s
+""" % {
+    'ref': ref,
+    'mark': self.next_mark,
+    'name': commitdata.name,
+    'email': commitdata.email,
+    'timestamp': commitdata.timestamp,
+    'timezone': commitdata.timezone,
+    'msgLength': len(commitdata.message),
+    'msg': commitdata.message,
+})
+
+        if ref not in self.refs:
+            self.refs.add(ref)
+            parent = ref + "^0"
+            if self.obj_fetcher.get_sha1(parent):
+                self.f.write("from %s\n" % (parent))
+
+        for op in commitdata.pathops:
+            self.f.write(" ".join(op))
+            self.f.write("\n")
+        self.f.write("\n")
+        retval = self.next_mark
+        self.next_mark += 1
+        return retval
+
+    def blob (self, data):
+        """Import the given blob.
+
+        Return the mark number identifying this blob.
+
+        """
+        self.f.write("blob\nmark :%i\ndata %i\n%s\n" %
+                     (self.next_mark, len(data), data))
+        retval = self.next_mark
+        self.next_mark += 1
+        return retval
+
+    def reset (self, ref, objname):
+        """Reset the given ref to point at the given Git object."""
+        self.f.write("reset %s\nfrom %s\n\n" %
+                     (ref, GitFICommit.parse_objname(objname)))
+        if ref not in self.refs:
+            self.refs.add(ref)
+
+
+class GitNotes(object):
+
+    """Encapsulate access to Git notes.
+
+    Simulates a dictionary of object name (SHA1) -> Git note mappings.
+
+    """
+
+    def __init__ (self, notes_ref, obj_fetcher):
+        """Create a new Git notes interface, bound to the given notes ref."""
+        self.notes_ref = notes_ref
+        self.obj_fetcher = obj_fetcher  # Used to get objects from repo
+        self.imports = []  # list: (objname, note data blob name) tuples
+
+    def __del__ (self):
+        """Verify that self.commit_notes() was called before destruction."""
+        if self.imports:
+            error("Missing call to self.commit_notes().")
+            error("%i notes are not committed!", len(self.imports))
+
+    def _load (self, objname):
+        """Return the note data associated with the given git object.
+
+        The note data is returned in string form. If no note is found
+        for the given object, None is returned.
+
+        """
+        try:
+            f = self.obj_fetcher.open_obj("%s:%s" % (self.notes_ref, objname))
+            ret = f.read()
+            f.close()
+        except KeyError:
+            ret = None
+        return ret
+
+    def __getitem__ (self, objname):
+        """Return the note contents associated with the given object.
+
+        Raise KeyError if given object has no associated note.
+
+        """
+        blobdata = self._load(objname)
+        if blobdata is None:
+            raise KeyError("Object '%s' has no note" % (objname))
+        return blobdata
+
+    def get (self, objname, default = None):
+        """Return the note contents associated with the given object.
+
+        Return given default if given object has no associated note.
+
+        """
+        blobdata = self._load(objname)
+        if blobdata is None:
+            return default
+        return blobdata
+
+    def import_note (self, objname, data, gfi):
+        """Tell git fast-import to store data as a note for objname.
+
+        This method uses the given GitFastImport object to create a
+        blob containing the given note data.  Also an entry mapping the
+        given object name to the created blob is stored until
+        commit_notes() is called.
+
+        Note that this method only works if it is later followed by a
+        call to self.commit_notes() (which produces the note commit
+        that refers to the blob produced here).
+
+        """
+        if not data.endswith("\n"):
+            data += "\n"
+        gfi.comment("Importing note for object %s" % (objname))
+        mark = gfi.blob(data)
+        self.imports.append((objname, mark))
+
+    def commit_notes (self, gfi, author, message):
+        """Produce a git fast-import note commit for the imported notes.
+
+        This method uses the given GitFastImport object to create a
+        commit on the notes ref, introducing the notes previously
+        submitted to import_note().
+
+        """
+        if not self.imports:
+            return
+        commitdata = GitFICommit(author[0], author[1],
+                                 time.time(), "0000", message)
+        for objname, blobname in self.imports:
+            assert isinstance(objname, int) and objname > 0
+            assert isinstance(blobname, int) and blobname > 0
+            commitdata.note(blobname, objname)
+        gfi.commit(self.notes_ref, commitdata)
+        self.imports = []
+
+
+class GitCachedNotes(GitNotes):
+
+    """Encapsulate access to Git notes (cached version).
+
+    Only use this class if no caching is done at a higher level.
+
+    Simulates a dictionary of object name (SHA1) -> Git note mappings.
+
+    """
+
+    def __init__ (self, notes_ref, obj_fetcher):
+        """Set up a caching wrapper around GitNotes."""
+        GitNotes.__init__(self, notes_ref, obj_fetcher)
+        self._cache = {}  # Cache: object name -> note data
+
+    def __del__ (self):
+        """Verify that GitNotes' destructor is called."""
+        GitNotes.__del__(self)
+
+    def _load (self, objname):
+        """Extend GitNotes._load() with a local objname -> note cache."""
+        if objname not in self._cache:
+            self._cache[objname] = GitNotes._load(self, objname)
+        return self._cache[objname]
+
+    def import_note (self, objname, data, gfi):
+        """Extend GitNotes.import_note() with a local objname -> note cache."""
+        if not data.endswith("\n"):
+            data += "\n"
+        assert objname not in self._cache
+        self._cache[objname] = data
+        GitNotes.import_note(self, objname, data, gfi)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/git_remote_helpers/setup.py b/git_remote_helpers/setup.py
new file mode 100644 (file)
index 0000000..4d434b6
--- /dev/null
@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+
+"""Distutils build/install script for the git_remote_helpers package."""
+
+from distutils.core import setup
+
+setup(
+    name = 'git_remote_helpers',
+    version = '0.1.0',
+    description = 'Git remote helper program for non-git repositories',
+    license = 'GPLv2',
+    author = 'The Git Community',
+    author_email = 'git@vger.kernel.org',
+    url = 'http://www.git-scm.com/',
+    package_dir = {'git_remote_helpers': ''},
+    packages = ['git_remote_helpers', 'git_remote_helpers.git'],
+)
diff --git a/git_remote_helpers/util.py b/git_remote_helpers/util.py
new file mode 100644 (file)
index 0000000..dce83e6
--- /dev/null
@@ -0,0 +1,194 @@
+#!/usr/bin/env python
+
+"""Misc. useful functionality used by the rest of this package.
+
+This module provides common functionality used by the other modules in
+this package.
+
+"""
+
+import sys
+import os
+import subprocess
+
+
+# Whether or not to show debug messages
+DEBUG = False
+
+def notify(msg, *args):
+    """Print a message to stderr."""
+    print >> sys.stderr, msg % args
+
+def debug (msg, *args):
+    """Print a debug message to stderr when DEBUG is enabled."""
+    if DEBUG:
+        print >> sys.stderr, msg % args
+
+def error (msg, *args):
+    """Print an error message to stderr."""
+    print >> sys.stderr, "ERROR:", msg % args
+
+def warn(msg, *args):
+    """Print a warning message to stderr."""
+    print >> sys.stderr, "warning:", msg % args
+
+def die (msg, *args):
+    """Print as error message to stderr and exit the program."""
+    error(msg, *args)
+    sys.exit(1)
+
+
+class ProgressIndicator(object):
+
+    """Simple progress indicator.
+
+    Displayed as a spinning character by default, but can be customized
+    by passing custom messages that overrides the spinning character.
+
+    """
+
+    States = ("|", "/", "-", "\\")
+
+    def __init__ (self, prefix = "", f = sys.stdout):
+        """Create a new ProgressIndicator, bound to the given file object."""
+        self.n = 0  # Simple progress counter
+        self.f = f  # Progress is written to this file object
+        self.prev_len = 0  # Length of previous msg (to be overwritten)
+        self.prefix = prefix  # Prefix prepended to each progress message
+        self.prefix_lens = [] # Stack of prefix string lengths
+
+    def pushprefix (self, prefix):
+        """Append the given prefix onto the prefix stack."""
+        self.prefix_lens.append(len(self.prefix))
+        self.prefix += prefix
+
+    def popprefix (self):
+        """Remove the last prefix from the prefix stack."""
+        prev_len = self.prefix_lens.pop()
+        self.prefix = self.prefix[:prev_len]
+
+    def __call__ (self, msg = None, lf = False):
+        """Indicate progress, possibly with a custom message."""
+        if msg is None:
+            msg = self.States[self.n % len(self.States)]
+        msg = self.prefix + msg
+        print >> self.f, "\r%-*s" % (self.prev_len, msg),
+        self.prev_len = len(msg.expandtabs())
+        if lf:
+            print >> self.f
+            self.prev_len = 0
+        self.n += 1
+
+    def finish (self, msg = "done", noprefix = False):
+        """Finalize progress indication with the given message."""
+        if noprefix:
+            self.prefix = ""
+        self(msg, True)
+
+
+def start_command (args, cwd = None, shell = False, add_env = None,
+                   stdin = subprocess.PIPE, stdout = subprocess.PIPE,
+                   stderr = subprocess.PIPE):
+    """Start the given command, and return a subprocess object.
+
+    This provides a simpler interface to the subprocess module.
+
+    """
+    env = None
+    if add_env is not None:
+        env = os.environ.copy()
+        env.update(add_env)
+    return subprocess.Popen(args, bufsize = 1, stdin = stdin, stdout = stdout,
+                            stderr = stderr, cwd = cwd, shell = shell,
+                            env = env, universal_newlines = True)
+
+
+def run_command (args, cwd = None, shell = False, add_env = None,
+                 flag_error = True):
+    """Run the given command to completion, and return its results.
+
+    This provides a simpler interface to the subprocess module.
+
+    The results are formatted as a 3-tuple: (exit_code, output, errors)
+
+    If flag_error is enabled, Error messages will be produced if the
+    subprocess terminated with a non-zero exit code and/or stderr
+    output.
+
+    The other arguments are passed on to start_command().
+
+    """
+    process = start_command(args, cwd, shell, add_env)
+    (output, errors) = process.communicate()
+    exit_code = process.returncode
+    if flag_error and errors:
+        error("'%s' returned errors:\n---\n%s---", " ".join(args), errors)
+    if flag_error and exit_code:
+        error("'%s' returned exit code %i", " ".join(args), exit_code)
+    return (exit_code, output, errors)
+
+
+def file_reader_method (missing_ok = False):
+    """Decorator for simplifying reading of files.
+
+    If missing_ok is True, a failure to open a file for reading will
+    not raise the usual IOError, but instead the wrapped method will be
+    called with f == None.  The method must in this case properly
+    handle f == None.
+
+    """
+    def _wrap (method):
+        """Teach given method to handle both filenames and file objects.
+
+        The given method must take a file object as its second argument
+        (the first argument being 'self', of course).  This decorator
+        will take a filename given as the second argument and promote
+        it to a file object.
+
+        """
+        def _wrapped_method (self, filename, *args, **kwargs):
+            if isinstance(filename, file):
+                f = filename
+            else:
+                try:
+                    f = open(filename, 'r')
+                except IOError:
+                    if missing_ok:
+                        f = None
+                    else:
+                        raise
+            try:
+                return method(self, f, *args, **kwargs)
+            finally:
+                if not isinstance(filename, file) and f:
+                    f.close()
+        return _wrapped_method
+    return _wrap
+
+
+def file_writer_method (method):
+    """Decorator for simplifying writing of files.
+
+    Enables the given method to handle both filenames and file objects.
+
+    The given method must take a file object as its second argument
+    (the first argument being 'self', of course).  This decorator will
+    take a filename given as the second argument and promote it to a
+    file object.
+
+    """
+    def _new_method (self, filename, *args, **kwargs):
+        if isinstance(filename, file):
+            f = filename
+        else:
+            # Make sure the containing directory exists
+            parent_dir = os.path.dirname(filename)
+            if not os.path.isdir(parent_dir):
+                os.makedirs(parent_dir)
+            f = open(filename, 'w')
+        try:
+            return method(self, f, *args, **kwargs)
+        finally:
+            if not isinstance(filename, file):
+                f.close()
+    return _new_method
index a0214b7004f141b7c918a5360d85f8f9e2db285c..364c7a84cbcf923deb72c2a91b4ec5f5d75bf4c3 100644 (file)
@@ -2,11 +2,13 @@
 # Tcl ignores the next line -*- tcl -*- \
 exec wish "$0" -- "$@"
 
-# Copyright © 2005-2008 Paul Mackerras.  All rights reserved.
+# Copyright © 2005-2009 Paul Mackerras.  All rights reserved.
 # This program is free software; it may be used, copied, modified
 # and distributed under the terms of the GNU General Public Licence,
 # either version 2, or (at your option) any later version.
 
+package require Tk
+
 proc gitdir {} {
     global env
     if {[info exists env(GIT_DIR)]} {
@@ -265,7 +267,7 @@ proc parseviewrevs {view revs} {
                }
                lappend badrev $line
            }
-       }                   
+       }
        error_popup "[mc "Error parsing revisions:"] $err"
        return {}
     }
@@ -987,6 +989,18 @@ proc removefakerow {id} {
     drawvisible
 }
 
+proc real_children {vp} {
+    global children nullid nullid2
+
+    set kids {}
+    foreach id $children($vp) {
+       if {$id ne $nullid && $id ne $nullid2} {
+           lappend kids $id
+       }
+    }
+    return $kids
+}
+
 proc first_real_child {vp} {
     global children nullid nullid2
 
@@ -1769,6 +1783,15 @@ proc removehead {id name} {
     unset headids($name)
 }
 
+proc ttk_toplevel {w args} {
+    global use_ttk
+    eval [linsert $args 0 ::toplevel $w]
+    if {$use_ttk} {
+        place [ttk::frame $w._toplevel_background] -x 0 -y 0 -relwidth 1 -relheight 1
+    }
+    return $w
+}
+
 proc make_transient {window origin} {
     global have_tk85
 
@@ -1787,10 +1810,13 @@ proc make_transient {window origin} {
     }
 }
 
-proc show_error {w top msg} {
+proc show_error {w top msg {mc mc}} {
+    global NS
+    if {![info exists NS]} {set NS ""}
+    if {[wm state $top] eq "withdrawn"} { wm deiconify $top }
     message $w.m -text $msg -justify center -aspect 400
     pack $w.m -side top -fill x -padx 20 -pady 20
-    button $w.ok -text [mc OK] -command "destroy $top"
+    ${NS}::button $w.ok -default active -text [$mc OK] -command "destroy $top"
     pack $w.ok -side bottom -fill x
     bind $top <Visibility> "grab $top; focus $top"
     bind $top <Key-Return> "destroy $top"
@@ -1800,45 +1826,56 @@ proc show_error {w top msg} {
 }
 
 proc error_popup {msg {owner .}} {
-    set w .error
-    toplevel $w
-    make_transient $w $owner
-    show_error $w $w $msg
+    if {[tk windowingsystem] eq "win32"} {
+        tk_messageBox -icon error -type ok -title [wm title .] \
+            -parent $owner -message $msg
+    } else {
+        set w .error
+        ttk_toplevel $w
+        make_transient $w $owner
+        show_error $w $w $msg
+    }
 }
 
 proc confirm_popup {msg {owner .}} {
-    global confirm_ok
+    global confirm_ok NS
     set confirm_ok 0
     set w .confirm
-    toplevel $w
+    ttk_toplevel $w
     make_transient $w $owner
     message $w.m -text $msg -justify center -aspect 400
     pack $w.m -side top -fill x -padx 20 -pady 20
-    button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
+    ${NS}::button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
     pack $w.ok -side left -fill x
-    button $w.cancel -text [mc Cancel] -command "destroy $w"
+    ${NS}::button $w.cancel -text [mc Cancel] -command "destroy $w"
     pack $w.cancel -side right -fill x
     bind $w <Visibility> "grab $w; focus $w"
     bind $w <Key-Return> "set confirm_ok 1; destroy $w"
     bind $w <Key-space>  "set confirm_ok 1; destroy $w"
     bind $w <Key-Escape> "destroy $w"
+    tk::PlaceWindow $w widget $owner
     tkwait window $w
     return $confirm_ok
 }
 
 proc setoptions {} {
-    option add *Panedwindow.showHandle 1 startupFile
-    option add *Panedwindow.sashRelief raised startupFile
+    if {[tk windowingsystem] ne "win32"} {
+        option add *Panedwindow.showHandle 1 startupFile
+        option add *Panedwindow.sashRelief raised startupFile
+        if {[tk windowingsystem] ne "aqua"} {
+            option add *Menu.font uifont startupFile
+        }
+    } else {
+        option add *Menu.TearOff 0 startupFile
+    }
     option add *Button.font uifont startupFile
     option add *Checkbutton.font uifont startupFile
     option add *Radiobutton.font uifont startupFile
-    if {[tk windowingsystem] ne "aqua"} {
-       option add *Menu.font uifont startupFile
-    }
     option add *Menubutton.font uifont startupFile
     option add *Label.font uifont startupFile
     option add *Message.font uifont startupFile
     option add *Entry.font uifont startupFile
+    option add *Labelframe.font uifont startupFile
 }
 
 # Make a menu and submenus.
@@ -1895,6 +1932,22 @@ proc mca {str} {
     return [string map {&& & & {}} [mc $str]]
 }
 
+proc makedroplist {w varname args} {
+    global use_ttk
+    if {$use_ttk} {
+        set width 0
+        foreach label $args {
+            set cx [string length $label]
+            if {$cx > $width} {set width $cx}
+        }
+       set gm [ttk::combobox $w -width $width -state readonly\
+                   -textvariable $varname -values $args]
+    } else {
+       set gm [eval [linsert $args 0 tk_optionMenu $w $varname]]
+    }
+    return $gm
+}
+
 proc makewindow {} {
     global canv canv2 canv3 linespc charspc ctext cflist cscroll
     global tabstop
@@ -1910,7 +1963,7 @@ proc makewindow {} {
     global headctxmenu progresscanv progressitem progresscoords statusw
     global fprogitem fprogcoord lastprogupdate progupdatepending
     global rprogitem rprogcoord rownumsel numcommits
-    global have_tk85
+    global have_tk85 use_ttk NS
 
     # The "mc" arguments here are purely so that xgettext
     # sees the following string as needing to be translated
@@ -1962,8 +2015,13 @@ proc makewindow {} {
     makemenu .bar $bar
     . configure -menu .bar
 
+    if {$use_ttk} {
+        # cover the non-themed toplevel with a themed frame.
+        place [ttk::frame ._main_background] -x 0 -y 0 -relwidth 1 -relheight 1
+    }
+
     # the gui has upper and lower half, parts of a paned window.
-    panedwindow .ctop -orient vertical
+    ${NS}::panedwindow .ctop -orient vertical
 
     # possibly use assumed geometry
     if {![info exists geometry(pwsash0)]} {
@@ -1971,14 +2029,17 @@ proc makewindow {} {
         set geometry(topwidth) [expr {80 * $charspc}]
         set geometry(botheight) [expr {15 * $linespc}]
         set geometry(botwidth) [expr {50 * $charspc}]
-        set geometry(pwsash0) "[expr {40 * $charspc}] 2"
-        set geometry(pwsash1) "[expr {60 * $charspc}] 2"
+        set geometry(pwsash0) [list [expr {40 * $charspc}] 2]
+        set geometry(pwsash1) [list [expr {60 * $charspc}] 2]
     }
 
     # the upper half will have a paned window, a scroll bar to the right, and some stuff below
-    frame .tf -height $geometry(topheight) -width $geometry(topwidth)
-    frame .tf.histframe
-    panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
+    ${NS}::frame .tf -height $geometry(topheight) -width $geometry(topwidth)
+    ${NS}::frame .tf.histframe
+    ${NS}::panedwindow .tf.histframe.pwclist -orient horizontal
+    if {!$use_ttk} {
+       .tf.histframe.pwclist configure -sashpad 0 -handlesize 4
+    }
 
     # create three canvases
     set cscroll .tf.histframe.csb
@@ -1998,19 +2059,28 @@ proc makewindow {} {
        -selectbackground $selectbgcolor \
        -background $bgcolor -bd 0 -yscrollincr $linespc
     .tf.histframe.pwclist add $canv3
-    eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
-    eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
+    if {$use_ttk} {
+       bind .tf.histframe.pwclist <Map> {
+           bind %W <Map> {}
+           .tf.histframe.pwclist sashpos 1 [lindex $::geometry(pwsash1) 0]
+           .tf.histframe.pwclist sashpos 0 [lindex $::geometry(pwsash0) 0]
+       }
+    } else {
+       eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
+       eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
+    }
 
     # a scroll bar to rule them
-    scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
+    ${NS}::scrollbar $cscroll -command {allcanvs yview}
+    if {!$use_ttk} {$cscroll configure -highlightthickness 0}
     pack $cscroll -side right -fill y
     bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
     lappend bglist $canv $canv2 $canv3
     pack .tf.histframe.pwclist -fill both -expand 1 -side left
 
     # we have two button bars at bottom of top frame. Bar 1
-    frame .tf.bar
-    frame .tf.lbar -height 15
+    ${NS}::frame .tf.bar
+    ${NS}::frame .tf.lbar -height 15
 
     set sha1entry .tf.bar.sha1
     set entries $sha1entry
@@ -2019,7 +2089,7 @@ proc makewindow {} {
        -command gotocommit -width 8
     $sha1but conf -disabledforeground [$sha1but cget -foreground]
     pack .tf.bar.sha1label -side left
-    entry $sha1entry -width 40 -font textfont -textvariable sha1string
+    ${NS}::entry $sha1entry -width 40 -font textfont -textvariable sha1string
     trace add variable sha1string write sha1change
     pack $sha1entry -side left -pady 2
 
@@ -2039,36 +2109,43 @@ proc makewindow {} {
        0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
        0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
     }
-    button .tf.bar.leftbut -image bm-left -command goback \
+    ${NS}::button .tf.bar.leftbut -image bm-left -command goback \
        -state disabled -width 26
     pack .tf.bar.leftbut -side left -fill y
-    button .tf.bar.rightbut -image bm-right -command goforw \
+    ${NS}::button .tf.bar.rightbut -image bm-right -command goforw \
        -state disabled -width 26
     pack .tf.bar.rightbut -side left -fill y
 
-    label .tf.bar.rowlabel -text [mc "Row"]
+    ${NS}::label .tf.bar.rowlabel -text [mc "Row"]
     set rownumsel {}
-    label .tf.bar.rownum -width 7 -font textfont -textvariable rownumsel \
+    ${NS}::label .tf.bar.rownum -width 7 -textvariable rownumsel \
        -relief sunken -anchor e
-    label .tf.bar.rowlabel2 -text "/"
-    label .tf.bar.numcommits -width 7 -font textfont -textvariable numcommits \
+    ${NS}::label .tf.bar.rowlabel2 -text "/"
+    ${NS}::label .tf.bar.numcommits -width 7 -textvariable numcommits \
        -relief sunken -anchor e
     pack .tf.bar.rowlabel .tf.bar.rownum .tf.bar.rowlabel2 .tf.bar.numcommits \
        -side left
+    if {!$use_ttk} {
+        foreach w {rownum numcommits} {.tf.bar.$w configure -font textfont}
+    }
     global selectedline
     trace add variable selectedline write selectedline_change
 
     # Status label and progress bar
     set statusw .tf.bar.status
-    label $statusw -width 15 -relief sunken
+    ${NS}::label $statusw -width 15 -relief sunken
     pack $statusw -side left -padx 5
-    set h [expr {[font metrics uifont -linespace] + 2}]
-    set progresscanv .tf.bar.progress
-    canvas $progresscanv -relief sunken -height $h -borderwidth 2
-    set progressitem [$progresscanv create rect -1 0 0 $h -fill green]
-    set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
-    set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
-    pack $progresscanv -side right -expand 1 -fill x
+    if {$use_ttk} {
+       set progresscanv [ttk::progressbar .tf.bar.progress]
+    } else {
+       set h [expr {[font metrics uifont -linespace] + 2}]
+       set progresscanv .tf.bar.progress
+       canvas $progresscanv -relief sunken -height $h -borderwidth 2
+       set progressitem [$progresscanv create rect -1 0 0 $h -fill green]
+       set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
+       set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
+    }
+    pack $progresscanv -side right -expand 1 -fill x -padx {0 2}
     set progresscoords {0 0}
     set fprogcoord 0
     set rprogcoord 0
@@ -2077,14 +2154,14 @@ proc makewindow {} {
     set progupdatepending 0
 
     # build up the bottom bar of upper window
-    label .tf.lbar.flabel -text "[mc "Find"] "
-    button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1}
-    button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1}
-    label .tf.lbar.flab2 -text " [mc "commit"] "
+    ${NS}::label .tf.lbar.flabel -text "[mc "Find"] "
+    ${NS}::button .tf.lbar.fnext -text [mc "next"] -command {dofind 1 1}
+    ${NS}::button .tf.lbar.fprev -text [mc "prev"] -command {dofind -1 1}
+    ${NS}::label .tf.lbar.flab2 -text " [mc "commit"] "
     pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
        -side left -fill y
     set gdttype [mc "containing:"]
-    set gm [tk_optionMenu .tf.lbar.gdttype gdttype \
+    set gm [makedroplist .tf.lbar.gdttype gdttype \
                [mc "containing:"] \
                [mc "touching paths:"] \
                [mc "adding/removing string:"]]
@@ -2094,14 +2171,14 @@ proc makewindow {} {
     set findstring {}
     set fstring .tf.lbar.findstring
     lappend entries $fstring
-    entry $fstring -width 30 -font textfont -textvariable findstring
+    ${NS}::entry $fstring -width 30 -font textfont -textvariable findstring
     trace add variable findstring write find_change
     set findtype [mc "Exact"]
-    set findtypemenu [tk_optionMenu .tf.lbar.findtype \
-                     findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]]
+    set findtypemenu [makedroplist .tf.lbar.findtype \
+                         findtype [mc "Exact"] [mc "IgnCase"] [mc "Regexp"]]
     trace add variable findtype write findcom_change
     set findloc [mc "All fields"]
-    tk_optionMenu .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \
+    makedroplist .tf.lbar.findloc findloc [mc "All fields"] [mc "Headline"] \
        [mc "Comments"] [mc "Author"] [mc "Committer"]
     trace add variable findloc write find_change
     pack .tf.lbar.findloc -side right
@@ -2113,38 +2190,41 @@ proc makewindow {} {
     pack .tf.bar -in .tf -side bottom -fill x
     pack .tf.histframe -fill both -side top -expand 1
     .ctop add .tf
-    .ctop paneconfigure .tf -height $geometry(topheight)
-    .ctop paneconfigure .tf -width $geometry(topwidth)
+    if {!$use_ttk} {
+       .ctop paneconfigure .tf -height $geometry(topheight)
+       .ctop paneconfigure .tf -width $geometry(topwidth)
+    }
 
     # now build up the bottom
-    panedwindow .pwbottom -orient horizontal
+    ${NS}::panedwindow .pwbottom -orient horizontal
 
     # lower left, a text box over search bar, scroll bar to the right
     # if we know window height, then that will set the lower text height, otherwise
     # we set lower text height which will drive window height
     if {[info exists geometry(main)]} {
-        frame .bleft -width $geometry(botwidth)
+       ${NS}::frame .bleft -width $geometry(botwidth)
     } else {
-        frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
+       ${NS}::frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
     }
-    frame .bleft.top
-    frame .bleft.mid
-    frame .bleft.bottom
+    ${NS}::frame .bleft.top
+    ${NS}::frame .bleft.mid
+    ${NS}::frame .bleft.bottom
 
-    button .bleft.top.search -text [mc "Search"] -command dosearch
+    ${NS}::button .bleft.top.search -text [mc "Search"] -command dosearch
     pack .bleft.top.search -side left -padx 5
     set sstring .bleft.top.sstring
-    entry $sstring -width 20 -font textfont -textvariable searchstring
+    set searchstring ""
+    ${NS}::entry $sstring -width 20 -font textfont -textvariable searchstring
     lappend entries $sstring
     trace add variable searchstring write incrsearch
     pack $sstring -side left -expand 1 -fill x
-    radiobutton .bleft.mid.diff -text [mc "Diff"] \
+    ${NS}::radiobutton .bleft.mid.diff -text [mc "Diff"] \
        -command changediffdisp -variable diffelide -value {0 0}
-    radiobutton .bleft.mid.old -text [mc "Old version"] \
+    ${NS}::radiobutton .bleft.mid.old -text [mc "Old version"] \
        -command changediffdisp -variable diffelide -value {0 1}
-    radiobutton .bleft.mid.new -text [mc "New version"] \
+    ${NS}::radiobutton .bleft.mid.new -text [mc "New version"] \
        -command changediffdisp -variable diffelide -value {1 0}
-    label .bleft.mid.labeldiffcontext -text "      [mc "Lines of context"]: "
+    ${NS}::label .bleft.mid.labeldiffcontext -text "      [mc "Lines of context"]: "
     pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
     spinbox .bleft.mid.diffcontext -width 5 -font textfont \
        -from 0 -increment 1 -to 10000000 \
@@ -2154,7 +2234,7 @@ proc makewindow {} {
     trace add variable diffcontextstring write diffcontextchange
     lappend entries .bleft.mid.diffcontext
     pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
-    checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
+    ${NS}::checkbutton .bleft.mid.ignspace -text [mc "Ignore space change"] \
        -command changeignorespace -variable ignorespace
     pack .bleft.mid.ignspace -side left -padx 5
     set ctext .bleft.bottom.ctext
@@ -2165,9 +2245,8 @@ proc makewindow {} {
     if {$have_tk85} {
        $ctext conf -tabstyle wordprocessor
     }
-    scrollbar .bleft.bottom.sb -command "$ctext yview"
-    scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h \
-       -width 10
+    ${NS}::scrollbar .bleft.bottom.sb -command "$ctext yview"
+    ${NS}::scrollbar .bleft.bottom.sbhorizontal -command "$ctext xview" -orient h
     pack .bleft.top -side top -fill x
     pack .bleft.mid -side top -fill x
     grid $ctext .bleft.bottom.sb -sticky nsew
@@ -2207,14 +2286,16 @@ proc makewindow {} {
     $ctext tag conf found -back yellow
 
     .pwbottom add .bleft
-    .pwbottom paneconfigure .bleft -width $geometry(botwidth)
+    if {!$use_ttk} {
+       .pwbottom paneconfigure .bleft -width $geometry(botwidth)
+    }
 
     # lower right
-    frame .bright
-    frame .bright.mode
-    radiobutton .bright.mode.patch -text [mc "Patch"] \
+    ${NS}::frame .bright
+    ${NS}::frame .bright.mode
+    ${NS}::radiobutton .bright.mode.patch -text [mc "Patch"] \
        -command reselectline -variable cmitmode -value "patch"
-    radiobutton .bright.mode.tree -text [mc "Tree"] \
+    ${NS}::radiobutton .bright.mode.tree -text [mc "Tree"] \
        -command reselectline -variable cmitmode -value "tree"
     grid .bright.mode.patch .bright.mode.tree -sticky ew
     pack .bright.mode -side top -fill x
@@ -2230,7 +2311,7 @@ proc makewindow {} {
        -spacing1 1 -spacing3 1
     lappend bglist $cflist
     lappend fglist $cflist
-    scrollbar .bright.sb -command "$cflist yview"
+    ${NS}::scrollbar .bright.sb -command "$cflist yview"
     pack .bright.sb -side right -fill y
     pack $cflist -side left -fill both -expand 1
     $cflist tag configure highlight \
@@ -2265,6 +2346,17 @@ proc makewindow {} {
         set ::BM "2"
     }
 
+    if {$use_ttk} {
+        bind .ctop <Map> {
+            bind %W <Map> {}
+            %W sashpos 0 $::geometry(topheight)
+        }
+        bind .pwbottom <Map> {
+            bind %W <Map> {}
+            %W sashpos 0 $::geometry(botwidth)
+        }
+    }
+
     bind .pwbottom <Configure> {resizecdetpanes %W %w}
     pack .ctop -fill both -expand 1
     bindall <1> {selcanvline %W %x %y}
@@ -2484,7 +2576,12 @@ proc click {w} {
 proc adjustprogress {} {
     global progresscanv progressitem progresscoords
     global fprogitem fprogcoord lastprogupdate progupdatepending
-    global rprogitem rprogcoord
+    global rprogitem rprogcoord use_ttk
+
+    if {$use_ttk} {
+       $progresscanv configure -value [expr {int($fprogcoord * 100)}]
+       return
+    }
 
     set w [expr {[winfo width $progresscanv] - 4}]
     set x0 [expr {$w * [lindex $progresscoords 0]}]
@@ -2519,9 +2616,9 @@ proc savestuff {w} {
     global maxwidth showneartags showlocalchanges
     global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
     global cmitmode wrapcomment datetimeformat limitdiffs
-    global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
-    global autoselect extdifftool perfile_attrs markbgcolor
-    global hideremotes
+    global colors uicolor bgcolor fgcolor diffcolors diffcontext selectbgcolor
+    global autoselect extdifftool perfile_attrs markbgcolor use_ttk
+    global hideremotes want_ttk
 
     if {$stuffsaved} return
     if {![winfo viewable .]} return
@@ -2546,6 +2643,8 @@ proc savestuff {w} {
        puts $f [list set showlocalchanges $showlocalchanges]
        puts $f [list set datetimeformat $datetimeformat]
        puts $f [list set limitdiffs $limitdiffs]
+       puts $f [list set uicolor $uicolor]
+       puts $f [list set want_ttk $want_ttk]
        puts $f [list set bgcolor $bgcolor]
        puts $f [list set fgcolor $fgcolor]
        puts $f [list set colors $colors]
@@ -2560,8 +2659,13 @@ proc savestuff {w} {
        puts $f "set geometry(state) [wm state .]"
        puts $f "set geometry(topwidth) [winfo width .tf]"
        puts $f "set geometry(topheight) [winfo height .tf]"
-        puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
-        puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
+       if {$use_ttk} {
+           puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sashpos 0] 1\""
+           puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sashpos 1] 1\""
+       } else {
+           puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
+           puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
+       }
        puts $f "set geometry(botwidth) [winfo width .bleft]"
        puts $f "set geometry(botheight) [winfo height .bleft]"
 
@@ -2579,10 +2683,15 @@ proc savestuff {w} {
 }
 
 proc resizeclistpanes {win w} {
-    global oldwidth
+    global oldwidth use_ttk
     if {[info exists oldwidth($win)]} {
-       set s0 [$win sash coord 0]
-       set s1 [$win sash coord 1]
+       if {$use_ttk} {
+           set s0 [$win sashpos 0]
+           set s1 [$win sashpos 1]
+       } else {
+           set s0 [$win sash coord 0]
+           set s1 [$win sash coord 1]
+       }
        if {$w < 60} {
            set sash0 [expr {int($w/2 - 2)}]
            set sash1 [expr {int($w*5/6 - 2)}]
@@ -2603,16 +2712,25 @@ proc resizeclistpanes {win w} {
                }
            }
        }
-       $win sash place 0 $sash0 [lindex $s0 1]
-       $win sash place 1 $sash1 [lindex $s1 1]
+       if {$use_ttk} {
+           $win sashpos 0 $sash0
+           $win sashpos 1 $sash1
+       } else {
+           $win sash place 0 $sash0 [lindex $s0 1]
+           $win sash place 1 $sash1 [lindex $s1 1]
+       }
     }
     set oldwidth($win) $w
 }
 
 proc resizecdetpanes {win w} {
-    global oldwidth
+    global oldwidth use_ttk
     if {[info exists oldwidth($win)]} {
-       set s0 [$win sash coord 0]
+       if {$use_ttk} {
+           set s0 [$win sashpos 0]
+       } else {
+           set s0 [$win sash coord 0]
+       }
        if {$w < 60} {
            set sash0 [expr {int($w*3/4 - 2)}]
        } else {
@@ -2625,7 +2743,11 @@ proc resizecdetpanes {win w} {
                set sash0 [expr {$w - 15}]
            }
        }
-       $win sash place 0 $sash0 [lindex $s0 1]
+       if {$use_ttk} {
+           $win sashpos 0 $sash0
+       } else {
+           $win sash place 0 $sash0 [lindex $s0 1]
+       }
     }
     set oldwidth($win) $w
 }
@@ -2645,31 +2767,33 @@ proc bindall {event action} {
 }
 
 proc about {} {
-    global uifont
+    global uifont NS
     set w .about
     if {[winfo exists $w]} {
        raise $w
        return
     }
-    toplevel $w
+    ttk_toplevel $w
     wm title $w [mc "About gitk"]
     make_transient $w .
     message $w.m -text [mc "
 Gitk - a commit viewer for git
 
-Copyright © 2005-2008 Paul Mackerras
+Copyright \u00a9 2005-2009 Paul Mackerras
 
 Use and redistribute under the terms of the GNU General Public License"] \
            -justify center -aspect 400 -border 2 -bg white -relief groove
     pack $w.m -side top -fill x -padx 2 -pady 2
-    button $w.ok -text [mc "Close"] -command "destroy $w" -default active
+    ${NS}::button $w.ok -text [mc "Close"] -command "destroy $w" -default active
     pack $w.ok -side bottom
     bind $w <Visibility> "focus $w.ok"
     bind $w <Key-Escape> "destroy $w"
     bind $w <Key-Return> "destroy $w"
+    tk::PlaceWindow $w widget .
 }
 
 proc keys {} {
+    global NS
     set w .keys
     if {[winfo exists $w]} {
        raise $w
@@ -2680,7 +2804,7 @@ proc keys {} {
     } else {
        set M1T Ctrl
     }
-    toplevel $w
+    ttk_toplevel $w
     wm title $w [mc "Gitk key bindings"]
     make_transient $w .
     message $w.m -text "
@@ -2724,7 +2848,7 @@ proc keys {} {
 " \
            -justify left -bg white -border 2 -relief groove
     pack $w.m -side top -fill both -padx 2 -pady 2
-    button $w.ok -text [mc "Close"] -command "destroy $w" -default active
+    ${NS}::button $w.ok -text [mc "Close"] -command "destroy $w" -default active
     bind $w <Key-Escape> [list destroy $w]
     pack $w.ok -side bottom
     bind $w <Visibility> "focus $w.ok"
@@ -3377,6 +3501,9 @@ proc index_sha1 {fname} {
 
 # Turn an absolute path into one relative to the current directory
 proc make_relative {f} {
+    if {[file pathtype $f] eq "relative"} {
+       return $f
+    }
     set elts [file split $f]
     set here [file split [pwd]]
     set ei 0
@@ -3839,16 +3966,16 @@ proc editview {} {
 
 proc vieweditor {top n title} {
     global newviewname newviewopts viewfiles bgcolor
-    global known_view_options
+    global known_view_options NS
 
-    toplevel $top
+    ttk_toplevel $top
     wm title $top [concat $title [mc "-- criteria for selecting revisions"]]
     make_transient $top .
 
     # View name
-    frame $top.nfr
-    label $top.nl -text [mc "View Name:"]
-    entry $top.name -width 20 -textvariable newviewname($n)
+    ${NS}::frame $top.nfr
+    ${NS}::label $top.nl -text [mc "View Name"]
+    ${NS}::entry $top.name -width 20 -textvariable newviewname($n)
     pack $top.nfr -in $top -fill x -pady 5 -padx 3
     pack $top.nl -in $top.nfr -side left -padx {0 5}
     pack $top.name -in $top.nfr -side left -padx {0 25}
@@ -3867,13 +3994,13 @@ proc vieweditor {top n title} {
        if {$flags eq "+" || $flags eq "*"} {
            set cframe $top.fr$cnt
            incr cnt
-           frame $cframe
+           ${NS}::frame $cframe
            pack $cframe -in $top -fill x -pady 3 -padx 3
            set cexpand [expr {$flags eq "*"}]
         } elseif {$flags eq ".." || $flags eq "*."} {
            set cframe $top.fr$cnt
            incr cnt
-           frame $cframe
+           ${NS}::frame $cframe
            pack $cframe -in $top -fill x -pady 3 -padx [list 15 3]
            set cexpand [expr {$flags eq "*."}]
        } else {
@@ -3881,31 +4008,31 @@ proc vieweditor {top n title} {
        }
 
        if {$type eq "l"} {
-            label $cframe.l_$id -text $title
+            ${NS}::label $cframe.l_$id -text $title
             pack $cframe.l_$id -in $cframe -side left -pady [list 3 0] -anchor w
        } elseif {$type eq "b"} {
-           checkbutton $cframe.c_$id -text $title -variable newviewopts($n,$id)
+           ${NS}::checkbutton $cframe.c_$id -text $title -variable newviewopts($n,$id)
            pack $cframe.c_$id -in $cframe -side left \
                -padx [list $lxpad 0] -expand $cexpand -anchor w
        } elseif {[regexp {^r(\d+)$} $type type sz]} {
            regexp {^(.*_)} $id uselessvar button_id
-           radiobutton $cframe.c_$id -text $title -variable newviewopts($n,$button_id) -value $sz
+           ${NS}::radiobutton $cframe.c_$id -text $title -variable newviewopts($n,$button_id) -value $sz
            pack $cframe.c_$id -in $cframe -side left \
                -padx [list $lxpad 0] -expand $cexpand -anchor w
        } elseif {[regexp {^t(\d+)$} $type type sz]} {
-           message $cframe.l_$id -aspect 1500 -text $title
-           entry $cframe.e_$id -width $sz -background $bgcolor \
+           ${NS}::label $cframe.l_$id -text $title
+           ${NS}::entry $cframe.e_$id -width $sz -background $bgcolor \
                -textvariable newviewopts($n,$id)
            pack $cframe.l_$id -in $cframe -side left -padx [list $lxpad 0]
            pack $cframe.e_$id -in $cframe -side left -expand 1 -fill x
        } elseif {[regexp {^t(\d+)=$} $type type sz]} {
-           message $cframe.l_$id -aspect 1500 -text $title
-           entry $cframe.e_$id -width $sz -background $bgcolor \
+           ${NS}::label $cframe.l_$id -text $title
+           ${NS}::entry $cframe.e_$id -width $sz -background $bgcolor \
                -textvariable newviewopts($n,$id)
            pack $cframe.l_$id -in $cframe -side top -pady [list 3 0] -anchor w
            pack $cframe.e_$id -in $cframe -side top -fill x
        } elseif {$type eq "path"} {
-           message $top.l -aspect 1500 -text $title
+           ${NS}::label $top.l -text $title
            pack $top.l -in $top -side top -pady [list 3 0] -anchor w -padx 3
            text $top.t -width 40 -height 5 -background $bgcolor -font uifont
            if {[info exists viewfiles($n)]} {
@@ -3920,10 +4047,10 @@ proc vieweditor {top n title} {
        }
     }
 
-    frame $top.buts
-    button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
-    button $top.buts.apply -text [mc "Apply (F5)"] -command [list newviewok $top $n 1]
-    button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
+    ${NS}::frame $top.buts
+    ${NS}::button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n]
+    ${NS}::button $top.buts.apply -text [mc "Apply (F5)"] -command [list newviewok $top $n 1]
+    ${NS}::button $top.buts.can -text [mc "Cancel"] -command [list destroy $top]
     bind $top <Control-Return> [list newviewok $top $n]
     bind $top <F5> [list newviewok $top $n 1]
     bind $top <Escape> [list destroy $top]
@@ -6787,14 +6914,13 @@ proc selectline {l isnew {desired_loc {}}} {
     make_secsel $id
 
     if {$isnew} {
-       addtohistory [list selbyid $id]
+       addtohistory [list selbyid $id 0] savecmitpos
     }
 
     $sha1entry delete 0 end
     $sha1entry insert 0 $id
     if {$autoselect} {
-       $sha1entry selection from 0
-       $sha1entry selection to end
+       $sha1entry selection range 0 end
     }
     rhighlight_sel $id
 
@@ -6939,10 +7065,12 @@ proc reselectline {} {
     }
 }
 
-proc addtohistory {cmd} {
+proc addtohistory {cmd {saveproc {}}} {
     global history historyindex curview
 
-    set elt [list $curview $cmd]
+    unset_posvars
+    save_position
+    set elt [list $curview $cmd $saveproc {}]
     if {$historyindex > 0
        && [lindex $history [expr {$historyindex - 1}]] == $elt} {
        return
@@ -6962,14 +7090,45 @@ proc addtohistory {cmd} {
     .tf.bar.rightbut conf -state disabled
 }
 
+# save the scrolling position of the diff display pane
+proc save_position {} {
+    global historyindex history
+
+    if {$historyindex < 1} return
+    set hi [expr {$historyindex - 1}]
+    set fn [lindex $history $hi 2]
+    if {$fn ne {}} {
+       lset history $hi 3 [eval $fn]
+    }
+}
+
+proc unset_posvars {} {
+    global last_posvars
+
+    if {[info exists last_posvars]} {
+       foreach {var val} $last_posvars {
+           global $var
+           catch {unset $var}
+       }
+       unset last_posvars
+    }
+}
+
 proc godo {elt} {
-    global curview
+    global curview last_posvars
 
     set view [lindex $elt 0]
     set cmd [lindex $elt 1]
+    set pv [lindex $elt 3]
     if {$curview != $view} {
        showview $view
     }
+    unset_posvars
+    foreach {var val} $pv {
+       global $var
+       set $var $val
+    }
+    set last_posvars $pv
     eval $cmd
 }
 
@@ -6978,6 +7137,7 @@ proc goback {} {
     focus .
 
     if {$historyindex > 1} {
+       save_position
        incr historyindex -1
        godo [lindex $history [expr {$historyindex - 1}]]
        .tf.bar.rightbut conf -state normal
@@ -6992,6 +7152,7 @@ proc goforw {} {
     focus .
 
     if {$historyindex < [llength $history]} {
+       save_position
        set cmd [lindex $history $historyindex]
        incr historyindex
        godo $cmd
@@ -7224,7 +7385,7 @@ proc diffcmd {ids flags} {
        set cmd [concat | git diff-index --cached $flags]
        if {[llength $ids] > 1} {
            # comparing index with specific revision
-           if {$i == 0} {
+           if {$j == 0} {
                lappend cmd -R [lindex $ids 1]
            } else {
                lappend cmd [lindex $ids 0]
@@ -7343,7 +7504,11 @@ proc getblobdiffs {ids} {
     if {[package vcompare $git_version "1.6.1"] >= 0} {
        set textconv "--textconv"
     }
-    set cmd [diffcmd $ids "-p $textconv -C --cc --no-commit-id -U$diffcontext"]
+    set submodule {}
+    if {[package vcompare $git_version "1.6.6"] >= 0} {
+       set submodule "--submodule"
+    }
+    set cmd [diffcmd $ids "-p $textconv $submodule  -C --cc --no-commit-id -U$diffcontext"]
     if {$ignorespace} {
        append cmd " -w"
     }
@@ -7363,6 +7528,34 @@ proc getblobdiffs {ids} {
     filerun $bdf [list getblobdiffline $bdf $diffids]
 }
 
+proc savecmitpos {} {
+    global ctext cmitmode
+
+    if {$cmitmode eq "tree"} {
+       return {}
+    }
+    return [list target_scrollpos [$ctext index @0,0]]
+}
+
+proc savectextpos {} {
+    global ctext
+
+    return [list target_scrollpos [$ctext index @0,0]]
+}
+
+proc maybe_scroll_ctext {ateof} {
+    global ctext target_scrollpos
+
+    if {![info exists target_scrollpos]} return
+    if {!$ateof} {
+       set nlines [expr {[winfo height $ctext]
+                         / [font metrics textfont -linespace]}]
+       if {[$ctext compare "$target_scrollpos + $nlines lines" <= end]} return
+    }
+    $ctext yview $target_scrollpos
+    unset target_scrollpos
+}
+
 proc setinlist {var i val} {
     global $var
 
@@ -7481,6 +7674,21 @@ proc getblobdiffline {bdf ids} {
            set diffnparents [expr {[string length $ats] - 1}]
            set diffinhdr 0
 
+       } elseif {![string compare -length 10 "Submodule " $line]} {
+           # start of a new submodule
+           if {[string compare [$ctext get "end - 4c" end] "\n \n\n"]} {
+               $ctext insert end "\n";     # Add newline after commit message
+           }
+           set curdiffstart [$ctext index "end - 1c"]
+           lappend ctext_file_names ""
+           set fname [string range $line 10 [expr [string last " " $line] - 1]]
+           lappend ctext_file_lines $fname
+           makediffhdr $fname $ids
+           $ctext insert end "\n$line\n" filesep
+       } elseif {![string compare -length 3 "  >" $line]} {
+           $ctext insert end "$line\n" dresult
+       } elseif {![string compare -length 3 "  <" $line]} {
+           $ctext insert end "$line\n" d0
        } elseif {$diffinhdr} {
            if {![string compare -length 12 "rename from " $line]} {
                set fname [string range $line [expr 6 + [string first " from " $line] ] end]
@@ -7558,6 +7766,7 @@ proc getblobdiffline {bdf ids} {
     if {[info exists seehere]} {
        mark_ctext_line [lindex [split $seehere .] 0]
     }
+    maybe_scroll_ctext [eof $bdf]
     $ctext conf -state disabled
     if {[eof $bdf]} {
        catch {close $bdf}
@@ -8057,7 +8266,7 @@ proc lineclick {x y id isnew} {
     }
 
     if {$isnew} {
-       addtohistory [list lineclick $x $y $id 0]
+       addtohistory [list lineclick $x $y $id 0] savectextpos
     }
     # fill the details pane with info about this line
     $ctext conf -state normal
@@ -8088,6 +8297,7 @@ proc lineclick {x y id isnew} {
            $ctext insert end "\n\t[mc "Date"]:\t$date\n"
        }
     }
+    maybe_scroll_ctext 1
     $ctext conf -state disabled
     init_flist {}
 }
@@ -8101,10 +8311,10 @@ proc normalline {} {
     }
 }
 
-proc selbyid {id} {
+proc selbyid {id {isnew 1}} {
     global curview
     if {[commitinview $id $curview]} {
-       selectline [rowofcommit $id] 1
+       selectline [rowofcommit $id] $isnew
     }
 }
 
@@ -8289,23 +8499,23 @@ proc do_cmp_commits {a b} {
            }
        }
        if {$skipa} {
-           if {[llength $children($curview,$a)] != 1} {
+           set kids [real_children $curview,$a]
+           if {[llength $kids] != 1} {
                $ctext insert end "\n"
                appendshortlink $a [mc "Commit "] \
-                   [mc " has %s children - stopping\n" \
-                        [llength $children($curview,$a)]]
+                   [mc " has %s children - stopping\n" [llength $kids]]
                break
            }
-           set a [lindex $children($curview,$a) 0]
+           set a [lindex $kids 0]
        }
        if {$skipb} {
-           if {[llength $children($curview,$b)] != 1} {
+           set kids [real_children $curview,$b]
+           if {[llength $kids] != 1} {
                appendshortlink $b [mc "Commit "] \
-                   [mc " has %s children - stopping\n" \
-                        [llength $children($curview,$b)]]
+                   [mc " has %s children - stopping\n" [llength $kids]]
                break
            }
-           set b [lindex $children($curview,$b) 0]
+           set b [lindex $kids 0]
        }
     }
     $ctext conf -state disabled
@@ -8347,7 +8557,7 @@ proc diffvssel {dirn} {
        set oldid $rowmenuid
        set newid [commitonrow $selectedline]
     }
-    addtohistory [list doseldiff $oldid $newid]
+    addtohistory [list doseldiff $oldid $newid] savectextpos
     doseldiff $oldid $newid
 }
 
@@ -8375,7 +8585,7 @@ proc doseldiff {oldid newid} {
 }
 
 proc mkpatch {} {
-    global rowmenuid currentid commitinfo patchtop patchnum
+    global rowmenuid currentid commitinfo patchtop patchnum NS
 
     if {![info exists currentid]} return
     set oldid $currentid
@@ -8385,38 +8595,38 @@ proc mkpatch {} {
     set top .patch
     set patchtop $top
     catch {destroy $top}
-    toplevel $top
+    ttk_toplevel $top
     make_transient $top .
-    label $top.title -text [mc "Generate patch"]
+    ${NS}::label $top.title -text [mc "Generate patch"]
     grid $top.title - -pady 10
-    label $top.from -text [mc "From:"]
-    entry $top.fromsha1 -width 40 -relief flat
+    ${NS}::label $top.from -text [mc "From:"]
+    ${NS}::entry $top.fromsha1 -width 40
     $top.fromsha1 insert 0 $oldid
     $top.fromsha1 conf -state readonly
     grid $top.from $top.fromsha1 -sticky w
-    entry $top.fromhead -width 60 -relief flat
+    ${NS}::entry $top.fromhead -width 60
     $top.fromhead insert 0 $oldhead
     $top.fromhead conf -state readonly
     grid x $top.fromhead -sticky w
-    label $top.to -text [mc "To:"]
-    entry $top.tosha1 -width 40 -relief flat
+    ${NS}::label $top.to -text [mc "To:"]
+    ${NS}::entry $top.tosha1 -width 40
     $top.tosha1 insert 0 $newid
     $top.tosha1 conf -state readonly
     grid $top.to $top.tosha1 -sticky w
-    entry $top.tohead -width 60 -relief flat
+    ${NS}::entry $top.tohead -width 60
     $top.tohead insert 0 $newhead
     $top.tohead conf -state readonly
     grid x $top.tohead -sticky w
-    button $top.rev -text [mc "Reverse"] -command mkpatchrev -padx 5
-    grid $top.rev x -pady 10
-    label $top.flab -text [mc "Output file:"]
-    entry $top.fname -width 60
+    ${NS}::button $top.rev -text [mc "Reverse"] -command mkpatchrev
+    grid $top.rev x -pady 10 -padx 5
+    ${NS}::label $top.flab -text [mc "Output file:"]
+    ${NS}::entry $top.fname -width 60
     $top.fname insert 0 [file normalize "patch$patchnum.patch"]
     incr patchnum
     grid $top.flab $top.fname -sticky w
-    frame $top.buts
-    button $top.buts.gen -text [mc "Generate"] -command mkpatchgo
-    button $top.buts.can -text [mc "Cancel"] -command mkpatchcan
+    ${NS}::frame $top.buts
+    ${NS}::button $top.buts.gen -text [mc "Generate"] -command mkpatchgo
+    ${NS}::button $top.buts.can -text [mc "Cancel"] -command mkpatchcan
     bind $top <Key-Return> mkpatchgo
     bind $top <Key-Escape> mkpatchcan
     grid $top.buts.gen $top.buts.can
@@ -8467,30 +8677,30 @@ proc mkpatchcan {} {
 }
 
 proc mktag {} {
-    global rowmenuid mktagtop commitinfo
+    global rowmenuid mktagtop commitinfo NS
 
     set top .maketag
     set mktagtop $top
     catch {destroy $top}
-    toplevel $top
+    ttk_toplevel $top
     make_transient $top .
-    label $top.title -text [mc "Create tag"]
+    ${NS}::label $top.title -text [mc "Create tag"]
     grid $top.title - -pady 10
-    label $top.id -text [mc "ID:"]
-    entry $top.sha1 -width 40 -relief flat
+    ${NS}::label $top.id -text [mc "ID:"]
+    ${NS}::entry $top.sha1 -width 40
     $top.sha1 insert 0 $rowmenuid
     $top.sha1 conf -state readonly
     grid $top.id $top.sha1 -sticky w
-    entry $top.head -width 60 -relief flat
+    ${NS}::entry $top.head -width 60
     $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
     $top.head conf -state readonly
     grid x $top.head -sticky w
-    label $top.tlab -text [mc "Tag name:"]
-    entry $top.tag -width 60
+    ${NS}::label $top.tlab -text [mc "Tag name:"]
+    ${NS}::entry $top.tag -width 60
     grid $top.tlab $top.tag -sticky w
-    frame $top.buts
-    button $top.buts.gen -text [mc "Create"] -command mktaggo
-    button $top.buts.can -text [mc "Cancel"] -command mktagcan
+    ${NS}::frame $top.buts
+    ${NS}::button $top.buts.gen -text [mc "Create"] -command mktaggo
+    ${NS}::button $top.buts.can -text [mc "Cancel"] -command mktagcan
     bind $top <Key-Return> mktaggo
     bind $top <Key-Escape> mktagcan
     grid $top.buts.gen $top.buts.can
@@ -8573,34 +8783,34 @@ proc mktaggo {} {
 }
 
 proc writecommit {} {
-    global rowmenuid wrcomtop commitinfo wrcomcmd
+    global rowmenuid wrcomtop commitinfo wrcomcmd NS
 
     set top .writecommit
     set wrcomtop $top
     catch {destroy $top}
-    toplevel $top
+    ttk_toplevel $top
     make_transient $top .
-    label $top.title -text [mc "Write commit to file"]
+    ${NS}::label $top.title -text [mc "Write commit to file"]
     grid $top.title - -pady 10
-    label $top.id -text [mc "ID:"]
-    entry $top.sha1 -width 40 -relief flat
+    ${NS}::label $top.id -text [mc "ID:"]
+    ${NS}::entry $top.sha1 -width 40
     $top.sha1 insert 0 $rowmenuid
     $top.sha1 conf -state readonly
     grid $top.id $top.sha1 -sticky w
-    entry $top.head -width 60 -relief flat
+    ${NS}::entry $top.head -width 60
     $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
     $top.head conf -state readonly
     grid x $top.head -sticky w
-    label $top.clab -text [mc "Command:"]
-    entry $top.cmd -width 60 -textvariable wrcomcmd
+    ${NS}::label $top.clab -text [mc "Command:"]
+    ${NS}::entry $top.cmd -width 60 -textvariable wrcomcmd
     grid $top.clab $top.cmd -sticky w -pady 10
-    label $top.flab -text [mc "Output file:"]
-    entry $top.fname -width 60
+    ${NS}::label $top.flab -text [mc "Output file:"]
+    ${NS}::entry $top.fname -width 60
     $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
     grid $top.flab $top.fname -sticky w
-    frame $top.buts
-    button $top.buts.gen -text [mc "Write"] -command wrcomgo
-    button $top.buts.can -text [mc "Cancel"] -command wrcomcan
+    ${NS}::frame $top.buts
+    ${NS}::button $top.buts.gen -text [mc "Write"] -command wrcomgo
+    ${NS}::button $top.buts.can -text [mc "Cancel"] -command wrcomcan
     bind $top <Key-Return> wrcomgo
     bind $top <Key-Escape> wrcomcan
     grid $top.buts.gen $top.buts.can
@@ -8631,25 +8841,25 @@ proc wrcomcan {} {
 }
 
 proc mkbranch {} {
-    global rowmenuid mkbrtop
+    global rowmenuid mkbrtop NS
 
     set top .makebranch
     catch {destroy $top}
-    toplevel $top
+    ttk_toplevel $top
     make_transient $top .
-    label $top.title -text [mc "Create new branch"]
+    ${NS}::label $top.title -text [mc "Create new branch"]
     grid $top.title - -pady 10
-    label $top.id -text [mc "ID:"]
-    entry $top.sha1 -width 40 -relief flat
+    ${NS}::label $top.id -text [mc "ID:"]
+    ${NS}::entry $top.sha1 -width 40
     $top.sha1 insert 0 $rowmenuid
     $top.sha1 conf -state readonly
     grid $top.id $top.sha1 -sticky w
-    label $top.nlab -text [mc "Name:"]
-    entry $top.name -width 40
+    ${NS}::label $top.nlab -text [mc "Name:"]
+    ${NS}::entry $top.name -width 40
     grid $top.nlab $top.name -sticky w
-    frame $top.buts
-    button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
-    button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
+    ${NS}::frame $top.buts
+    ${NS}::button $top.buts.go -text [mc "Create"] -command [list mkbrgo $top]
+    ${NS}::button $top.buts.can -text [mc "Cancel"] -command "catch {destroy $top}"
     bind $top <Key-Return> [list mkbrgo $top]
     bind $top <Key-Escape> "catch {destroy $top}"
     grid $top.buts.go $top.buts.can
@@ -8794,34 +9004,31 @@ proc cherrypick {} {
 }
 
 proc resethead {} {
-    global mainhead rowmenuid confirm_ok resettype
+    global mainhead rowmenuid confirm_ok resettype NS
 
     set confirm_ok 0
     set w ".confirmreset"
-    toplevel $w
+    ttk_toplevel $w
     make_transient $w .
     wm title $w [mc "Confirm reset"]
-    message $w.m -text \
-       [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]] \
-       -justify center -aspect 1000
+    ${NS}::label $w.m -text \
+       [mc "Reset branch %s to %s?" $mainhead [string range $rowmenuid 0 7]]
     pack $w.m -side top -fill x -padx 20 -pady 20
-    frame $w.f -relief sunken -border 2
-    message $w.f.rt -text [mc "Reset type:"] -aspect 1000
-    grid $w.f.rt -sticky w
+    ${NS}::labelframe $w.f -text [mc "Reset type:"]
     set resettype mixed
-    radiobutton $w.f.soft -value soft -variable resettype -justify left \
+    ${NS}::radiobutton $w.f.soft -value soft -variable resettype \
        -text [mc "Soft: Leave working tree and index untouched"]
     grid $w.f.soft -sticky w
-    radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
+    ${NS}::radiobutton $w.f.mixed -value mixed -variable resettype \
        -text [mc "Mixed: Leave working tree untouched, reset index"]
     grid $w.f.mixed -sticky w
-    radiobutton $w.f.hard -value hard -variable resettype -justify left \
+    ${NS}::radiobutton $w.f.hard -value hard -variable resettype \
        -text [mc "Hard: Reset working tree and index\n(discard ALL local changes)"]
     grid $w.f.hard -sticky w
-    pack $w.f -side top -fill x
-    button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
+    pack $w.f -side top -fill x -padx 4
+    ${NS}::button $w.ok -text [mc OK] -command "set confirm_ok 1; destroy $w"
     pack $w.ok -side left -fill x -padx 20 -pady 20
-    button $w.cancel -text [mc Cancel] -command "destroy $w"
+    ${NS}::button $w.cancel -text [mc Cancel] -command "destroy $w"
     bind $w <Key-Escape> [list destroy $w]
     pack $w.cancel -side right -fill x -padx 20 -pady 20
     bind $w <Visibility> "grab $w; focus $w"
@@ -8877,6 +9084,9 @@ proc headmenu {x y id head} {
     set headmenuid $id
     set headmenuhead $head
     set state normal
+    if {[string match "remotes/*" $head]} {
+       set state disabled
+    }
     if {$head eq $mainhead} {
        set state disabled
     }
@@ -8969,7 +9179,7 @@ proc rmbranch {} {
 
 # Display a list of tags and heads
 proc showrefs {} {
-    global showrefstop bgcolor fgcolor selectbgcolor
+    global showrefstop bgcolor fgcolor selectbgcolor NS
     global bglist fglist reflistfilter reflist maincursor
 
     set top .showrefs
@@ -8979,7 +9189,7 @@ proc showrefs {} {
        refill_reflist
        return
     }
-    toplevel $top
+    ttk_toplevel $top
     wm title $top [mc "Tags and heads: %s" [file tail [pwd]]]
     make_transient $top .
     text $top.list -background $bgcolor -foreground $fgcolor \
@@ -8990,19 +9200,19 @@ proc showrefs {} {
     $top.list tag configure highlight -background $selectbgcolor
     lappend bglist $top.list
     lappend fglist $top.list
-    scrollbar $top.ysb -command "$top.list yview" -orient vertical
-    scrollbar $top.xsb -command "$top.list xview" -orient horizontal
+    ${NS}::scrollbar $top.ysb -command "$top.list yview" -orient vertical
+    ${NS}::scrollbar $top.xsb -command "$top.list xview" -orient horizontal
     grid $top.list $top.ysb -sticky nsew
     grid $top.xsb x -sticky ew
-    frame $top.f
-    label $top.f.l -text "[mc "Filter"]: "
-    entry $top.f.e -width 20 -textvariable reflistfilter
+    ${NS}::frame $top.f
+    ${NS}::label $top.f.l -text "[mc "Filter"]: "
+    ${NS}::entry $top.f.e -width 20 -textvariable reflistfilter
     set reflistfilter "*"
     trace add variable reflistfilter write reflistfilter_change
     pack $top.f.e -side right -fill x -expand 1
     pack $top.f.l -side left
     grid $top.f - -sticky ew -pady 2
-    button $top.close -command [list destroy $top] -text [mc "Close"]
+    ${NS}::button $top.close -command [list destroy $top] -text [mc "Close"]
     bind $top <Key-Escape> [list destroy $top]
     grid $top.close -
     grid columnconfigure $top 0 -weight 1
@@ -9200,7 +9410,7 @@ proc getallclines {fd} {
     global allparents allchildren idtags idheads nextarc
     global arcnos arcids arctags arcout arcend arcstart archeads growing
     global seeds allcommits cachedarcs allcupdate
-    
+
     set nid 0
     while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
        set id [lindex $line 0]
@@ -10262,7 +10472,7 @@ proc showtag {tag isnew} {
     global ctext tagcontents tagids linknum tagobjid
 
     if {$isnew} {
-       addtohistory [list showtag $tag 0]
+       addtohistory [list showtag $tag 0] savectextpos
     }
     $ctext conf -state normal
     clear_ctext
@@ -10279,6 +10489,7 @@ proc showtag {tag isnew} {
        set text "[mc "Tag"]: $tag\n[mc "Id"]:  $tagids($tag)"
     }
     appendwithlinks $text {}
+    maybe_scroll_ctext 1
     $ctext conf -state disabled
     init_flist {}
 }
@@ -10297,19 +10508,20 @@ proc doquit {} {
 }
 
 proc mkfontdisp {font top which} {
-    global fontattr fontpref $font
+    global fontattr fontpref $font NS use_ttk
 
     set fontpref($font) [set $font]
-    button $top.${font}but -text $which -font optionfont \
+    ${NS}::button $top.${font}but -text $which \
        -command [list choosefont $font $which]
-    label $top.$font -relief flat -font $font \
+    if {!$use_ttk} {$top.${font}but configure  -font optionfont}
+    ${NS}::label $top.$font -relief flat -font $font \
        -text $fontattr($font,family) -justify left
     grid x $top.${font}but $top.$font -sticky w
 }
 
 proc choosefont {font which} {
     global fontparam fontlist fonttop fontattr
-    global prefstop
+    global prefstop NS
 
     set fontparam(which) $which
     set fontparam(font) $font
@@ -10322,21 +10534,21 @@ proc choosefont {font which} {
     if {![winfo exists $top]} {
        font create sample
        eval font config sample [font actual $font]
-       toplevel $top
+       ttk_toplevel $top
        make_transient $top $prefstop
        wm title $top [mc "Gitk font chooser"]
-       label $top.l -textvariable fontparam(which)
+       ${NS}::label $top.l -textvariable fontparam(which)
        pack $top.l -side top
        set fontlist [lsort [font families]]
-       frame $top.f
+       ${NS}::frame $top.f
        listbox $top.f.fam -listvariable fontlist \
            -yscrollcommand [list $top.f.sb set]
        bind $top.f.fam <<ListboxSelect>> selfontfam
-       scrollbar $top.f.sb -command [list $top.f.fam yview]
+       ${NS}::scrollbar $top.f.sb -command [list $top.f.fam yview]
        pack $top.f.sb -side right -fill y
        pack $top.f.fam -side left -fill both -expand 1
        pack $top.f -side top -fill both -expand 1
-       frame $top.g
+       ${NS}::frame $top.g
        spinbox $top.g.size -from 4 -to 40 -width 4 \
            -textvariable fontparam(size) \
            -validatecommand {string is integer -strict %s}
@@ -10354,9 +10566,9 @@ proc choosefont {font which} {
            -fill black -tags text
        bind $top.c <Configure> [list centertext $top.c]
        pack $top.c -side top -fill x
-       frame $top.buts
-       button $top.buts.ok -text [mc "OK"] -command fontok -default active
-       button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal
+       ${NS}::frame $top.buts
+       ${NS}::button $top.buts.ok -text [mc "OK"] -command fontok -default active
+       ${NS}::button $top.buts.can -text [mc "Cancel"] -command fontcan -default normal
        bind $top <Key-Return> fontok
        bind $top <Key-Escape> fontcan
        grid $top.buts.ok $top.buts.can
@@ -10392,7 +10604,7 @@ proc fontok {} {
     }
     set w $prefstop.$f
     $w conf -text $fontparam(family) -font $fontpref($f)
-       
+
     fontcan
 }
 
@@ -10407,6 +10619,28 @@ proc fontcan {} {
     }
 }
 
+if {[package vsatisfies [package provide Tk] 8.6]} {
+    # In Tk 8.6 we have a native font chooser dialog. Overwrite the above
+    # function to make use of it.
+    proc choosefont {font which} {
+       tk fontchooser configure -title $which -font $font \
+           -command [list on_choosefont $font $which]
+       tk fontchooser show
+    }
+    proc on_choosefont {font which newfont} {
+       global fontparam
+       puts stderr "$font $newfont"
+       array set f [font actual $newfont]
+       set fontparam(which) $which
+       set fontparam(font) $font
+       set fontparam(family) $f(-family)
+       set fontparam(size) $f(-size)
+       set fontparam(weight) $f(-weight)
+       set fontparam(slant) $f(-slant)
+       fontok
+    }
+}
+
 proc selfontfam {} {
     global fonttop fontparam
 
@@ -10423,11 +10657,11 @@ proc chg_fontparam {v sub op} {
 }
 
 proc doprefs {} {
-    global maxwidth maxgraphpct
+    global maxwidth maxgraphpct use_ttk NS
     global oldprefs prefstop showneartags showlocalchanges
-    global bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor
+    global uicolor bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor
     global tabstop limitdiffs autoselect extdifftool perfile_attrs
-    global hideremotes
+    global hideremotes want_ttk have_ttk
 
     set top .gitkprefs
     set prefstop $top
@@ -10436,109 +10670,131 @@ proc doprefs {} {
        return
     }
     foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
-                  limitdiffs tabstop perfile_attrs hideremotes} {
+                  limitdiffs tabstop perfile_attrs hideremotes want_ttk} {
        set oldprefs($v) [set $v]
     }
-    toplevel $top
+    ttk_toplevel $top
     wm title $top [mc "Gitk preferences"]
     make_transient $top .
-    label $top.ldisp -text [mc "Commit list display options"]
+    ${NS}::label $top.ldisp -text [mc "Commit list display options"]
     grid $top.ldisp - -sticky w -pady 10
-    label $top.spacer -text " "
-    label $top.maxwidthl -text [mc "Maximum graph width (lines)"] \
-       -font optionfont
+    ${NS}::label $top.spacer -text " "
+    ${NS}::label $top.maxwidthl -text [mc "Maximum graph width (lines)"]
     spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
     grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
-    label $top.maxpctl -text [mc "Maximum graph width (% of pane)"] \
-       -font optionfont
+    ${NS}::label $top.maxpctl -text [mc "Maximum graph width (% of pane)"]
     spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
     grid x $top.maxpctl $top.maxpct -sticky w
-    checkbutton $top.showlocal -text [mc "Show local changes"] \
-       -font optionfont -variable showlocalchanges
+    ${NS}::checkbutton $top.showlocal -text [mc "Show local changes"] \
+       -variable showlocalchanges
     grid x $top.showlocal -sticky w
-    checkbutton $top.autoselect -text [mc "Auto-select SHA1"] \
-       -font optionfont -variable autoselect
+    ${NS}::checkbutton $top.autoselect -text [mc "Auto-select SHA1"] \
+       -variable autoselect
     grid x $top.autoselect -sticky w
+    ${NS}::checkbutton $top.hideremotes -text [mc "Hide remote refs"] \
+       -variable hideremotes
+    grid x $top.hideremotes -sticky w
 
-    label $top.ddisp -text [mc "Diff display options"]
+    ${NS}::label $top.ddisp -text [mc "Diff display options"]
     grid $top.ddisp - -sticky w -pady 10
-    label $top.tabstopl -text [mc "Tab spacing"] -font optionfont
+    ${NS}::label $top.tabstopl -text [mc "Tab spacing"]
     spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
     grid x $top.tabstopl $top.tabstop -sticky w
-    checkbutton $top.ntag -text [mc "Display nearby tags"] \
-       -font optionfont -variable showneartags
+    ${NS}::checkbutton $top.ntag -text [mc "Display nearby tags"] \
+       -variable showneartags
     grid x $top.ntag -sticky w
-    checkbutton $top.hideremotes -text [mc "Hide remote refs"] \
-       -font optionfont -variable hideremotes
-    grid x $top.hideremotes -sticky w
-    checkbutton $top.ldiff -text [mc "Limit diffs to listed paths"] \
-       -font optionfont -variable limitdiffs
+    ${NS}::checkbutton $top.ldiff -text [mc "Limit diffs to listed paths"] \
+       -variable limitdiffs
     grid x $top.ldiff -sticky w
-    checkbutton $top.lattr -text [mc "Support per-file encodings"] \
-       -font optionfont -variable perfile_attrs
+    ${NS}::checkbutton $top.lattr -text [mc "Support per-file encodings"] \
+       -variable perfile_attrs
     grid x $top.lattr -sticky w
 
-    entry $top.extdifft -textvariable extdifftool
-    frame $top.extdifff
-    label $top.extdifff.l -text [mc "External diff tool" ] -font optionfont \
-       -padx 10
-    button $top.extdifff.b -text [mc "Choose..."] -font optionfont \
-       -command choose_extdiff
+    ${NS}::entry $top.extdifft -textvariable extdifftool
+    ${NS}::frame $top.extdifff
+    ${NS}::label $top.extdifff.l -text [mc "External diff tool" ]
+    ${NS}::button $top.extdifff.b -text [mc "Choose..."] -command choose_extdiff
     pack $top.extdifff.l $top.extdifff.b -side left
-    grid x $top.extdifff $top.extdifft -sticky w
+    pack configure $top.extdifff.l -padx 10
+    grid x $top.extdifff $top.extdifft -sticky ew
+
+    ${NS}::label $top.lgen -text [mc "General options"]
+    grid $top.lgen - -sticky w -pady 10
+    ${NS}::checkbutton $top.want_ttk -variable want_ttk \
+       -text [mc "Use themed widgets"]
+    if {$have_ttk} {
+       ${NS}::label $top.ttk_note -text [mc "(change requires restart)"]
+    } else {
+       ${NS}::label $top.ttk_note -text [mc "(currently unavailable)"]
+    }
+    grid x $top.want_ttk $top.ttk_note -sticky w
 
-    label $top.cdisp -text [mc "Colors: press to choose"]
+    ${NS}::label $top.cdisp -text [mc "Colors: press to choose"]
     grid $top.cdisp - -sticky w -pady 10
+    label $top.ui -padx 40 -relief sunk -background $uicolor
+    ${NS}::button $top.uibut -text [mc "Interface"] \
+       -command [list choosecolor uicolor {} $top.ui [mc "interface"] setui]
+    grid x $top.uibut $top.ui -sticky w
     label $top.bg -padx 40 -relief sunk -background $bgcolor
-    button $top.bgbut -text [mc "Background"] -font optionfont \
+    ${NS}::button $top.bgbut -text [mc "Background"] \
        -command [list choosecolor bgcolor {} $top.bg [mc "background"] setbg]
     grid x $top.bgbut $top.bg -sticky w
     label $top.fg -padx 40 -relief sunk -background $fgcolor
-    button $top.fgbut -text [mc "Foreground"] -font optionfont \
+    ${NS}::button $top.fgbut -text [mc "Foreground"] \
        -command [list choosecolor fgcolor {} $top.fg [mc "foreground"] setfg]
     grid x $top.fgbut $top.fg -sticky w
     label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
-    button $top.diffoldbut -text [mc "Diff: old lines"] -font optionfont \
+    ${NS}::button $top.diffoldbut -text [mc "Diff: old lines"] \
        -command [list choosecolor diffcolors 0 $top.diffold [mc "diff old lines"] \
                      [list $ctext tag conf d0 -foreground]]
     grid x $top.diffoldbut $top.diffold -sticky w
     label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
-    button $top.diffnewbut -text [mc "Diff: new lines"] -font optionfont \
+    ${NS}::button $top.diffnewbut -text [mc "Diff: new lines"] \
        -command [list choosecolor diffcolors 1 $top.diffnew [mc "diff new lines"] \
                      [list $ctext tag conf dresult -foreground]]
     grid x $top.diffnewbut $top.diffnew -sticky w
     label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
-    button $top.hunksepbut -text [mc "Diff: hunk header"] -font optionfont \
+    ${NS}::button $top.hunksepbut -text [mc "Diff: hunk header"] \
        -command [list choosecolor diffcolors 2 $top.hunksep \
                      [mc "diff hunk header"] \
                      [list $ctext tag conf hunksep -foreground]]
     grid x $top.hunksepbut $top.hunksep -sticky w
     label $top.markbgsep -padx 40 -relief sunk -background $markbgcolor
-    button $top.markbgbut -text [mc "Marked line bg"] -font optionfont \
+    ${NS}::button $top.markbgbut -text [mc "Marked line bg"] \
        -command [list choosecolor markbgcolor {} $top.markbgsep \
                      [mc "marked line background"] \
                      [list $ctext tag conf omark -background]]
     grid x $top.markbgbut $top.markbgsep -sticky w
     label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
-    button $top.selbgbut -text [mc "Select bg"] -font optionfont \
+    ${NS}::button $top.selbgbut -text [mc "Select bg"] \
        -command [list choosecolor selectbgcolor {} $top.selbgsep [mc "background"] setselbg]
     grid x $top.selbgbut $top.selbgsep -sticky w
 
-    label $top.cfont -text [mc "Fonts: press to choose"]
+    ${NS}::label $top.cfont -text [mc "Fonts: press to choose"]
     grid $top.cfont - -sticky w -pady 10
     mkfontdisp mainfont $top [mc "Main font"]
     mkfontdisp textfont $top [mc "Diff display font"]
     mkfontdisp uifont $top [mc "User interface font"]
 
-    frame $top.buts
-    button $top.buts.ok -text [mc "OK"] -command prefsok -default active
-    button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
+    if {!$use_ttk} {
+       foreach w {maxpctl maxwidthl showlocal autoselect tabstopl ntag
+           ldiff lattr extdifff.l extdifff.b bgbut fgbut
+           diffoldbut diffnewbut hunksepbut markbgbut selbgbut
+           want_ttk ttk_note} {
+           $top.$w configure -font optionfont
+       }
+    }
+
+    ${NS}::frame $top.buts
+    ${NS}::button $top.buts.ok -text [mc "OK"] -command prefsok -default active
+    ${NS}::button $top.buts.can -text [mc "Cancel"] -command prefscan -default normal
     bind $top <Key-Return> prefsok
     bind $top <Key-Escape> prefscan
     grid $top.buts.ok $top.buts.can
     grid columnconfigure $top.buts 0 -weight 1 -uniform a
     grid columnconfigure $top.buts 1 -weight 1 -uniform a
     grid $top.buts - - -pady 10 -sticky ew
+    grid columnconfigure $top 2 -weight 1
     bind $top <Visibility> "focus $top.buts.ok"
 }
 
@@ -10572,6 +10828,20 @@ proc setselbg {c} {
     allcanvs itemconf secsel -fill $c
 }
 
+# This sets the background color and the color scheme for the whole UI.
+# For some reason, tk_setPalette chooses a nasty dark red for selectColor
+# if we don't specify one ourselves, which makes the checkbuttons and
+# radiobuttons look bad.  This chooses white for selectColor if the
+# background color is light, or black if it is dark.
+proc setui {c} {
+    set bg [winfo rgb . $c]
+    set selc black
+    if {[lindex $bg 0] + 1.5 * [lindex $bg 1] + 0.5 * [lindex $bg 2] > 100000} {
+       set selc white
+    }
+    tk_setPalette background $c selectColor $selc
+}
+
 proc setbg {c} {
     global bglist
 
@@ -10595,7 +10865,7 @@ proc prefscan {} {
     global oldprefs prefstop
 
     foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
-                  limitdiffs tabstop perfile_attrs hideremotes} {
+                  limitdiffs tabstop perfile_attrs hideremotes want_ttk} {
        global $v
        set $v $oldprefs($v)
     }
@@ -11006,8 +11276,8 @@ proc get_path_encoding {path} {
 
 # First check that Tcl/Tk is recent enough
 if {[catch {package require Tk 8.4} err]} {
-    show_error {} . [mc "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
-                    Gitk requires at least Tcl/Tk 8.4."]
+    show_error {} . "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
+                    Gitk requires at least Tcl/Tk 8.4." list
     exit 1
 }
 
@@ -11071,6 +11341,7 @@ set limitdiffs 1
 set datetimeformat "%Y-%m-%d %H:%M:%S"
 set autoselect 1
 set perfile_attrs 0
+set want_ttk 1
 
 if {[tk windowingsystem] eq "aqua"} {
     set extdifftool "opendiff"
@@ -11079,12 +11350,20 @@ if {[tk windowingsystem] eq "aqua"} {
 }
 
 set colors {green red blue magenta darkgrey brown orange}
-set bgcolor white
-set fgcolor black
+if {[tk windowingsystem] eq "win32"} {
+    set uicolor SystemButtonFace
+    set bgcolor SystemWindow
+    set fgcolor SystemButtonText
+    set selectbgcolor SystemHighlight
+} else {
+    set uicolor grey85
+    set bgcolor white
+    set fgcolor black
+    set selectbgcolor gray85
+}
 set diffcolors {red "#00a000" blue}
 set diffcontext 3
 set ignorespace 0
-set selectbgcolor gray85
 set markbgcolor "#e0e0ff"
 
 set circlecolors {white blue gray blue blue}
@@ -11130,6 +11409,8 @@ eval font create textfontbold [fontflags textfont 1]
 parsefont uifont $uifont
 eval font create uifont [fontflags uifont]
 
+setui $uicolor
+
 setoptions
 
 # check that we can find a .git directory somewhere...
@@ -11207,6 +11488,12 @@ set nullid2 "0000000000000000000000000000000000000001"
 set nullfile "/dev/null"
 
 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
+if {![info exists have_ttk]} {
+    set have_ttk [llength [info commands ::ttk::style]]
+}
+set use_ttk [expr {$have_ttk && $want_ttk}]
+set NS [expr {$use_ttk ? "ttk" : ""}]
+
 set git_version [join [lrange [split [lindex [exec git version] end] .] 0 2] .]
 
 set runq {}
diff --git a/gitk-git/po/ja.po b/gitk-git/po/ja.po
new file mode 100644 (file)
index 0000000..c0c92ad
--- /dev/null
@@ -0,0 +1,1255 @@
+# Japanese translations for gitk package.
+# Copyright (C) 2005-2009 Paul Mackerras
+# This file is distributed under the same license as the gitk package.
+#
+# Mizar <mizar.jp@gmail.com>, 2009.
+# Junio C Hamano <gitster@pobox.com>, 2009.
+msgid ""
+msgstr ""
+"Project-Id-Version: gitk\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-11-04 00:08+0900\n"
+"PO-Revision-Date: 2009-11-06 01:45+0900\n"
+"Last-Translator: Mizar <mizar.jp@gmail.com>\n"
+"Language-Team: Japanese\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: gitk:113
+msgid "Couldn't get list of unmerged files:"
+msgstr "マージされていないファイルのリストを取得できません:"
+
+#: gitk:269
+msgid "Error parsing revisions:"
+msgstr "リビジョン解析エラー:"
+
+#: gitk:324
+msgid "Error executing --argscmd command:"
+msgstr "--argscmd コマンド実行エラー:"
+
+#: gitk:337
+msgid "No files selected: --merge specified but no files are unmerged."
+msgstr ""
+"ファイル未選択: --merge が指定されましたが、マージされていないファイルはあり"
+"ません。"
+
+#: gitk:340
+msgid ""
+"No files selected: --merge specified but no unmerged files are within file "
+"limit."
+msgstr ""
+"ファイル未選択: --merge が指定されましたが、ファイル制限内にマージされていな"
+"いファイルはありません。"
+
+#: gitk:362 gitk:509
+msgid "Error executing git log:"
+msgstr "git log 実行エラー:"
+
+#: gitk:380 gitk:525
+msgid "Reading"
+msgstr "読み込み中"
+
+#: gitk:440 gitk:4132
+msgid "Reading commits..."
+msgstr "コミット読み込み中..."
+
+#: gitk:443 gitk:1561 gitk:4135
+msgid "No commits selected"
+msgstr "コミットが選択されていません"
+
+#: gitk:1437
+msgid "Can't parse git log output:"
+msgstr "git log の出力を解析できません:"
+
+#: gitk:1657
+msgid "No commit information available"
+msgstr "有効なコミットの情報がありません"
+
+#: gitk:1790
+msgid "mc"
+msgstr "mc"
+
+#: gitk:1817 gitk:3925 gitk:8842 gitk:10378 gitk:10558
+msgid "OK"
+msgstr "OK"
+
+#: gitk:1819 gitk:3927 gitk:8439 gitk:8513 gitk:8623 gitk:8672 gitk:8844
+#: gitk:10379 gitk:10559
+msgid "Cancel"
+msgstr "キャンセル"
+
+#: gitk:1919
+msgid "Update"
+msgstr "更新"
+
+#: gitk:1920
+msgid "Reload"
+msgstr "リロード"
+
+#: gitk:1921
+msgid "Reread references"
+msgstr "リファレンスを再読み込み"
+
+#: gitk:1922
+msgid "List references"
+msgstr "リファレンスリストを表示"
+
+#: gitk:1924
+msgid "Start git gui"
+msgstr "git gui の開始"
+
+#: gitk:1926
+msgid "Quit"
+msgstr "終了"
+
+#: gitk:1918
+msgid "File"
+msgstr "ファイル"
+
+#: gitk:1930
+msgid "Preferences"
+msgstr "設定"
+
+#: gitk:1929
+msgid "Edit"
+msgstr "編集"
+
+#: gitk:1934
+msgid "New view..."
+msgstr "新規ビュー..."
+
+#: gitk:1935
+msgid "Edit view..."
+msgstr "ビュー編集..."
+
+#: gitk:1936
+msgid "Delete view"
+msgstr "ビュー削除"
+
+#: gitk:1938
+msgid "All files"
+msgstr "全てのファイル"
+
+#: gitk:1933 gitk:3679
+msgid "View"
+msgstr "ビュー"
+
+#: gitk:1943 gitk:1953 gitk:2656
+msgid "About gitk"
+msgstr "gitk について"
+
+#: gitk:1944 gitk:1958
+msgid "Key bindings"
+msgstr "キーバインディング"
+
+#: gitk:1942 gitk:1957
+msgid "Help"
+msgstr "ヘルプ"
+
+#: gitk:2018
+msgid "SHA1 ID: "
+msgstr "SHA1 ID: "
+
+#: gitk:2049
+msgid "Row"
+msgstr "行"
+
+#: gitk:2080
+msgid "Find"
+msgstr "検索"
+
+#: gitk:2081
+msgid "next"
+msgstr "次"
+
+#: gitk:2082
+msgid "prev"
+msgstr "前"
+
+#: gitk:2083
+msgid "commit"
+msgstr "コミット"
+
+#: gitk:2086 gitk:2088 gitk:4293 gitk:4316 gitk:4340 gitk:6281 gitk:6353
+#: gitk:6437
+msgid "containing:"
+msgstr "含む:"
+
+#: gitk:2089 gitk:3164 gitk:3169 gitk:4368
+msgid "touching paths:"
+msgstr "パスの一部:"
+
+#: gitk:2090 gitk:4373
+msgid "adding/removing string:"
+msgstr "追加/除去する文字列:"
+
+#: gitk:2099 gitk:2101
+msgid "Exact"
+msgstr "英字の大小を区別する"
+
+#: gitk:2101 gitk:4448 gitk:6249
+msgid "IgnCase"
+msgstr "英字の大小を区別しない"
+
+#: gitk:2101 gitk:4342 gitk:4446 gitk:6245
+msgid "Regexp"
+msgstr "正規表現"
+
+#: gitk:2103 gitk:2104 gitk:4467 gitk:4497 gitk:4504 gitk:6373 gitk:6441
+msgid "All fields"
+msgstr "全ての項目"
+
+#: gitk:2104 gitk:4465 gitk:4497 gitk:6312
+msgid "Headline"
+msgstr "ヘッドライン"
+
+#: gitk:2105 gitk:4465 gitk:6312 gitk:6441 gitk:6875
+msgid "Comments"
+msgstr "コメント"
+
+#: gitk:2105 gitk:4465 gitk:4469 gitk:4504 gitk:6312 gitk:6810 gitk:8091
+#: gitk:8106
+msgid "Author"
+msgstr "作者"
+
+#: gitk:2105 gitk:4465 gitk:6312 gitk:6812
+msgid "Committer"
+msgstr "コミット者"
+
+#: gitk:2134
+msgid "Search"
+msgstr "検索"
+
+#: gitk:2141
+msgid "Diff"
+msgstr "Diff"
+
+#: gitk:2143
+msgid "Old version"
+msgstr "旧バージョン"
+
+#: gitk:2145
+msgid "New version"
+msgstr "新バージョン"
+
+#: gitk:2147
+msgid "Lines of context"
+msgstr "文脈行数"
+
+#: gitk:2157
+msgid "Ignore space change"
+msgstr "空白の違いを無視"
+
+#: gitk:2215
+msgid "Patch"
+msgstr "パッチ"
+
+#: gitk:2217
+msgid "Tree"
+msgstr "ツリー"
+
+#: gitk:2361 gitk:2378
+msgid "Diff this -> selected"
+msgstr "これと選択したコミットのdiffを見る"
+
+#: gitk:2362 gitk:2379
+msgid "Diff selected -> this"
+msgstr "選択したコミットとこれのdiffを見る"
+
+#: gitk:2363 gitk:2380
+msgid "Make patch"
+msgstr "パッチ作成"
+
+#: gitk:2364 gitk:8497
+msgid "Create tag"
+msgstr "タグ生成"
+
+#: gitk:2365 gitk:8603
+msgid "Write commit to file"
+msgstr "コミットをファイルに書き出す"
+
+#: gitk:2366 gitk:8660
+msgid "Create new branch"
+msgstr "新規ブランチ生成"
+
+#: gitk:2367
+msgid "Cherry-pick this commit"
+msgstr "このコミットをチェリーピックする"
+
+#: gitk:2368
+msgid "Reset HEAD branch to here"
+msgstr "ブランチのHEADをここにリセットする"
+
+#: gitk:2369
+msgid "Mark this commit"
+msgstr "このコミットにマークをつける"
+
+#: gitk:2370
+msgid "Return to mark"
+msgstr "マークを付けた所に戻る"
+
+#: gitk:2371
+msgid "Find descendant of this and mark"
+msgstr "これとマークをつけた所との子孫を見つける"
+
+#: gitk:2372
+msgid "Compare with marked commit"
+msgstr "マークを付けたコミットと比較する"
+
+#: gitk:2386
+msgid "Check out this branch"
+msgstr "このブランチをチェックアウトする"
+
+#: gitk:2387
+msgid "Remove this branch"
+msgstr "このブランチを除去する"
+
+#: gitk:2394
+msgid "Highlight this too"
+msgstr "これもハイライトさせる"
+
+#: gitk:2395
+msgid "Highlight this only"
+msgstr "これだけをハイライトさせる"
+
+#: gitk:2396
+msgid "External diff"
+msgstr "外部diffツール"
+
+#: gitk:2397
+msgid "Blame parent commit"
+msgstr "親コミットから blame をかける"
+
+#: gitk:2404
+msgid "Show origin of this line"
+msgstr "この行の出自を表示する"
+
+#: gitk:2405
+msgid "Run git gui blame on this line"
+msgstr "この行に git gui で blame をかける"
+
+#: gitk:2658
+msgid ""
+"\n"
+"Gitk - a commit viewer for git\n"
+"\n"
+"Copyright © 2005-2008 Paul Mackerras\n"
+"\n"
+"Use and redistribute under the terms of the GNU General Public License"
+msgstr ""
+"\n"
+"Gitk - gitコミットビューア\n"
+"\n"
+"Copyright © 2005-2008 Paul Mackerras\n"
+"\n"
+"使用および再配布は GNU General Public License に従ってください"
+
+#: gitk:2666 gitk:2728 gitk:9025
+msgid "Close"
+msgstr "閉じる"
+
+#: gitk:2685
+msgid "Gitk key bindings"
+msgstr "Gitk キーバインディング"
+
+#: gitk:2688
+msgid "Gitk key bindings:"
+msgstr "Gitk キーバインディング:"
+
+#: gitk:2690
+#, tcl-format
+msgid "<%s-Q>\t\tQuit"
+msgstr "<%s-Q>\t\t終了"
+
+#: gitk:2691
+msgid "<Home>\t\tMove to first commit"
+msgstr "<Home>\t\t最初のコミットに移動"
+
+#: gitk:2692
+msgid "<End>\t\tMove to last commit"
+msgstr "<End>\t\t最後のコミットに移動"
+
+#: gitk:2693
+msgid "<Up>, p, i\tMove up one commit"
+msgstr "<Up>, p, i\t一つ上のコミットに移動"
+
+#: gitk:2694
+msgid "<Down>, n, k\tMove down one commit"
+msgstr "<Down>, n, k\t一つ下のコミットに移動"
+
+#: gitk:2695
+msgid "<Left>, z, j\tGo back in history list"
+msgstr "<Left>, z, j\t履歴の前に戻る"
+
+#: gitk:2696
+msgid "<Right>, x, l\tGo forward in history list"
+msgstr "<Right>, x, l\t履歴の次へ進む"
+
+#: gitk:2697
+msgid "<PageUp>\tMove up one page in commit list"
+msgstr "<PageUp>\tコミットリストの一つ上のページに移動"
+
+#: gitk:2698
+msgid "<PageDown>\tMove down one page in commit list"
+msgstr "<PageDown>\tコミットリストの一つ下のページに移動"
+
+#: gitk:2699
+#, tcl-format
+msgid "<%s-Home>\tScroll to top of commit list"
+msgstr "<%s-Home>\tコミットリストの一番上にスクロールする"
+
+#: gitk:2700
+#, tcl-format
+msgid "<%s-End>\tScroll to bottom of commit list"
+msgstr "<%s-End>\tコミットリストの一番下にスクロールする"
+
+#: gitk:2701
+#, tcl-format
+msgid "<%s-Up>\tScroll commit list up one line"
+msgstr "<%s-Up>\tコミットリストの一つ下の行にスクロールする"
+
+#: gitk:2702
+#, tcl-format
+msgid "<%s-Down>\tScroll commit list down one line"
+msgstr "<%s-Down>\tコミットリストの一つ下の行にスクロールする"
+
+#: gitk:2703
+#, tcl-format
+msgid "<%s-PageUp>\tScroll commit list up one page"
+msgstr "<%s-PageUp>\tコミットリストの上のページにスクロールする"
+
+#: gitk:2704
+#, tcl-format
+msgid "<%s-PageDown>\tScroll commit list down one page"
+msgstr "<%s-PageDown>\tコミットリストの下のページにスクロールする"
+
+#: gitk:2705
+msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
+msgstr "<Shift-Up>\t後方を検索 (上方の・新しいコミット)"
+
+#: gitk:2706
+msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
+msgstr "<Shift-Down>\t前方を検索(下方の・古いコミット)"
+
+#: gitk:2707
+msgid "<Delete>, b\tScroll diff view up one page"
+msgstr "<Delete>, b\tdiff画面を上のページにスクロールする"
+
+#: gitk:2708
+msgid "<Backspace>\tScroll diff view up one page"
+msgstr "<Backspace>\tdiff画面を上のページにスクロールする"
+
+#: gitk:2709
+msgid "<Space>\t\tScroll diff view down one page"
+msgstr "<Space>\t\tdiff画面を下のページにスクロールする"
+
+#: gitk:2710
+msgid "u\t\tScroll diff view up 18 lines"
+msgstr "u\t\tdiff画面を上に18行スクロールする"
+
+#: gitk:2711
+msgid "d\t\tScroll diff view down 18 lines"
+msgstr "d\t\tdiff画面を下に18行スクロールする"
+
+#: gitk:2712
+#, tcl-format
+msgid "<%s-F>\t\tFind"
+msgstr "<%s-F>\t\t検索"
+
+#: gitk:2713
+#, tcl-format
+msgid "<%s-G>\t\tMove to next find hit"
+msgstr "<%s-G>\t\t次を検索して移動"
+
+#: gitk:2714
+msgid "<Return>\tMove to next find hit"
+msgstr "<Return>\t次を検索して移動"
+
+#: gitk:2715
+msgid "/\t\tFocus the search box"
+msgstr "/\t\t検索ボックスにフォーカス"
+
+#: gitk:2716
+msgid "?\t\tMove to previous find hit"
+msgstr "?\t\t前を検索して移動"
+
+#: gitk:2717
+msgid "f\t\tScroll diff view to next file"
+msgstr "f\t\t次のファイルにdiff画面をスクロールする"
+
+#: gitk:2718
+#, tcl-format
+msgid "<%s-S>\t\tSearch for next hit in diff view"
+msgstr "<%s-S>\t\tdiff画面の次を検索"
+
+#: gitk:2719
+#, tcl-format
+msgid "<%s-R>\t\tSearch for previous hit in diff view"
+msgstr "<%s-R>\t\tdiff画面の前を検索"
+
+#: gitk:2720
+#, tcl-format
+msgid "<%s-KP+>\tIncrease font size"
+msgstr "<%s-KP+>\t文字サイズを拡大"
+
+#: gitk:2721
+#, tcl-format
+msgid "<%s-plus>\tIncrease font size"
+msgstr "<%s-plus>\t文字サイズを拡大"
+
+#: gitk:2722
+#, tcl-format
+msgid "<%s-KP->\tDecrease font size"
+msgstr "<%s-KP->\t文字サイズを縮小"
+
+#: gitk:2723
+#, tcl-format
+msgid "<%s-minus>\tDecrease font size"
+msgstr "<%s-minus>\t文字サイズを縮小"
+
+#: gitk:2724
+msgid "<F5>\t\tUpdate"
+msgstr "<F5>\t\t更新"
+
+#: gitk:3179 gitk:3188
+#, tcl-format
+msgid "Error creating temporary directory %s:"
+msgstr "一時ディレクトリ %s 生成時エラー:"
+
+#: gitk:3201
+#, tcl-format
+msgid "Error getting \"%s\" from %s:"
+msgstr "\"%s\" のエラーが %s に発生:"
+
+#: gitk:3264
+msgid "command failed:"
+msgstr "コマンド失敗:"
+
+#: gitk:3410
+msgid "No such commit"
+msgstr "そのようなコミットはありません"
+
+#: gitk:3424
+msgid "git gui blame: command failed:"
+msgstr "git gui blame: コマンド失敗:"
+
+#: gitk:3455
+#, tcl-format
+msgid "Couldn't read merge head: %s"
+msgstr "マージする HEAD を読み込めません: %s"
+
+#: gitk:3463
+#, tcl-format
+msgid "Error reading index: %s"
+msgstr "インデックス読み込みエラー: %s"
+
+#: gitk:3488
+#, tcl-format
+msgid "Couldn't start git blame: %s"
+msgstr "git blame を始められません: %s"
+
+#: gitk:3491 gitk:6280
+msgid "Searching"
+msgstr "検索中"
+
+#: gitk:3523
+#, tcl-format
+msgid "Error running git blame: %s"
+msgstr "git blame 実行エラー: %s"
+
+#: gitk:3551
+#, tcl-format
+msgid "That line comes from commit %s,  which is not in this view"
+msgstr "コミット %s に由来するその行は、このビューに表示されていません"
+
+#: gitk:3565
+msgid "External diff viewer failed:"
+msgstr "外部diffビューアが失敗:"
+
+#: gitk:3683
+msgid "Gitk view definition"
+msgstr "Gitk ビュー定義"
+
+#: gitk:3687
+msgid "Remember this view"
+msgstr "このビューを記憶する"
+
+#: gitk:3688
+msgid "References (space separated list):"
+msgstr "リファレンス(スペース区切りのリスト):"
+
+#: gitk:3689
+msgid "Branches & tags:"
+msgstr "ブランチ&タグ:"
+
+#: gitk:3690
+msgid "All refs"
+msgstr "全てのリファレンス"
+
+#: gitk:3691
+msgid "All (local) branches"
+msgstr "全ての(ローカルな)ブランチ"
+
+#: gitk:3692
+msgid "All tags"
+msgstr "全てのタグ"
+
+#: gitk:3693
+msgid "All remote-tracking branches"
+msgstr "全てのリモート追跡ブランチ"
+
+#: gitk:3694
+msgid "Commit Info (regular expressions):"
+msgstr "コミット情報(正規表現):"
+
+#: gitk:3695
+msgid "Author:"
+msgstr "作者:"
+
+#: gitk:3696
+msgid "Committer:"
+msgstr "コミット者:"
+
+#: gitk:3697
+msgid "Commit Message:"
+msgstr "コミットメッセージ:"
+
+#: gitk:3698
+msgid "Matches all Commit Info criteria"
+msgstr "コミット情報の全ての条件に一致"
+
+#: gitk:3699
+msgid "Changes to Files:"
+msgstr "変更したファイル:"
+
+#: gitk:3700
+msgid "Fixed String"
+msgstr "固定文字列"
+
+#: gitk:3701
+msgid "Regular Expression"
+msgstr "正規表現"
+
+#: gitk:3702
+msgid "Search string:"
+msgstr "検索文字列:"
+
+#: gitk:3703
+msgid ""
+"Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
+"15:27:38\"):"
+msgstr ""
+"コミット日時 (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 "
+"15:27:38\"):"
+
+#: gitk:3704
+msgid "Since:"
+msgstr "期間の始め:"
+
+#: gitk:3705
+msgid "Until:"
+msgstr "期間の終わり:"
+
+#: gitk:3706
+msgid "Limit and/or skip a number of revisions (positive integer):"
+msgstr "制限・省略するリビジョンの数(正の整数):"
+
+#: gitk:3707
+msgid "Number to show:"
+msgstr "表示する数:"
+
+#: gitk:3708
+msgid "Number to skip:"
+msgstr "省略する数:"
+
+#: gitk:3709
+msgid "Miscellaneous options:"
+msgstr "その他のオプション:"
+
+#: gitk:3710
+msgid "Strictly sort by date"
+msgstr "厳密に日付順で並び替え"
+
+#: gitk:3711
+msgid "Mark branch sides"
+msgstr "側枝マーク"
+
+#: gitk:3712
+msgid "Limit to first parent"
+msgstr "最初の親に制限"
+
+#: gitk:3713
+msgid "Simple history"
+msgstr "簡易な履歴"
+
+#: gitk:3714
+msgid "Additional arguments to git log:"
+msgstr "git log への追加の引数:"
+
+#: gitk:3715
+msgid "Enter files and directories to include, one per line:"
+msgstr "含まれるファイル・ディレクトリを一行ごとに入力:"
+
+#: gitk:3716
+msgid "Command to generate more commits to include:"
+msgstr "コミット追加コマンド:"
+
+#: gitk:3838
+msgid "Gitk: edit view"
+msgstr "Gitk: ビュー編集"
+
+#: gitk:3846
+msgid "-- criteria for selecting revisions"
+msgstr "― リビジョンの選択条件"
+
+#: gitk:3851
+msgid "View Name:"
+msgstr "ビュー名:"
+
+#: gitk:3926
+msgid "Apply (F5)"
+msgstr "適用 (F5)"
+
+#: gitk:3964
+msgid "Error in commit selection arguments:"
+msgstr "コミット選択引数のエラー:"
+
+#: gitk:4017 gitk:4069 gitk:4517 gitk:4531 gitk:5792 gitk:11263 gitk:11264
+msgid "None"
+msgstr "無し"
+
+#: gitk:4465 gitk:6312 gitk:8093 gitk:8108
+msgid "Date"
+msgstr "日付"
+
+#: gitk:4465 gitk:6312
+msgid "CDate"
+msgstr "作成日"
+
+#: gitk:4614 gitk:4619
+msgid "Descendant"
+msgstr "子孫"
+
+#: gitk:4615
+msgid "Not descendant"
+msgstr "非子孫"
+
+#: gitk:4622 gitk:4627
+msgid "Ancestor"
+msgstr "祖先"
+
+#: gitk:4623
+msgid "Not ancestor"
+msgstr "非祖先"
+
+#: gitk:4913
+msgid "Local changes checked in to index but not committed"
+msgstr "ステージされた、コミット前のローカルな変更"
+
+#: gitk:4949
+msgid "Local uncommitted changes, not checked in to index"
+msgstr "ステージされていない、コミット前のローカルな変更"
+
+#: gitk:6630
+msgid "many"
+msgstr "多数"
+
+#: gitk:6814
+msgid "Tags:"
+msgstr "タグ:"
+
+#: gitk:6831 gitk:6837 gitk:8086
+msgid "Parent"
+msgstr "親"
+
+#: gitk:6842
+msgid "Child"
+msgstr "子"
+
+#: gitk:6851
+msgid "Branch"
+msgstr "ブランチ"
+
+#: gitk:6854
+msgid "Follows"
+msgstr "下位"
+
+#: gitk:6857
+msgid "Precedes"
+msgstr "上位"
+
+#: gitk:7359
+#, tcl-format
+msgid "Error getting diffs: %s"
+msgstr "diff取得エラー: %s"
+
+#: gitk:7914
+msgid "Goto:"
+msgstr "Goto:"
+
+#: gitk:7916
+msgid "SHA1 ID:"
+msgstr "SHA1 ID:"
+
+#: gitk:7935
+#, tcl-format
+msgid "Short SHA1 id %s is ambiguous"
+msgstr "%s を含む SHA1 ID は複数存在します"
+
+#: gitk:7942
+#, tcl-format
+msgid "Revision %s is not known"
+msgstr "リビジョン %s は不明です"
+
+#: gitk:7952
+#, tcl-format
+msgid "SHA1 id %s is not known"
+msgstr "SHA1 id %s は不明です"
+
+#: gitk:7954
+#, tcl-format
+msgid "Revision %s is not in the current view"
+msgstr "リビジョン %s は現在のビューにはありません"
+
+#: gitk:8096
+msgid "Children"
+msgstr "子"
+
+#: gitk:8153
+#, tcl-format
+msgid "Reset %s branch to here"
+msgstr "%s ブランチをここにリセットする"
+
+#: gitk:8155
+msgid "Detached head: can't reset"
+msgstr "切り離されたHEAD: リセットできません"
+
+#: gitk:8264 gitk:8270
+msgid "Skipping merge commit "
+msgstr "コミットマージをスキップ: "
+
+#: gitk:8279 gitk:8284
+msgid "Error getting patch ID for "
+msgstr "パッチ取得エラー: ID "
+
+#: gitk:8280 gitk:8285
+msgid " - stopping\n"
+msgstr " - 停止\n"
+
+#: gitk:8290 gitk:8293 gitk:8301 gitk:8314 gitk:8323
+msgid "Commit "
+msgstr "コミット "
+
+#: gitk:8294
+msgid ""
+" is the same patch as\n"
+"       "
+msgstr ""
+" は下記のパッチと同等\n"
+"       "
+
+#: gitk:8302
+msgid ""
+" differs from\n"
+"       "
+msgstr ""
+" 下記からのdiff\n"
+"       "
+
+#: gitk:8304
+msgid ""
+"Diff of commits:\n"
+"\n"
+msgstr ""
+"コミットのdiff:\n"
+"\n"
+
+#: gitk:8315 gitk:8324
+#, tcl-format
+msgid " has %s children - stopping\n"
+msgstr " には %s の子があります - 停止\n"
+
+#: gitk:8344
+#, tcl-format
+msgid "Error writing commit to file: %s"
+msgstr "ファイルへのコミット書き出しエラー: %s"
+
+#: gitk:8350
+#, tcl-format
+msgid "Error diffing commits: %s"
+msgstr "コミットのdiff実行エラー: %s"
+
+#: gitk:8380
+msgid "Top"
+msgstr "Top"
+
+#: gitk:8381
+msgid "From"
+msgstr "From"
+
+#: gitk:8386
+msgid "To"
+msgstr "To"
+
+#: gitk:8410
+msgid "Generate patch"
+msgstr "パッチ生成"
+
+#: gitk:8412
+msgid "From:"
+msgstr "From:"
+
+#: gitk:8421
+msgid "To:"
+msgstr "To:"
+
+#: gitk:8430
+msgid "Reverse"
+msgstr "逆"
+
+#: gitk:8432 gitk:8617
+msgid "Output file:"
+msgstr "出力ファイル:"
+
+#: gitk:8438
+msgid "Generate"
+msgstr "生成"
+
+#: gitk:8476
+msgid "Error creating patch:"
+msgstr "パッチ生成エラー:"
+
+#: gitk:8499 gitk:8605 gitk:8662
+msgid "ID:"
+msgstr "ID:"
+
+#: gitk:8508
+msgid "Tag name:"
+msgstr "タグ名:"
+
+#: gitk:8512 gitk:8671
+msgid "Create"
+msgstr "生成"
+
+#: gitk:8529
+msgid "No tag name specified"
+msgstr "タグの名称が指定されていません"
+
+#: gitk:8533
+#, tcl-format
+msgid "Tag \"%s\" already exists"
+msgstr "タグ \"%s\" は既に存在します"
+
+#: gitk:8539
+msgid "Error creating tag:"
+msgstr "タグ生成エラー:"
+
+#: gitk:8614
+msgid "Command:"
+msgstr "コマンド:"
+
+#: gitk:8622
+msgid "Write"
+msgstr "書き出し"
+
+#: gitk:8640
+msgid "Error writing commit:"
+msgstr "コミット書き出しエラー:"
+
+#: gitk:8667
+msgid "Name:"
+msgstr "名前:"
+
+#: gitk:8690
+msgid "Please specify a name for the new branch"
+msgstr "新しいブランチの名前を指定してください"
+
+#: gitk:8695
+#, tcl-format
+msgid "Branch '%s' already exists. Overwrite?"
+msgstr "ブランチ '%s' は既に存在します。上書きしますか?"
+
+#: gitk:8761
+#, tcl-format
+msgid "Commit %s is already included in branch %s -- really re-apply it?"
+msgstr ""
+"コミット %s は既にブランチ %s に含まれています ― 本当にこれを再適用しますか?"
+
+#: gitk:8766
+msgid "Cherry-picking"
+msgstr "チェリーピック中"
+
+#: gitk:8775
+#, tcl-format
+msgid ""
+"Cherry-pick failed because of local changes to file '%s'.\n"
+"Please commit, reset or stash your changes and try again."
+msgstr ""
+"ファイル '%s' のローカルな変更のためにチェリーピックは失敗しました。\n"
+"あなたの変更に commit, reset, stash のいずれかを行ってからやり直してくださ"
+"い。"
+
+#: gitk:8781
+msgid ""
+"Cherry-pick failed because of merge conflict.\n"
+"Do you wish to run git citool to resolve it?"
+msgstr ""
+"マージの衝突によってチェリーピックは失敗しました。\n"
+"この解決のために git citool を実行したいですか?"
+
+#: gitk:8797
+msgid "No changes committed"
+msgstr "何の変更もコミットされていません"
+
+#: gitk:8823
+msgid "Confirm reset"
+msgstr "確認を取り消す"
+
+#: gitk:8825
+#, tcl-format
+msgid "Reset branch %s to %s?"
+msgstr "ブランチ %s を %s にリセットしますか?"
+
+#: gitk:8829
+msgid "Reset type:"
+msgstr "Reset タイプ:"
+
+#: gitk:8833
+msgid "Soft: Leave working tree and index untouched"
+msgstr "Soft: 作業ツリーもインデックスもそのままにする"
+
+#: gitk:8836
+msgid "Mixed: Leave working tree untouched, reset index"
+msgstr "Mixed: 作業ツリーをそのままにして、インデックスをリセット"
+
+#: gitk:8839
+msgid ""
+"Hard: Reset working tree and index\n"
+"(discard ALL local changes)"
+msgstr ""
+"Hard: 作業ツリーやインデックスをリセット\n"
+"(「全ての」ローカルな変更を破棄)"
+
+#: gitk:8856
+msgid "Resetting"
+msgstr "リセット中"
+
+#: gitk:8913
+msgid "Checking out"
+msgstr "チェックアウト"
+
+#: gitk:8966
+msgid "Cannot delete the currently checked-out branch"
+msgstr "現在チェックアウトされているブランチを削除することはできません"
+
+#: gitk:8972
+#, tcl-format
+msgid ""
+"The commits on branch %s aren't on any other branch.\n"
+"Really delete branch %s?"
+msgstr ""
+"ブランチ %s には他のブランチに存在しないコミットがあります。\n"
+"本当にブランチ %s を削除しますか?"
+
+#: gitk:9003
+#, tcl-format
+msgid "Tags and heads: %s"
+msgstr "タグとHEAD: %s"
+
+#: gitk:9018
+msgid "Filter"
+msgstr "フィルター"
+
+#: gitk:9313
+msgid ""
+"Error reading commit topology information; branch and preceding/following "
+"tag information will be incomplete."
+msgstr ""
+"コミット構造情報読み込みエラー; ブランチ及び上位/下位のタグ情報が不完全である"
+"ようです。"
+
+#: gitk:10299
+msgid "Tag"
+msgstr "タグ"
+
+#: gitk:10299
+msgid "Id"
+msgstr "ID"
+
+#: gitk:10347
+msgid "Gitk font chooser"
+msgstr "Gitk フォント選択"
+
+#: gitk:10364
+msgid "B"
+msgstr "B"
+
+#: gitk:10367
+msgid "I"
+msgstr "I"
+
+#: gitk:10463
+msgid "Gitk preferences"
+msgstr "Gitk 設定"
+
+#: gitk:10465
+msgid "Commit list display options"
+msgstr "コミットリスト表示オプション"
+
+#: gitk:10468
+msgid "Maximum graph width (lines)"
+msgstr "最大グラフ幅(線の本数)"
+
+#: gitk:10472
+#, tcl-format
+msgid "Maximum graph width (% of pane)"
+msgstr "最大グラフ幅(ペインに対する%)"
+
+#: gitk:10476
+msgid "Show local changes"
+msgstr "ローカルな変更を表示"
+
+#: gitk:10479
+msgid "Auto-select SHA1"
+msgstr "SHA1 の自動選択"
+
+#: gitk:10483
+msgid "Diff display options"
+msgstr "diff表示オプション"
+
+#: gitk:10485
+msgid "Tab spacing"
+msgstr "タブ空白幅"
+
+#: gitk:10488
+msgid "Display nearby tags"
+msgstr "近くのタグを表示する"
+
+#: gitk:10491
+msgid "Hide remote refs"
+msgstr "リモートリファレンスを隠す"
+
+#: gitk:10494
+msgid "Limit diffs to listed paths"
+msgstr "diff をリストのパスに制限"
+
+#: gitk:10497
+msgid "Support per-file encodings"
+msgstr "ファイルごとのエンコーディングのサポート"
+
+#: gitk:10503 gitk:10572
+msgid "External diff tool"
+msgstr "外部diffツール"
+
+#: gitk:10505
+msgid "Choose..."
+msgstr "選択..."
+
+#: gitk:10510
+msgid "Colors: press to choose"
+msgstr "色: ボタンを押して選択"
+
+#: gitk:10513
+msgid "Interface"
+msgstr "インターフェイス"
+
+#: gitk:10514
+msgid "interface"
+msgstr "インターフェイス"
+
+#: gitk:10517
+msgid "Background"
+msgstr "背景"
+
+#: gitk:10518 gitk:10548
+msgid "background"
+msgstr "背景"
+
+#: gitk:10521
+msgid "Foreground"
+msgstr "前景"
+
+#: gitk:10522
+msgid "foreground"
+msgstr "前景"
+
+#: gitk:10525
+msgid "Diff: old lines"
+msgstr "Diff: 旧バージョン"
+
+#: gitk:10526
+msgid "diff old lines"
+msgstr "diff 旧バージョン"
+
+#: gitk:10530
+msgid "Diff: new lines"
+msgstr "Diff: 新バージョン"
+
+#: gitk:10531
+msgid "diff new lines"
+msgstr "diff 新バージョン"
+
+#: gitk:10535
+msgid "Diff: hunk header"
+msgstr "Diff: hunkヘッダ"
+
+#: gitk:10537
+msgid "diff hunk header"
+msgstr "diff hunkヘッダ"
+
+#: gitk:10541
+msgid "Marked line bg"
+msgstr "マーク行の背景"
+
+#: gitk:10543
+msgid "marked line background"
+msgstr "マーク行の背景"
+
+#: gitk:10547
+msgid "Select bg"
+msgstr "選択の背景"
+
+#: gitk:10551
+msgid "Fonts: press to choose"
+msgstr "フォント: ボタンを押して選択"
+
+#: gitk:10553
+msgid "Main font"
+msgstr "主フォント"
+
+#: gitk:10554
+msgid "Diff display font"
+msgstr "Diff表示用フォント"
+
+#: gitk:10555
+msgid "User interface font"
+msgstr "UI用フォント"
+
+#: gitk:10582
+#, tcl-format
+msgid "Gitk: choose color for %s"
+msgstr "Gitk: 「%s」 の色を選択"
+
+#: gitk:11168
+msgid "Cannot find a git repository here."
+msgstr "ここにはgitリポジトリがありません。"
+
+#: gitk:11172
+#, tcl-format
+msgid "Cannot find the git directory \"%s\"."
+msgstr "gitディレクトリ \"%s\" を見つけられません。"
+
+#: gitk:11219
+#, tcl-format
+msgid "Ambiguous argument '%s': both revision and filename"
+msgstr "あいまいな引数 '%s': リビジョンとファイル名の両方に解釈できます"
+
+#: gitk:11231
+msgid "Bad arguments to gitk:"
+msgstr "gitkへの不正な引数:"
+
+#: gitk:11316
+msgid "Command line"
+msgstr "コマンド行"
index 66c6a9391d3494ae8928e88501d8fa0d36dffa42..e34ee793ef51584344f8c808454b9ead0c3348c6 100644 (file)
@@ -92,6 +92,11 @@ You can specify the following configuration variables when building GIT:
    web browsers that support favicons (website icons) may display them
    in the browser's URL bar and next to site name in bookmarks).  Relative
    to base URI of gitweb.  [Default: git-favicon.png]
+ * GITWEB_JS
+   Points to the localtion where you put gitweb.js on your web server
+   (or to be more generic URI of JavaScript code used by gitweb).
+   Relative to base URI of gitweb.  [Default: gitweb.js (or gitweb.min.js
+   if JSMIN build variable is defined / JavaScript minifier is used)]
  * GITWEB_CONFIG
    This Perl file will be loaded using 'do' and can be used to override any
    of the options above as well as some other options -- see the "Runtime
index cb3f0baf05e8a48484132bb8936b4f46f22493ed..50067f2e0dea2c1e17a41e9a5e8b2a8f6240c13d 100644 (file)
@@ -79,6 +79,13 @@ div.page_footer_text {
        font-style: italic;
 }
 
+div#generating_info {
+       margin: 4px;
+       font-size: smaller;
+       text-align: center;
+       color: #505050;
+}
+
 div.page_body {
        padding: 8px;
        font-family: monospace;
@@ -254,6 +261,11 @@ tr.no-previous td.linenr {
        font-weight: bold;
 }
 
+/* for 'blame_incremental', during processing */
+tr.color1 { background-color: #f6fff6; }
+tr.color2 { background-color: #f6f6ff; }
+tr.color3 { background-color: #fff6f6; }
+
 td {
        padding: 2px 5px;
        font-size: 100%;
@@ -345,6 +357,17 @@ td.mode {
        font-family: monospace;
 }
 
+/* progress of blame_interactive */
+div#progress_bar {
+       height: 2px;
+       margin-bottom: -2px;
+       background-color: #d8d9d0;
+}
+div#progress_info {
+       float: right;
+       text-align: right;
+}
+
 /* format of (optional) objects size in 'tree' view */
 td.size {
        font-family: monospace;
diff --git a/gitweb/gitweb.js b/gitweb/gitweb.js
new file mode 100644 (file)
index 0000000..2a25b7c
--- /dev/null
@@ -0,0 +1,870 @@
+// Copyright (C) 2007, Fredrik Kuivinen <frekui@gmail.com>
+//               2007, Petr Baudis <pasky@suse.cz>
+//          2008-2009, Jakub Narebski <jnareb@gmail.com>
+
+/**
+ * @fileOverview JavaScript code for gitweb (git web interface).
+ * @license GPLv2 or later
+ */
+
+/* ============================================================ */
+/* functions for generic gitweb actions and views */
+
+/**
+ * used to check if link has 'js' query parameter already (at end),
+ * and other reasons to not add 'js=1' param at the end of link
+ * @constant
+ */
+var jsExceptionsRe = /[;?]js=[01]$/;
+
+/**
+ * Add '?js=1' or ';js=1' to the end of every link in the document
+ * that doesn't have 'js' query parameter set already.
+ *
+ * Links with 'js=1' lead to JavaScript version of given action, if it
+ * exists (currently there is only 'blame_incremental' for 'blame')
+ *
+ * @globals jsExceptionsRe
+ */
+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';
+               }
+       }
+}
+
+
+/* ============================================================ */
+
+/*
+ * This code uses DOM methods instead of (nonstandard) innerHTML
+ * to modify page.
+ *
+ * innerHTML is non-standard IE extension, though supported by most
+ * browsers; however Firefox up to version 1.5 didn't implement it in
+ * a strict mode (application/xml+xhtml mimetype).
+ *
+ * Also my simple benchmarks show that using elem.firstChild.data =
+ * 'content' is slightly faster than elem.innerHTML = 'content'.  It
+ * is however more fragile (text element fragment must exists), and
+ * less feature-rich (we cannot add HTML).
+ *
+ * Note that DOM 2 HTML is preferred over generic DOM 2 Core; the
+ * equivalent using DOM 2 Core is usually shown in comments.
+ */
+
+
+/* ============================================================ */
+/* generic utility functions */
+
+
+/**
+ * pad number N with nonbreakable spaces on the left, to WIDTH characters
+ * example: padLeftStr(12, 3, '\u00A0') == '\u00A012'
+ *          ('\u00A0' is nonbreakable space)
+ *
+ * @param {Number|String} input: number to pad
+ * @param {Number} width: visible width of output
+ * @param {String} str: string to prefix to string, e.g. '\u00A0'
+ * @returns {String} INPUT prefixed with (WIDTH - INPUT.length) x STR
+ */
+function padLeftStr(input, width, str) {
+       var prefix = '';
+
+       width -= input.toString().length;
+       while (width > 0) {
+               prefix += str;
+               width--;
+       }
+       return prefix + input;
+}
+
+/**
+ * Pad INPUT on the left to SIZE width, using given padding character CH,
+ * for example padLeft('a', 3, '_') is '__a'.
+ *
+ * @param {String} input: input value converted to string.
+ * @param {Number} width: desired length of output.
+ * @param {String} ch: single character to prefix to string.
+ *
+ * @returns {String} Modified string, at least SIZE length.
+ */
+function padLeft(input, width, ch) {
+       var s = input + "";
+       while (s.length < width) {
+               s = ch + s;
+       }
+       return s;
+}
+
+/**
+ * Create XMLHttpRequest object in cross-browser way
+ * @returns XMLHttpRequest object, or null
+ */
+function createRequestObject() {
+       try {
+               return new XMLHttpRequest();
+       } catch (e) {}
+       try {
+               return window.createRequest();
+       } catch (e) {}
+       try {
+               return new ActiveXObject("Msxml2.XMLHTTP");
+       } catch (e) {}
+       try {
+               return new ActiveXObject("Microsoft.XMLHTTP");
+       } catch (e) {}
+
+       return null;
+}
+
+
+/* ============================================================ */
+/* utility/helper functions (and variables) */
+
+var xhr;        // XMLHttpRequest object
+var projectUrl; // partial query + separator ('?' or ';')
+
+// 'commits' is an associative map. It maps SHA1s to Commit objects.
+var commits = {};
+
+/**
+ * constructor for Commit objects, used in 'blame'
+ * @class Represents a blamed commit
+ * @param {String} sha1: SHA-1 identifier of a commit
+ */
+function Commit(sha1) {
+       if (this instanceof Commit) {
+               this.sha1 = sha1;
+               this.nprevious = 0; /* number of 'previous', effective parents */
+       } else {
+               return new Commit(sha1);
+       }
+}
+
+/* ............................................................ */
+/* progress info, timing, error reporting */
+
+var blamedLines = 0;
+var totalLines  = '???';
+var div_progress_bar;
+var div_progress_info;
+
+/**
+ * Detects how many lines does a blamed file have,
+ * This information is used in progress info
+ *
+ * @returns {Number|String} Number of lines in file, or string '...'
+ */
+function countLines() {
+       var table =
+               document.getElementById('blame_table') ||
+               document.getElementsByTagName('table')[0];
+
+       if (table) {
+               return table.getElementsByTagName('tr').length - 1; // for header
+       } else {
+               return '...';
+       }
+}
+
+/**
+ * update progress info and length (width) of progress bar
+ *
+ * @globals div_progress_info, div_progress_bar, blamedLines, totalLines
+ */
+function updateProgressInfo() {
+       if (!div_progress_info) {
+               div_progress_info = document.getElementById('progress_info');
+       }
+       if (!div_progress_bar) {
+               div_progress_bar = document.getElementById('progress_bar');
+       }
+       if (!div_progress_info && !div_progress_bar) {
+               return;
+       }
+
+       var percentage = Math.floor(100.0*blamedLines/totalLines);
+
+       if (div_progress_info) {
+               div_progress_info.firstChild.data  = blamedLines + ' / ' + totalLines +
+                       ' (' + padLeftStr(percentage, 3, '\u00A0') + '%)';
+       }
+
+       if (div_progress_bar) {
+               //div_progress_bar.setAttribute('style', 'width: '+percentage+'%;');
+               div_progress_bar.style.width = percentage + '%';
+       }
+}
+
+
+var t_interval_server = '';
+var cmds_server = '';
+var t0 = new Date();
+
+/**
+ * write how much it took to generate data, and to run script
+ *
+ * @globals t0, t_interval_server, cmds_server
+ */
+function writeTimeInterval() {
+       var info_time = document.getElementById('generating_time');
+       if (!info_time || !t_interval_server) {
+               return;
+       }
+       var t1 = new Date();
+       info_time.firstChild.data += ' + (' +
+               t_interval_server + ' sec server blame_data / ' +
+               (t1.getTime() - t0.getTime())/1000 + ' sec client JavaScript)';
+
+       var info_cmds = document.getElementById('generating_cmd');
+       if (!info_time || !cmds_server) {
+               return;
+       }
+       info_cmds.firstChild.data += ' + ' + cmds_server;
+}
+
+/**
+ * show an error message alert to user within page (in prohress info area)
+ * @param {String} str: plain text error message (no HTML)
+ *
+ * @globals div_progress_info
+ */
+function errorInfo(str) {
+       if (!div_progress_info) {
+               div_progress_info = document.getElementById('progress_info');
+       }
+       if (div_progress_info) {
+               div_progress_info.className = 'error';
+               div_progress_info.firstChild.data = str;
+       }
+}
+
+/* ............................................................ */
+/* coloring rows during blame_data (git blame --incremental) run */
+
+/**
+ * used to extract N from 'colorN', where N is a number,
+ * @constant
+ */
+var colorRe = /\bcolor([0-9]*)\b/;
+
+/**
+ * return N if <tr class="colorN">, otherwise return null
+ * (some browsers require CSS class names to begin with letter)
+ *
+ * @param {HTMLElement} tr: table row element to check
+ * @param {String} tr.className: 'class' attribute of tr element
+ * @returns {Number|null} N if tr.className == 'colorN', otherwise null
+ *
+ * @globals colorRe
+ */
+function getColorNo(tr) {
+       if (!tr) {
+               return null;
+       }
+       var className = tr.className;
+       if (className) {
+               var match = colorRe.exec(className);
+               if (match) {
+                       return parseInt(match[1], 10);
+               }
+       }
+       return null;
+}
+
+var colorsFreq = [0, 0, 0];
+/**
+ * return one of given possible colors (curently least used one)
+ * example: chooseColorNoFrom(2, 3) returns 2 or 3
+ *
+ * @param {Number[]} arguments: one or more numbers
+ *        assumes that  1 <= arguments[i] <= colorsFreq.length
+ * @returns {Number} Least used color number from arguments
+ * @globals colorsFreq
+ */
+function chooseColorNoFrom() {
+       // choose the color which is least used
+       var colorNo = arguments[0];
+       for (var i = 1; i < arguments.length; i++) {
+               if (colorsFreq[arguments[i]-1] < colorsFreq[colorNo-1]) {
+                       colorNo = arguments[i];
+               }
+       }
+       colorsFreq[colorNo-1]++;
+       return colorNo;
+}
+
+/**
+ * given two neigbour <tr> elements, find color which would be different
+ * from color of both of neighbours; used to 3-color blame table
+ *
+ * @param {HTMLElement} tr_prev
+ * @param {HTMLElement} tr_next
+ * @returns {Number} color number N such that
+ * colorN != tr_prev.className && colorN != tr_next.className
+ */
+function findColorNo(tr_prev, tr_next) {
+       var color_prev = getColorNo(tr_prev);
+       var color_next = getColorNo(tr_next);
+
+
+       // neither of neighbours has color set
+       // THEN we can use any of 3 possible colors
+       if (!color_prev && !color_next) {
+               return chooseColorNoFrom(1,2,3);
+       }
+
+       // either both neighbours have the same color,
+       // or only one of neighbours have color set
+       // THEN we can use any color except given
+       var color;
+       if (color_prev === color_next) {
+               color = color_prev; // = color_next;
+       } else if (!color_prev) {
+               color = color_next;
+       } else if (!color_next) {
+               color = color_prev;
+       }
+       if (color) {
+               return chooseColorNoFrom((color % 3) + 1, ((color+1) % 3) + 1);
+       }
+
+       // neighbours have different colors
+       // THEN there is only one color left
+       return (3 - ((color_prev + color_next) % 3));
+}
+
+/* ............................................................ */
+/* coloring rows like 'blame' after 'blame_data' finishes */
+
+/**
+ * returns true if given row element (tr) is first in commit group
+ * to be used only after 'blame_data' finishes (after processing)
+ *
+ * @param {HTMLElement} tr: table row
+ * @returns {Boolean} true if TR is first in commit group
+ */
+function isStartOfGroup(tr) {
+       return tr.firstChild.className === 'sha1';
+}
+
+/**
+ * change colors to use zebra coloring (2 colors) instead of 3 colors
+ * concatenate neighbour commit groups belonging to the same commit
+ *
+ * @globals colorRe
+ */
+function fixColorsAndGroups() {
+       var colorClasses = ['light', 'dark'];
+       var linenum = 1;
+       var tr, prev_group;
+       var colorClass = 0;
+       var table =
+               document.getElementById('blame_table') ||
+               document.getElementsByTagName('table')[0];
+
+       while ((tr = document.getElementById('l'+linenum))) {
+       // index origin is 0, which is table header; start from 1
+       //while ((tr = table.rows[linenum])) { // <- it is slower
+               if (isStartOfGroup(tr, linenum, document)) {
+                       if (prev_group &&
+                           prev_group.firstChild.firstChild.href ===
+                                   tr.firstChild.firstChild.href) {
+                               // we have to concatenate groups
+                               var prev_rows = prev_group.firstChild.rowSpan || 1;
+                               var curr_rows =         tr.firstChild.rowSpan || 1;
+                               prev_group.firstChild.rowSpan = prev_rows + curr_rows;
+                               //tr.removeChild(tr.firstChild);
+                               tr.deleteCell(0); // DOM2 HTML way
+                       } else {
+                               colorClass = (colorClass + 1) % 2;
+                               prev_group = tr;
+                       }
+               }
+               var tr_class = tr.className;
+               tr.className = tr_class.replace(colorRe, colorClasses[colorClass]);
+               linenum++;
+       }
+}
+
+/* ............................................................ */
+/* time and data */
+
+/**
+ * used to extract hours and minutes from timezone info, e.g '-0900'
+ * @constant
+ */
+var tzRe = /^([+-][0-9][0-9])([0-9][0-9])$/;
+
+/**
+ * return date in local time formatted in iso-8601 like format
+ * 'yyyy-mm-dd HH:MM:SS +/-ZZZZ' e.g. '2005-08-07 21:49:46 +0200'
+ *
+ * @param {Number} epoch: seconds since '00:00:00 1970-01-01 UTC'
+ * @param {String} timezoneInfo: numeric timezone '(+|-)HHMM'
+ * @returns {String} date in local time in iso-8601 like format
+ *
+ * @globals tzRe
+ */
+function formatDateISOLocal(epoch, timezoneInfo) {
+       var match = tzRe.exec(timezoneInfo);
+       // date corrected by timezone
+       var localDate = new Date(1000 * (epoch +
+               (parseInt(match[1],10)*3600 + parseInt(match[2],10)*60)));
+       var localDateStr = // e.g. '2005-08-07'
+               localDate.getUTCFullYear()                 + '-' +
+               padLeft(localDate.getUTCMonth()+1, 2, '0') + '-' +
+               padLeft(localDate.getUTCDate(),    2, '0');
+       var localTimeStr = // e.g. '21:49:46'
+               padLeft(localDate.getUTCHours(),   2, '0') + ':' +
+               padLeft(localDate.getUTCMinutes(), 2, '0') + ':' +
+               padLeft(localDate.getUTCSeconds(), 2, '0');
+
+       return localDateStr + ' ' + localTimeStr + ' ' + timezoneInfo;
+}
+
+/* ............................................................ */
+/* unquoting/unescaping filenames */
+
+/**#@+
+ * @constant
+ */
+var escCodeRe = /\\([^0-7]|[0-7]{1,3})/g;
+var octEscRe = /^[0-7]{1,3}$/;
+var maybeQuotedRe = /^\"(.*)\"$/;
+/**#@-*/
+
+/**
+ * unquote maybe git-quoted filename
+ * e.g. 'aa' -> 'aa', '"a\ta"' -> 'a   a'
+ *
+ * @param {String} str: git-quoted string
+ * @returns {String} Unquoted and unescaped string
+ *
+ * @globals escCodeRe, octEscRe, maybeQuotedRe
+ */
+function unquote(str) {
+       function unq(seq) {
+               var es = {
+                       // character escape codes, aka escape sequences (from C)
+                       // replacements are to some extent JavaScript specific
+                       t: "\t",   // tab            (HT, TAB)
+                       n: "\n",   // newline        (NL)
+                       r: "\r",   // return         (CR)
+                       f: "\f",   // form feed      (FF)
+                       b: "\b",   // backspace      (BS)
+                       a: "\x07", // alarm (bell)   (BEL)
+                       e: "\x1B", // escape         (ESC)
+                       v: "\v"    // vertical tab   (VT)
+               };
+
+               if (seq.search(octEscRe) !== -1) {
+                       // octal char sequence
+                       return String.fromCharCode(parseInt(seq, 8));
+               } else if (seq in es) {
+                       // C escape sequence, aka character escape code
+                       return es[seq];
+               }
+               // quoted ordinary character
+               return seq;
+       }
+
+       var match = str.match(maybeQuotedRe);
+       if (match) {
+               str = match[1];
+               // perhaps str = eval('"'+str+'"'); would be enough?
+               str = str.replace(escCodeRe,
+                       function (substr, p1, offset, s) { return unq(p1); });
+       }
+       return str;
+}
+
+/* ============================================================ */
+/* main part: parsing response */
+
+/**
+ * Function called for each blame entry, as soon as it finishes.
+ * It updates page via DOM manipulation, adding sha1 info, etc.
+ *
+ * @param {Commit} commit: blamed commit
+ * @param {Object} group: object representing group of lines,
+ *                        which blame the same commit (blame entry)
+ *
+ * @globals blamedLines
+ */
+function handleLine(commit, group) {
+       /*
+          This is the structure of the HTML fragment we are working
+          with:
+
+          <tr id="l123" class="">
+            <td class="sha1" title=""><a href=""> </a></td>
+            <td class="linenr"><a class="linenr" href="">123</a></td>
+            <td class="pre"># times (my ext3 doesn&#39;t).</td>
+          </tr>
+       */
+
+       var resline = group.resline;
+
+       // format date and time string only once per commit
+       if (!commit.info) {
+               /* e.g. 'Kay Sievers, 2005-08-07 21:49:46 +0200' */
+               commit.info = commit.author + ', ' +
+                       formatDateISOLocal(commit.authorTime, commit.authorTimezone);
+       }
+
+       // color depends on group of lines, not only on blamed commit
+       var colorNo = findColorNo(
+               document.getElementById('l'+(resline-1)),
+               document.getElementById('l'+(resline+group.numlines))
+       );
+
+       // loop over lines in commit group
+       for (var i = 0; i < group.numlines; i++, resline++) {
+               var tr = document.getElementById('l'+resline);
+               if (!tr) {
+                       break;
+               }
+               /*
+                       <tr id="l123" class="">
+                         <td class="sha1" title=""><a href=""> </a></td>
+                         <td class="linenr"><a class="linenr" href="">123</a></td>
+                         <td class="pre"># times (my ext3 doesn&#39;t).</td>
+                       </tr>
+               */
+               var td_sha1  = tr.firstChild;
+               var a_sha1   = td_sha1.firstChild;
+               var a_linenr = td_sha1.nextSibling.firstChild;
+
+               /* <tr id="l123" class=""> */
+               var tr_class = '';
+               if (colorNo !== null) {
+                       tr_class = 'color'+colorNo;
+               }
+               if (commit.boundary) {
+                       tr_class += ' boundary';
+               }
+               if (commit.nprevious === 0) {
+                       tr_class += ' no-previous';
+               } else if (commit.nprevious > 1) {
+                       tr_class += ' multiple-previous';
+               }
+               tr.className = tr_class;
+
+               /* <td class="sha1" title="?" rowspan="?"><a href="?">?</a></td> */
+               if (i === 0) {
+                       td_sha1.title = commit.info;
+                       td_sha1.rowSpan = group.numlines;
+
+                       a_sha1.href = projectUrl + 'a=commit;h=' + commit.sha1;
+                       if (a_sha1.firstChild) {
+                               a_sha1.firstChild.data = commit.sha1.substr(0, 8);
+                       } else {
+                               a_sha1.appendChild(
+                                       document.createTextNode(commit.sha1.substr(0, 8)));
+                       }
+                       if (group.numlines >= 2) {
+                               var fragment = document.createDocumentFragment();
+                               var br   = document.createElement("br");
+                               var match = commit.author.match(/\b([A-Z])\B/g);
+                               if (match) {
+                                       var text = document.createTextNode(
+                                                       match.join(''));
+                               }
+                               if (br && text) {
+                                       var elem = fragment || td_sha1;
+                                       elem.appendChild(br);
+                                       elem.appendChild(text);
+                                       if (fragment) {
+                                               td_sha1.appendChild(fragment);
+                                       }
+                               }
+                       }
+               } else {
+                       //tr.removeChild(td_sha1); // DOM2 Core way
+                       tr.deleteCell(0); // DOM2 HTML way
+               }
+
+               /* <td class="linenr"><a class="linenr" href="?">123</a></td> */
+               var linenr_commit =
+                       ('previous' in commit ? commit.previous : commit.sha1);
+               var linenr_filename =
+                       ('file_parent' in commit ? commit.file_parent : commit.filename);
+               a_linenr.href = projectUrl + 'a=blame_incremental' +
+                       ';hb=' + linenr_commit +
+                       ';f='  + encodeURIComponent(linenr_filename) +
+                       '#l' + (group.srcline + i);
+
+               blamedLines++;
+
+               //updateProgressInfo();
+       }
+}
+
+// ----------------------------------------------------------------------
+
+var inProgress = false;   // are we processing response
+
+/**#@+
+ * @constant
+ */
+var sha1Re = /^([0-9a-f]{40}) ([0-9]+) ([0-9]+) ([0-9]+)/;
+var infoRe = /^([a-z-]+) ?(.*)/;
+var endRe  = /^END ?([^ ]*) ?(.*)/;
+/**@-*/
+
+var curCommit = new Commit();
+var curGroup  = {};
+
+var pollTimer = null;
+
+/**
+ * Parse output from 'git blame --incremental [...]', received via
+ * XMLHttpRequest from server (blamedataUrl), and call handleLine
+ * (which updates page) as soon as blame entry is completed.
+ *
+ * @param {String[]} lines: new complete lines from blamedata server
+ *
+ * @globals commits, curCommit, curGroup, t_interval_server, cmds_server
+ * @globals sha1Re, infoRe, endRe
+ */
+function processBlameLines(lines) {
+       var match;
+
+       for (var i = 0, len = lines.length; i < len; i++) {
+
+               if ((match = sha1Re.exec(lines[i]))) {
+                       var sha1 = match[1];
+                       var srcline  = parseInt(match[2], 10);
+                       var resline  = parseInt(match[3], 10);
+                       var numlines = parseInt(match[4], 10);
+
+                       var c = commits[sha1];
+                       if (!c) {
+                               c = new Commit(sha1);
+                               commits[sha1] = c;
+                       }
+                       curCommit = c;
+
+                       curGroup.srcline = srcline;
+                       curGroup.resline = resline;
+                       curGroup.numlines = numlines;
+
+               } else if ((match = infoRe.exec(lines[i]))) {
+                       var info = match[1];
+                       var data = match[2];
+                       switch (info) {
+                       case 'filename':
+                               curCommit.filename = unquote(data);
+                               // 'filename' information terminates the entry
+                               handleLine(curCommit, curGroup);
+                               updateProgressInfo();
+                               break;
+                       case 'author':
+                               curCommit.author = data;
+                               break;
+                       case 'author-time':
+                               curCommit.authorTime = parseInt(data, 10);
+                               break;
+                       case 'author-tz':
+                               curCommit.authorTimezone = data;
+                               break;
+                       case 'previous':
+                               curCommit.nprevious++;
+                               // store only first 'previous' header
+                               if (!'previous' in curCommit) {
+                                       var parts = data.split(' ', 2);
+                                       curCommit.previous    = parts[0];
+                                       curCommit.file_parent = unquote(parts[1]);
+                               }
+                               break;
+                       case 'boundary':
+                               curCommit.boundary = true;
+                               break;
+                       } // end switch
+
+               } else if ((match = endRe.exec(lines[i]))) {
+                       t_interval_server = match[1];
+                       cmds_server = match[2];
+
+               } else if (lines[i] !== '') {
+                       // malformed line
+
+               } // end if (match)
+
+       } // end for (lines)
+}
+
+/**
+ * Process new data and return pointer to end of processed part
+ *
+ * @param {String} unprocessed: new data (from nextReadPos)
+ * @param {Number} nextReadPos: end of last processed data
+ * @return {Number} end of processed data (new value for nextReadPos)
+ */
+function processData(unprocessed, nextReadPos) {
+       var lastLineEnd = unprocessed.lastIndexOf('\n');
+       if (lastLineEnd !== -1) {
+               var lines = unprocessed.substring(0, lastLineEnd).split('\n');
+               nextReadPos += lastLineEnd + 1 /* 1 == '\n'.length */;
+
+               processBlameLines(lines);
+       } // end if
+
+       return nextReadPos;
+}
+
+/**
+ * Handle XMLHttpRequest errors
+ *
+ * @param {XMLHttpRequest} xhr: XMLHttpRequest object
+ *
+ * @globals pollTimer, commits, inProgress
+ */
+function handleError(xhr) {
+       errorInfo('Server error: ' +
+               xhr.status + ' - ' + (xhr.statusText || 'Error contacting server'));
+
+       clearInterval(pollTimer);
+       commits = {}; // free memory
+
+       inProgress = false;
+}
+
+/**
+ * Called after XMLHttpRequest finishes (loads)
+ *
+ * @param {XMLHttpRequest} xhr: XMLHttpRequest object (unused)
+ *
+ * @globals pollTimer, commits, inProgress
+ */
+function responseLoaded(xhr) {
+       clearInterval(pollTimer);
+
+       fixColorsAndGroups();
+       writeTimeInterval();
+       commits = {}; // free memory
+
+       inProgress = false;
+}
+
+/**
+ * handler for XMLHttpRequest onreadystatechange event
+ * @see startBlame
+ *
+ * @globals xhr, inProgress
+ */
+function handleResponse() {
+
+       /*
+        * xhr.readyState
+        *
+        *  Value  Constant (W3C)    Description
+        *  -------------------------------------------------------------------
+        *  0      UNSENT            open() has not been called yet.
+        *  1      OPENED            send() has not been called yet.
+        *  2      HEADERS_RECEIVED  send() has been called, and headers
+        *                           and status are available.
+        *  3      LOADING           Downloading; responseText holds partial data.
+        *  4      DONE              The operation is complete.
+        */
+
+       if (xhr.readyState !== 4 && xhr.readyState !== 3) {
+               return;
+       }
+
+       // the server returned error
+       if (xhr.readyState === 3 && xhr.status !== 200) {
+               return;
+       }
+       if (xhr.readyState === 4 && xhr.status !== 200) {
+               handleError(xhr);
+               return;
+       }
+
+       // In konqueror xhr.responseText is sometimes null here...
+       if (xhr.responseText === null) {
+               return;
+       }
+
+       // in case we were called before finished processing
+       if (inProgress) {
+               return;
+       } else {
+               inProgress = true;
+       }
+
+       // extract new whole (complete) lines, and process them
+       while (xhr.prevDataLength !== xhr.responseText.length) {
+               if (xhr.readyState === 4 &&
+                   xhr.prevDataLength === xhr.responseText.length) {
+                       break;
+               }
+
+               xhr.prevDataLength = xhr.responseText.length;
+               var unprocessed = xhr.responseText.substring(xhr.nextReadPos);
+               xhr.nextReadPos = processData(unprocessed, xhr.nextReadPos);
+       } // end while
+
+       // did we finish work?
+       if (xhr.readyState === 4 &&
+           xhr.prevDataLength === xhr.responseText.length) {
+               responseLoaded(xhr);
+       }
+
+       inProgress = false;
+}
+
+// ============================================================
+// ------------------------------------------------------------
+
+/**
+ * Incrementally update line data in blame_incremental view in gitweb.
+ *
+ * @param {String} blamedataUrl: URL to server script generating blame data.
+ * @param {String} bUrl: partial URL to project, used to generate links.
+ *
+ * Called from 'blame_incremental' view after loading table with
+ * file contents, a base for blame view.
+ *
+ * @globals xhr, t0, projectUrl, div_progress_bar, totalLines, pollTimer
+*/
+function startBlame(blamedataUrl, bUrl) {
+
+       xhr = createRequestObject();
+       if (!xhr) {
+               errorInfo('ERROR: XMLHttpRequest not supported');
+               return;
+       }
+
+       t0 = new Date();
+       projectUrl = bUrl + (bUrl.indexOf('?') === -1 ? '?' : ';');
+       if ((div_progress_bar = document.getElementById('progress_bar'))) {
+               //div_progress_bar.setAttribute('style', 'width: 100%;');
+               div_progress_bar.style.cssText = 'width: 100%;';
+       }
+       totalLines = countLines();
+       updateProgressInfo();
+
+       /* add extra properties to xhr object to help processing response */
+       xhr.prevDataLength = -1;  // used to detect if we have new data
+       xhr.nextReadPos = 0;      // where unread part of response starts
+
+       xhr.onreadystatechange = handleResponse;
+       //xhr.onreadystatechange = function () { handleResponse(xhr); };
+
+       xhr.open('GET', blamedataUrl);
+       xhr.setRequestHeader('Accept', 'text/plain');
+       xhr.send(null);
+
+       // not all browsers call onreadystatechange event on each server flush
+       // poll response using timer every second to handle this issue
+       pollTimer = setInterval(xhr.onreadystatechange, 1000);
+}
+
+// end of gitweb.js
index f94536c680c05932595f0766fde1d87b5a8f2162..7e477af9567cff322d18fc9a1fbfe507732418dc 100755 (executable)
@@ -18,6 +18,12 @@ use File::Find qw();
 use File::Basename qw(basename);
 binmode STDOUT, ':utf8';
 
+our $t0;
+if (eval { require Time::HiRes; 1; }) {
+       $t0 = [Time::HiRes::gettimeofday()];
+}
+our $number_of_git_cmds = 0;
+
 BEGIN {
        CGI->compile() if $ENV{'MOD_PERL'};
 }
@@ -90,6 +96,8 @@ our $stylesheet = undef;
 our $logo = "++GITWEB_LOGO++";
 # URI of GIT favicon, assumed to be image/png type
 our $favicon = "++GITWEB_FAVICON++";
+# URI of gitweb.js (JavaScript code for gitweb)
+our $javascript = "++GITWEB_JS++";
 
 # URI and label (title) of GIT logo link
 #our $logo_url = "http://www.kernel.org/pub/software/scm/git/docs/";
@@ -417,6 +425,20 @@ our %feature = (
                'sub' => \&feature_avatar,
                'override' => 0,
                'default' => ['']},
+
+       # Enable displaying how much time and how many git commands
+       # it took to generate and display page.  Disabled by default.
+       # Project specific override is not supported.
+       'timed' => {
+               'override' => 0,
+               'default' => [0]},
+
+       # Enable turning some links into links to actions which require
+       # JavaScript to run (like 'blame_incremental').  Not enabled by
+       # default.  Project specific override is currently not supported.
+       'javascript-actions' => {
+               'override' => 0,
+               'default' => [0]},
 );
 
 sub gitweb_get_feature {
@@ -531,6 +553,7 @@ if (-e $GITWEB_CONFIG) {
 
 # version of the core git binary
 our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown";
+$number_of_git_cmds++;
 
 $projects_list ||= $projectroot;
 
@@ -568,12 +591,16 @@ our @cgi_param_mapping = (
        snapshot_format => "sf",
        extra_options => "opt",
        search_use_regexp => "sr",
+       # this must be last entry (for manipulation from JavaScript)
+       javascript => "js"
 );
 our %cgi_param_mapping = @cgi_param_mapping;
 
 # we will also need to know the possible actions, for validation
 our %actions = (
        "blame" => \&git_blame,
+       "blame_incremental" => \&git_blame_incremental,
+       "blame_data" => \&git_blame_data,
        "blobdiff" => \&git_blobdiff,
        "blobdiff_plain" => \&git_blobdiff_plain,
        "blob" => \&git_blob,
@@ -2006,6 +2033,7 @@ sub get_feed_info {
 
 # returns path to the core git executable and the --git-dir parameter as list
 sub git_cmd {
+       $number_of_git_cmds++;
        return $GIT, '--git-dir='.$git_dir;
 }
 
@@ -3281,10 +3309,36 @@ sub git_footer_html {
        }
        print "</div>\n"; # class="page_footer"
 
+       if (defined $t0 && gitweb_check_feature('timed')) {
+               print "<div id=\"generating_info\">\n";
+               print 'This page took '.
+                     '<span id="generating_time" class="time_span">'.
+                     Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]).
+                     ' seconds </span>'.
+                     ' and '.
+                     '<span id="generating_cmd">'.
+                     $number_of_git_cmds.
+                     '</span> git commands '.
+                     " to generate.\n";
+               print "</div>\n"; # class="page_footer"
+       }
+
        if (-f $site_footer) {
                insert_file($site_footer);
        }
 
+       print qq!<script type="text/javascript" src="$javascript"></script>\n!;
+       if ($action eq 'blame_incremental') {
+               print qq!<script type="text/javascript">\n!.
+                     qq!startBlame("!. href(action=>"blame_data", -replay=>1) .qq!",\n!.
+                     qq!           "!. href() .qq!");\n!.
+                     qq!</script>\n!;
+       } elsif (gitweb_check_feature('javascript-actions')) {
+               print qq!<script type="text/javascript">\n!.
+                     qq!window.onload = fixLinks;\n!.
+                     qq!</script>\n!;
+       }
+
        print "</body>\n" .
              "</html>";
 }
@@ -4881,7 +4935,13 @@ sub git_tag {
        git_footer_html();
 }
 
-sub git_blame {
+sub git_blame_common {
+       my $format = shift || 'porcelain';
+       if ($format eq 'porcelain' && $cgi->param('js')) {
+               $format = 'incremental';
+               $action = 'blame_incremental'; # for page title etc
+       }
+
        # permissions
        gitweb_check_feature('blame')
                or die_error(403, "Blame view not allowed");
@@ -4903,123 +4963,220 @@ sub git_blame {
                }
        }
 
-       # run git-blame --porcelain
-       open my $fd, "-|", git_cmd(), "blame", '-p',
-               $hash_base, '--', $file_name
-               or die_error(500, "Open git-blame failed");
+       my $fd;
+       if ($format eq 'incremental') {
+               # get file contents (as base)
+               open $fd, "-|", git_cmd(), 'cat-file', 'blob', $hash
+                       or die_error(500, "Open git-cat-file failed");
+       } elsif ($format eq 'data') {
+               # run git-blame --incremental
+               open $fd, "-|", git_cmd(), "blame", "--incremental",
+                       $hash_base, "--", $file_name
+                       or die_error(500, "Open git-blame --incremental failed");
+       } else {
+               # run git-blame --porcelain
+               open $fd, "-|", git_cmd(), "blame", '-p',
+                       $hash_base, '--', $file_name
+                       or die_error(500, "Open git-blame --porcelain failed");
+       }
+
+       # incremental blame data returns early
+       if ($format eq 'data') {
+               print $cgi->header(
+                       -type=>"text/plain", -charset => "utf-8",
+                       -status=> "200 OK");
+               local $| = 1; # output autoflush
+               print while <$fd>;
+               close $fd
+                       or print "ERROR $!\n";
+
+               print 'END';
+               if (defined $t0 && gitweb_check_feature('timed')) {
+                       print ' '.
+                             Time::HiRes::tv_interval($t0, [Time::HiRes::gettimeofday()]).
+                             ' '.$number_of_git_cmds;
+               }
+               print "\n";
+
+               return;
+       }
 
        # page header
        git_header_html();
        my $formats_nav =
                $cgi->a({-href => href(action=>"blob", -replay=>1)},
                        "blob") .
+               " | ";
+       if ($format eq 'incremental') {
+               $formats_nav .=
+                       $cgi->a({-href => href(action=>"blame", javascript=>0, -replay=>1)},
+                               "blame") . " (non-incremental)";
+       } else {
+               $formats_nav .=
+                       $cgi->a({-href => href(action=>"blame_incremental", -replay=>1)},
+                               "blame") . " (incremental)";
+       }
+       $formats_nav .=
                " | " .
                $cgi->a({-href => href(action=>"history", -replay=>1)},
                        "history") .
                " | " .
-               $cgi->a({-href => href(action=>"blame", file_name=>$file_name)},
+               $cgi->a({-href => href(action=>$action, file_name=>$file_name)},
                        "HEAD");
        git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
        git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
        git_print_page_path($file_name, $ftype, $hash_base);
 
        # page body
+       if ($format eq 'incremental') {
+               print "<noscript>\n<div class=\"error\"><center><b>\n".
+                     "This page requires JavaScript to run.\n Use ".
+                     $cgi->a({-href => href(action=>'blame',javascript=>0,-replay=>1)},
+                             'this page').
+                     " instead.\n".
+                     "</b></center></div>\n</noscript>\n";
+
+               print qq!<div id="progress_bar" style="width: 100%; background-color: yellow"></div>\n!;
+       }
+
+       print qq!<div class="page_body">\n!;
+       print qq!<div id="progress_info">... / ...</div>\n!
+               if ($format eq 'incremental');
+       print qq!<table id="blame_table" class="blame" width="100%">\n!.
+             #qq!<col width="5.5em" /><col width="2.5em" /><col width="*" />\n!.
+             qq!<thead>\n!.
+             qq!<tr><th>Commit</th><th>Line</th><th>Data</th></tr>\n!.
+             qq!</thead>\n!.
+             qq!<tbody>\n!;
+
        my @rev_color = qw(light dark);
        my $num_colors = scalar(@rev_color);
        my $current_color = 0;
-       my %metainfo = ();
 
-       print <<HTML;
-<div class="page_body">
-<table class="blame">
-<tr><th>Commit</th><th>Line</th><th>Data</th></tr>
-HTML
- LINE:
-       while (my $line = <$fd>) {
-               chomp $line;
-               # the header: <SHA-1> <src lineno> <dst lineno> [<lines in group>]
-               # no <lines in group> for subsequent lines in group of lines
-               my ($full_rev, $orig_lineno, $lineno, $group_size) =
-                  ($line =~ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/);
-               if (!exists $metainfo{$full_rev}) {
-                       $metainfo{$full_rev} = { 'nprevious' => 0 };
-               }
-               my $meta = $metainfo{$full_rev};
-               my $data;
-               while ($data = <$fd>) {
-                       chomp $data;
-                       last if ($data =~ s/^\t//); # contents of line
-                       if ($data =~ /^(\S+)(?: (.*))?$/) {
-                               $meta->{$1} = $2 unless exists $meta->{$1};
+       if ($format eq 'incremental') {
+               my $color_class = $rev_color[$current_color];
+
+               #contents of a file
+               my $linenr = 0;
+       LINE:
+               while (my $line = <$fd>) {
+                       chomp $line;
+                       $linenr++;
+
+                       print qq!<tr id="l$linenr" class="$color_class">!.
+                             qq!<td class="sha1"><a href=""> </a></td>!.
+                             qq!<td class="linenr">!.
+                             qq!<a class="linenr" href="">$linenr</a></td>!;
+                       print qq!<td class="pre">! . esc_html($line) . "</td>\n";
+                       print qq!</tr>\n!;
+               }
+
+       } else { # porcelain, i.e. ordinary blame
+               my %metainfo = (); # saves information about commits
+
+               # blame data
+       LINE:
+               while (my $line = <$fd>) {
+                       chomp $line;
+                       # the header: <SHA-1> <src lineno> <dst lineno> [<lines in group>]
+                       # no <lines in group> for subsequent lines in group of lines
+                       my ($full_rev, $orig_lineno, $lineno, $group_size) =
+                          ($line =~ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/);
+                       if (!exists $metainfo{$full_rev}) {
+                               $metainfo{$full_rev} = { 'nprevious' => 0 };
                        }
-                       if ($data =~ /^previous /) {
-                               $meta->{'nprevious'}++;
+                       my $meta = $metainfo{$full_rev};
+                       my $data;
+                       while ($data = <$fd>) {
+                               chomp $data;
+                               last if ($data =~ s/^\t//); # contents of line
+                               if ($data =~ /^(\S+)(?: (.*))?$/) {
+                                       $meta->{$1} = $2 unless exists $meta->{$1};
+                               }
+                               if ($data =~ /^previous /) {
+                                       $meta->{'nprevious'}++;
+                               }
                        }
-               }
-               my $short_rev = substr($full_rev, 0, 8);
-               my $author = $meta->{'author'};
-               my %date =
-                       parse_date($meta->{'author-time'}, $meta->{'author-tz'});
-               my $date = $date{'iso-tz'};
-               if ($group_size) {
-                       $current_color = ($current_color + 1) % $num_colors;
-               }
-               my $tr_class = $rev_color[$current_color];
-               $tr_class .= ' boundary' if (exists $meta->{'boundary'});
-               $tr_class .= ' no-previous' if ($meta->{'nprevious'} == 0);
-               $tr_class .= ' multiple-previous' if ($meta->{'nprevious'} > 1);
-               print "<tr id=\"l$lineno\" class=\"$tr_class\">\n";
-               if ($group_size) {
-                       print "<td class=\"sha1\"";
-                       print " title=\"". esc_html($author) . ", $date\"";
-                       print " rowspan=\"$group_size\"" if ($group_size > 1);
-                       print ">";
-                       print $cgi->a({-href => href(action=>"commit",
-                                                    hash=>$full_rev,
-                                                    file_name=>$file_name)},
-                                     esc_html($short_rev));
-                       if ($group_size >= 2) {
-                               my @author_initials = ($author =~ /\b([[:upper:]])\B/g);
-                               if (@author_initials) {
-                                       print "<br />" .
-                                             esc_html(join('', @author_initials));
-                                       #           or join('.', ...)
+                       my $short_rev = substr($full_rev, 0, 8);
+                       my $author = $meta->{'author'};
+                       my %date =
+                               parse_date($meta->{'author-time'}, $meta->{'author-tz'});
+                       my $date = $date{'iso-tz'};
+                       if ($group_size) {
+                               $current_color = ($current_color + 1) % $num_colors;
+                       }
+                       my $tr_class = $rev_color[$current_color];
+                       $tr_class .= ' boundary' if (exists $meta->{'boundary'});
+                       $tr_class .= ' no-previous' if ($meta->{'nprevious'} == 0);
+                       $tr_class .= ' multiple-previous' if ($meta->{'nprevious'} > 1);
+                       print "<tr id=\"l$lineno\" class=\"$tr_class\">\n";
+                       if ($group_size) {
+                               print "<td class=\"sha1\"";
+                               print " title=\"". esc_html($author) . ", $date\"";
+                               print " rowspan=\"$group_size\"" if ($group_size > 1);
+                               print ">";
+                               print $cgi->a({-href => href(action=>"commit",
+                                                            hash=>$full_rev,
+                                                            file_name=>$file_name)},
+                                             esc_html($short_rev));
+                               if ($group_size >= 2) {
+                                       my @author_initials = ($author =~ /\b([[:upper:]])\B/g);
+                                       if (@author_initials) {
+                                               print "<br />" .
+                                                     esc_html(join('', @author_initials));
+                                               #           or join('.', ...)
+                                       }
                                }
+                               print "</td>\n";
                        }
-                       print "</td>\n";
-               }
-               # 'previous' <sha1 of parent commit> <filename at commit>
-               if (exists $meta->{'previous'} &&
-                   $meta->{'previous'} =~ /^([a-fA-F0-9]{40}) (.*)$/) {
-                       $meta->{'parent'} = $1;
-                       $meta->{'file_parent'} = unquote($2);
-               }
-               my $linenr_commit =
-                       exists($meta->{'parent'}) ?
-                       $meta->{'parent'} : $full_rev;
-               my $linenr_filename =
-                       exists($meta->{'file_parent'}) ?
-                       $meta->{'file_parent'} : unquote($meta->{'filename'});
-               my $blamed = href(action => 'blame',
-                                 file_name => $linenr_filename,
-                                 hash_base => $linenr_commit);
-               print "<td class=\"linenr\">";
-               print $cgi->a({ -href => "$blamed#l$orig_lineno",
-                               -class => "linenr" },
-                             esc_html($lineno));
-               print "</td>";
-               print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
-               print "</tr>\n";
+                       # 'previous' <sha1 of parent commit> <filename at commit>
+                       if (exists $meta->{'previous'} &&
+                           $meta->{'previous'} =~ /^([a-fA-F0-9]{40}) (.*)$/) {
+                               $meta->{'parent'} = $1;
+                               $meta->{'file_parent'} = unquote($2);
+                       }
+                       my $linenr_commit =
+                               exists($meta->{'parent'}) ?
+                               $meta->{'parent'} : $full_rev;
+                       my $linenr_filename =
+                               exists($meta->{'file_parent'}) ?
+                               $meta->{'file_parent'} : unquote($meta->{'filename'});
+                       my $blamed = href(action => 'blame',
+                                         file_name => $linenr_filename,
+                                         hash_base => $linenr_commit);
+                       print "<td class=\"linenr\">";
+                       print $cgi->a({ -href => "$blamed#l$orig_lineno",
+                                       -class => "linenr" },
+                                     esc_html($lineno));
+                       print "</td>";
+                       print "<td class=\"pre\">" . esc_html($data) . "</td>\n";
+                       print "</tr>\n";
+               } # end while
+
        }
-       print "</table>\n";
-       print "</div>";
+
+       # footer
+       print "</tbody>\n".
+             "</table>\n"; # class="blame"
+       print "</div>\n";   # class="blame_body"
        close $fd
                or print "Reading blob failed\n";
 
-       # page footer
        git_footer_html();
 }
 
+sub git_blame {
+       git_blame_common();
+}
+
+sub git_blame_incremental {
+       git_blame_common('incremental');
+}
+
+sub git_blame_data {
+       git_blame_common('data');
+}
+
 sub git_tags {
        my $head = git_get_head_hash($project);
        git_header_html();
diff --git a/help.c b/help.c
index e8db31f60f010887ccb3a943344da566e0937403..9da97d7462040d3935e7eaa95b1167357b38a943 100644 (file)
--- a/help.c
+++ b/help.c
@@ -297,6 +297,9 @@ static void add_cmd_list(struct cmdnames *cmds, struct cmdnames *old)
        old->names = NULL;
 }
 
+/* An empirically derived magic number */
+#define SIMILAR_ENOUGH(x) ((x) < 6)
+
 const char *help_unknown_cmd(const char *cmd)
 {
        int i, n, best_similarity = 0;
@@ -331,7 +334,7 @@ const char *help_unknown_cmd(const char *cmd)
        n = 1;
        while (n < main_cmds.cnt && best_similarity == main_cmds.names[n]->len)
                ++n;
-       if (autocorrect && n == 1) {
+       if (autocorrect && n == 1 && SIMILAR_ENOUGH(best_similarity)) {
                const char *assumed = main_cmds.names[0]->name;
                main_cmds.names[0] = NULL;
                clean_cmdnames(&main_cmds);
@@ -349,7 +352,7 @@ const char *help_unknown_cmd(const char *cmd)
 
        fprintf(stderr, "git: '%s' is not a git-command. See 'git --help'.\n", cmd);
 
-       if (best_similarity < 6) {
+       if (SIMILAR_ENOUGH(best_similarity)) {
                fprintf(stderr, "\nDid you mean %s?\n",
                        n < 2 ? "this": "one of these");
 
diff --git a/http.c b/http.c
index ed6414a2aaa4e0f6cf7672a089f49060aad62bfb..455732fb243180bb1101a6b5135f332f9f13fd7a 100644 (file)
--- a/http.c
+++ b/http.c
@@ -1244,7 +1244,7 @@ int finish_http_object_request(struct http_object_request *freq)
        process_http_object_request(freq);
 
        if (freq->http_code == 416) {
-               fprintf(stderr, "Warning: requested range invalid; we may already have all the data.\n");
+               warning("requested range invalid; we may already have all the data.");
        } else if (freq->curl_result != CURLE_OK) {
                if (stat(freq->tmpfile, &st) == 0)
                        if (st.st_size == 0)
index a91208f295d00d73b8fc0f5cd6a92ac422d9eeb4..dd4fbd0e6bc22f2e5f5667205b47165f8aebbbd0 100644 (file)
@@ -172,23 +172,6 @@ static int git_merge_trees(int index_only,
        int rc;
        struct tree_desc t[3];
        struct unpack_trees_options opts;
-       struct unpack_trees_error_msgs msgs = {
-               /* would_overwrite */
-               "Your local changes to '%s' would be overwritten by merge.  Aborting.",
-               /* not_uptodate_file */
-               "Your local changes to '%s' would be overwritten by merge.  Aborting.",
-               /* not_uptodate_dir */
-               "Updating '%s' would lose untracked files in it.  Aborting.",
-               /* would_lose_untracked */
-               "Untracked working tree file '%s' would be %s by merge.  Aborting",
-               /* bind_overlap -- will not happen here */
-               NULL,
-       };
-       if (advice_commit_before_merge) {
-               msgs.would_overwrite = msgs.not_uptodate_file =
-                       "Your local changes to '%s' would be overwritten by merge.  Aborting.\n"
-                       "Please, commit your changes or stash them before you can merge.";
-       }
 
        memset(&opts, 0, sizeof(opts));
        if (index_only)
@@ -200,7 +183,7 @@ static int git_merge_trees(int index_only,
        opts.fn = threeway_merge;
        opts.src_index = &the_index;
        opts.dst_index = &the_index;
-       opts.msgs = msgs;
+       opts.msgs = get_porcelain_error_msgs();
 
        init_tree_desc_from_tree(t+0, common);
        init_tree_desc_from_tree(t+1, head);
@@ -1188,6 +1171,28 @@ static int process_entry(struct merge_options *o,
        return clean_merge;
 }
 
+struct unpack_trees_error_msgs get_porcelain_error_msgs(void)
+{
+       struct unpack_trees_error_msgs msgs = {
+               /* would_overwrite */
+               "Your local changes to '%s' would be overwritten by merge.  Aborting.",
+               /* not_uptodate_file */
+               "Your local changes to '%s' would be overwritten by merge.  Aborting.",
+               /* not_uptodate_dir */
+               "Updating '%s' would lose untracked files in it.  Aborting.",
+               /* would_lose_untracked */
+               "Untracked working tree file '%s' would be %s by merge.  Aborting",
+               /* bind_overlap -- will not happen here */
+               NULL,
+       };
+       if (advice_commit_before_merge) {
+               msgs.would_overwrite = msgs.not_uptodate_file =
+                       "Your local changes to '%s' would be overwritten by merge.  Aborting.\n"
+                       "Please, commit your changes or stash them before you can merge.";
+       }
+       return msgs;
+}
+
 int merge_trees(struct merge_options *o,
                struct tree *head,
                struct tree *merge,
index fd138ca14006843a7ce0c38cfde4de580d5ce36e..d8bc7299ee3a70484b2436afd3799fe6f7608422 100644 (file)
@@ -17,6 +17,9 @@ struct merge_options {
        struct string_list current_directory_set;
 };
 
+/* Return a list of user-friendly error messages to be used by merge */
+struct unpack_trees_error_msgs get_porcelain_error_msgs(void);
+
 /* merge_trees() but with recursive ancestor consolidation */
 int merge_recursive(struct merge_options *o,
                    struct commit *h1,
diff --git a/notes.c b/notes.c
index 50a4672d7c68d84024d6ef98e2ca11561d848e27..023adce982c668f39b01652e525b54fd512d5603 100644 (file)
--- a/notes.c
+++ b/notes.c
@@ -331,6 +331,8 @@ static void load_subtree(struct leaf_node *subtree, struct int_node *node,
                        hashcpy(l->key_sha1, commit_sha1);
                        hashcpy(l->val_sha1, entry.sha1);
                        if (len < 20) {
+                               if (!S_ISDIR(entry.mode))
+                                       continue; /* entry cannot be subtree */
                                l->key_sha1[19] = (unsigned char) len;
                                type = PTR_TYPE_SUBTREE;
                        }
index 5661cba5952e2e68dd067a8afebe144bc1ca63af..8f5bd1ab7f119715564fb13cb74de3937d1a3774 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -628,8 +628,8 @@ static void rewrap_message_tail(struct strbuf *sb,
        c->indent2 = new_indent2;
 }
 
-static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
-                               void *context)
+static size_t format_commit_one(struct strbuf *sb, const char *placeholder,
+                               void *context)
 {
        struct format_commit_context *c = context;
        const struct commit *commit = c->commit;
@@ -816,6 +816,44 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
        return 0;       /* unknown placeholder */
 }
 
+static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
+                                void *context)
+{
+       int consumed;
+       size_t orig_len;
+       enum {
+               NO_MAGIC,
+               ADD_LF_BEFORE_NON_EMPTY,
+               DEL_LF_BEFORE_EMPTY,
+       } magic = NO_MAGIC;
+
+       switch (placeholder[0]) {
+       case '-':
+               magic = DEL_LF_BEFORE_EMPTY;
+               break;
+       case '+':
+               magic = ADD_LF_BEFORE_NON_EMPTY;
+               break;
+       default:
+               break;
+       }
+       if (magic != NO_MAGIC)
+               placeholder++;
+
+       orig_len = sb->len;
+       consumed = format_commit_one(sb, placeholder, context);
+       if (magic == NO_MAGIC)
+               return consumed;
+
+       if ((orig_len == sb->len) && magic == DEL_LF_BEFORE_EMPTY) {
+               while (sb->len && sb->buf[sb->len - 1] == '\n')
+                       strbuf_setlen(sb, sb->len - 1);
+       } else if ((orig_len != sb->len) && magic == ADD_LF_BEFORE_NON_EMPTY) {
+               strbuf_insert(sb, orig_len, "\n", 1);
+       }
+       return consumed + 1;
+}
+
 void format_commit_message(const struct commit *commit,
                           const char *format, struct strbuf *sb,
                           const struct pretty_print_context *pretty_ctx)
index 1bbaf1cffba3058667787fc4a267b87e21a6cad3..9033dd3ab938e2ee7b4248ff97645414ee3688db 100644 (file)
@@ -1322,7 +1322,7 @@ int read_index_from(struct index_state *istate, const char *path)
                 * extension name (4-byte) and section length
                 * in 4-byte network byte order.
                 */
-               unsigned long extsize;
+               uint32_t extsize;
                memcpy(&extsize, (char *)mmap + src_offset + 4, 4);
                extsize = ntohl(extsize);
                if (read_index_extension(istate,
index b979a9642b81b456ab92cdf57e8eda944f01f92e..e3afecdb1069a244493e906b5e16a96dc47535d3 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -53,6 +53,11 @@ static struct rewrites rewrites_push;
 #define BUF_SIZE (2048)
 static char buffer[BUF_SIZE];
 
+static int valid_remote(const struct remote *remote)
+{
+       return (!!remote->url) || (!!remote->foreign_vcs);
+}
+
 static const char *alias_url(const char *url, struct rewrites *r)
 {
        int i, j;
@@ -441,6 +446,8 @@ static int handle_config(const char *key, const char *value, void *cb)
        } else if (!strcmp(subkey, ".proxy")) {
                return git_config_string((const char **)&remote->http_proxy,
                                         key, value);
+       } else if (!strcmp(subkey, ".vcs")) {
+               return git_config_string(&remote->foreign_vcs, key, value);
        }
        return 0;
 }
@@ -668,6 +675,16 @@ static struct refspec *parse_push_refspec(int nr_refspec, const char **refspec)
        return parse_refspec_internal(nr_refspec, refspec, 0, 0);
 }
 
+void free_refspec(int nr_refspec, struct refspec *refspec)
+{
+       int i;
+       for (i = 0; i < nr_refspec; i++) {
+               free(refspec[i].src);
+               free(refspec[i].dst);
+       }
+       free(refspec);
+}
+
 static int valid_remote_nick(const char *name)
 {
        if (!name[0] || is_dot_or_dotdot(name))
@@ -690,14 +707,14 @@ struct remote *remote_get(const char *name)
 
        ret = make_remote(name, 0);
        if (valid_remote_nick(name)) {
-               if (!ret->url)
+               if (!valid_remote(ret))
                        read_remotes_file(ret);
-               if (!ret->url)
+               if (!valid_remote(ret))
                        read_branches_file(ret);
        }
-       if (name_given && !ret->url)
+       if (name_given && !valid_remote(ret))
                add_url_alias(ret, name);
-       if (!ret->url)
+       if (!valid_remote(ret))
                return NULL;
        ret->fetch = parse_fetch_refspec(ret->fetch_refspec_nr, ret->fetch_refspec);
        ret->push = parse_push_refspec(ret->push_refspec_nr, ret->push_refspec);
@@ -810,6 +827,23 @@ 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)
+{
+       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 remote_find_tracking(struct remote *remote, struct refspec *refspec)
 {
        int find_src = refspec->src == NULL;
index d0aba81ead1847e43a971362659abf1c1737c12f..8b7ecf9197f2a9210299c9700cc88a92a796f714 100644 (file)
--- a/remote.h
+++ b/remote.h
@@ -11,6 +11,8 @@ struct remote {
        const char *name;
        int origin;
 
+       const char *foreign_vcs;
+
        const char **url;
        int url_nr;
        int url_alloc;
@@ -89,6 +91,11 @@ void ref_remove_duplicates(struct ref *ref_map);
 int valid_fetch_refspec(const char *refspec);
 struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec);
 
+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);
 
index a8a3c3a4bdf5bd9287cb820330e73e8c28b88564..25fa14d93e04531e8d1dde3d9ac8408670adf32a 100644 (file)
@@ -791,7 +791,7 @@ void init_revisions(struct rev_info *revs, const char *prefix)
        revs->ignore_merges = 1;
        revs->simplify_history = 1;
        DIFF_OPT_SET(&revs->pruning, RECURSIVE);
-       DIFF_OPT_SET(&revs->pruning, QUIET);
+       DIFF_OPT_SET(&revs->pruning, QUICK);
        revs->pruning.add_remove = file_add_remove;
        revs->pruning.change = file_change;
        revs->lifo = 1;
diff --git a/setup.c b/setup.c
index f67250b7c1f7b5a62c30de0122c404554357f61b..2cf0f1993718fd737cd87e79d2f4950832c59c9f 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -18,9 +18,12 @@ const char *prefix_path(const char *prefix, int len, const char *path)
        if (normalize_path_copy(sanitized, sanitized))
                goto error_out;
        if (is_absolute_path(orig)) {
+               size_t len, total;
                const char *work_tree = get_git_work_tree();
-               size_t len = strlen(work_tree);
-               size_t total = strlen(sanitized) + 1;
+               if (!work_tree)
+                       goto error_out;
+               len = strlen(work_tree);
+               total = strlen(sanitized) + 1;
                if (strncmp(sanitized, work_tree, len) ||
                    (sanitized[len] != '\0' && sanitized[len] != '/')) {
                error_out:
index 238c2f1c08d0c27b71cdde8c7ddc2b3240ea1fde..ab55eda158bb5a6ecad77302aa2fe17bd6e92be3 100755 (executable)
@@ -259,7 +259,7 @@ test_expect_success 'git repack' 'git repack'
 test_expect_success 'git prune-packed' 'git prune-packed'
 test_expect_success '-> only packed objects' '
        git prune && # Remove conflict marked blobs
-       ! find .git/objects/[0-9a-f][0-9a-f] -type f
+       test $(find .git/objects/[0-9a-f][0-9a-f] -type f -print 2>/dev/null | wc -l) = 0
 '
 
 test_done
index f6a6f839a18de4c3775ea965f164d0d20f2bbe9b..74e6443664010196f2694304179917fdadc53c01 100755 (executable)
@@ -174,4 +174,19 @@ test_expect_success 'git grep' '
        GIT_DIR=../.. GIT_WORK_TREE=.. git grep -l changed | grep dir/tracked)
 '
 
+test_expect_success 'git commit' '
+       (
+               cd repo.git &&
+               GIT_DIR=. GIT_WORK_TREE=work git commit -a -m done
+       )
+'
+
+test_expect_success 'absolute pathspec should fail gracefully' '
+       (
+               cd repo.git || exit 1
+               git config --unset core.worktree
+               test_must_fail git log HEAD -- /home
+       )
+'
+
 test_done
diff --git a/t/t3304-notes-mixed.sh b/t/t3304-notes-mixed.sh
new file mode 100755 (executable)
index 0000000..256687f
--- /dev/null
@@ -0,0 +1,172 @@
+#!/bin/sh
+
+test_description='Test notes trees that also contain non-notes'
+
+. ./test-lib.sh
+
+number_of_commits=100
+
+start_note_commit () {
+       test_tick &&
+       cat <<INPUT_END
+commit refs/notes/commits
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+notes
+COMMIT
+
+from refs/notes/commits^0
+deleteall
+INPUT_END
+
+}
+
+verify_notes () {
+       git log | grep "^    " > output &&
+       i=$number_of_commits &&
+       while [ $i -gt 0 ]; do
+               echo "    commit #$i" &&
+               echo "    note for commit #$i" &&
+               i=$(($i-1));
+       done > expect &&
+       test_cmp expect output
+}
+
+test_expect_success "setup: create a couple of commits" '
+
+       test_tick &&
+       cat <<INPUT_END >input &&
+commit refs/heads/master
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+commit #1
+COMMIT
+
+M 644 inline file
+data <<EOF
+file in commit #1
+EOF
+
+INPUT_END
+
+       test_tick &&
+       cat <<INPUT_END >>input &&
+commit refs/heads/master
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+commit #2
+COMMIT
+
+M 644 inline file
+data <<EOF
+file in commit #2
+EOF
+
+INPUT_END
+       git fast-import --quiet <input
+'
+
+test_expect_success "create a notes tree with both notes and non-notes" '
+
+       commit1=$(git rev-parse refs/heads/master^) &&
+       commit2=$(git rev-parse refs/heads/master) &&
+       test_tick &&
+       cat <<INPUT_END >input &&
+commit refs/notes/commits
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+notes commit #1
+COMMIT
+
+N inline $commit1
+data <<EOF
+note for commit #1
+EOF
+
+N inline $commit2
+data <<EOF
+note for commit #2
+EOF
+
+INPUT_END
+       test_tick &&
+       cat <<INPUT_END >>input &&
+commit refs/notes/commits
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+notes commit #2
+COMMIT
+
+M 644 inline foobar/non-note.txt
+data <<EOF
+A non-note in a notes tree
+EOF
+
+N inline $commit2
+data <<EOF
+edited note for commit #2
+EOF
+
+INPUT_END
+       test_tick &&
+       cat <<INPUT_END >>input &&
+commit refs/notes/commits
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+notes commit #3
+COMMIT
+
+N inline $commit1
+data <<EOF
+edited note for commit #1
+EOF
+
+M 644 inline deadbeef
+data <<EOF
+non-note with SHA1-like name
+EOF
+
+M 644 inline de/adbeef
+data <<EOF
+another non-note with SHA1-like name
+EOF
+
+INPUT_END
+       git fast-import --quiet <input &&
+       git config core.notesRef refs/notes/commits
+'
+
+cat >expect <<EXPECT_END
+    commit #2
+    edited note for commit #2
+    commit #1
+    edited note for commit #1
+EXPECT_END
+
+test_expect_success "verify contents of notes" '
+
+       git log | grep "^    " > actual &&
+       test_cmp expect actual
+'
+
+cat >expect_nn1 <<EXPECT_END
+A non-note in a notes tree
+EXPECT_END
+cat >expect_nn2 <<EXPECT_END
+non-note with SHA1-like name
+EXPECT_END
+cat >expect_nn3 <<EXPECT_END
+another non-note with SHA1-like name
+EXPECT_END
+
+test_expect_success "verify contents of non-notes" '
+
+       git cat-file -p refs/notes/commits:foobar/non-note.txt > actual_nn1 &&
+       test_cmp expect_nn1 actual_nn1 &&
+       git cat-file -p refs/notes/commits:deadbeef > actual_nn2 &&
+       test_cmp expect_nn2 actual_nn2 &&
+       git cat-file -p refs/notes/commits:de/adbeef > actual_nn3 &&
+       test_cmp expect_nn3 actual_nn3
+'
+
+test_done
index d86bc81abfd954194c245e7e180a23f2b9b7c840..b6eba6a83904a00724b7b550a9bc3b1b35825bee 100755 (executable)
@@ -228,6 +228,26 @@ test_expect_success 'add first line works' '
        test_cmp expected diff
 '
 
+cat >expected <<EOF
+diff --git a/non-empty b/non-empty
+deleted file mode 100644
+index d95f3ad..0000000
+--- a/non-empty
++++ /dev/null
+@@ -1 +0,0 @@
+-content
+EOF
+test_expect_success 'deleting a non-empty file' '
+       git reset --hard &&
+       echo content >non-empty &&
+       git add non-empty &&
+       git commit -m non-empty &&
+       rm non-empty &&
+       echo y | git add -p non-empty &&
+       git diff --cached >diff &&
+       test_cmp expected diff
+'
+
 cat >expected <<EOF
 diff --git a/empty b/empty
 deleted file mode 100644
index 7f267f9ed1d888eeb4b3c780a048c7d09e29db3b..3bc1cccf8869aef26e175e207dc2923d3ddb1e65 100755 (executable)
@@ -552,4 +552,9 @@ test_expect_success 'format-patch --numstat should produce a patch' '
        git format-patch --numstat --stdout master..side > output &&
        test 6 = $(grep "^diff --git a/" output | wc -l)'
 
+test_expect_success 'format-patch -- <path>' '
+       git format-patch master..side -- file 2>error &&
+       ! grep "Use .--" error
+'
+
 test_done
index 8dd147d78f6ee6a4be4b1af235ed35af19b273df..90f33423731a84310caf92780482a44f8d2f32d8 100755 (executable)
@@ -93,8 +93,6 @@ git diff > out
 test_expect_success 'another test, without options' 'test_cmp expect out'
 
 cat << EOF > expect
-diff --git a/x b/x
-index d99af23..8b32fb5 100644
 EOF
 git diff -w > out
 test_expect_success 'another test, with -w' 'test_cmp expect out'
@@ -386,6 +384,18 @@ test_expect_success 'checkdiff allows new blank lines' '
        git diff --check
 '
 
+cat <<EOF >expect
+EOF
+test_expect_success 'whitespace-only changes not reported' '
+       git reset --hard &&
+       echo >x "hello world" &&
+       git add x &&
+       git commit -m "hello 1" &&
+       echo >x "hello  world" &&
+       git diff -b >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'combined diff with autocrlf conversion' '
 
        git reset --hard &&
index 3a3663fbcb4795a9e3755fe831c89a7e312f659e..f6d1f1ebab406fcd4f405178ec151149754500b0 100755 (executable)
@@ -20,11 +20,27 @@ test_expect_success setup '
 
 blue_grep='7;34m' ;# ESC [ 7 ; 3 4 m
 
+printf "\033[%s" "$blue_grep" >check-grep
+if (grep "$blue_grep" <check-grep | grep "$blue_grep") >/dev/null 2>&1
+then
+       grep_a=grep
+elif (grep -a "$blue_grep" <check-grep | grep -a "$blue_grep") >/dev/null 2>&1
+then
+       grep_a='grep -a'
+else
+       grep_a=grep ;# expected to fail...
+fi
+rm -f check-grep
+
+prepare_output () {
+       git diff --color >output
+       $grep_a "$blue_grep" output >error
+       $grep_a -v "$blue_grep" output >normal
+}
+
 test_expect_success default '
 
-       git diff --color >output
-       grep "$blue_grep" output >error
-       grep -v "$blue_grep" output >normal
+       prepare_output
 
        grep Eight normal >/dev/null &&
        grep HT error >/dev/null &&
@@ -37,9 +53,7 @@ test_expect_success default '
 test_expect_success 'without -trail' '
 
        git config core.whitespace -trail
-       git diff --color >output
-       grep "$blue_grep" output >error
-       grep -v "$blue_grep" output >normal
+       prepare_output
 
        grep Eight normal >/dev/null &&
        grep HT error >/dev/null &&
@@ -53,9 +67,7 @@ test_expect_success 'without -trail (attribute)' '
 
        git config --unset core.whitespace
        echo "F whitespace=-trail" >.gitattributes
-       git diff --color >output
-       grep "$blue_grep" output >error
-       grep -v "$blue_grep" output >normal
+       prepare_output
 
        grep Eight normal >/dev/null &&
        grep HT error >/dev/null &&
@@ -69,9 +81,7 @@ test_expect_success 'without -space' '
 
        rm -f .gitattributes
        git config core.whitespace -space
-       git diff --color >output
-       grep "$blue_grep" output >error
-       grep -v "$blue_grep" output >normal
+       prepare_output
 
        grep Eight normal >/dev/null &&
        grep HT normal >/dev/null &&
@@ -85,9 +95,7 @@ test_expect_success 'without -space (attribute)' '
 
        git config --unset core.whitespace
        echo "F whitespace=-space" >.gitattributes
-       git diff --color >output
-       grep "$blue_grep" output >error
-       grep -v "$blue_grep" output >normal
+       prepare_output
 
        grep Eight normal >/dev/null &&
        grep HT normal >/dev/null &&
@@ -101,9 +109,7 @@ test_expect_success 'with indent-non-tab only' '
 
        rm -f .gitattributes
        git config core.whitespace indent,-trailing,-space
-       git diff --color >output
-       grep "$blue_grep" output >error
-       grep -v "$blue_grep" output >normal
+       prepare_output
 
        grep Eight error >/dev/null &&
        grep HT normal >/dev/null &&
@@ -117,9 +123,7 @@ test_expect_success 'with indent-non-tab only (attribute)' '
 
        git config --unset core.whitespace
        echo "F whitespace=indent,-trailing,-space" >.gitattributes
-       git diff --color >output
-       grep "$blue_grep" output >error
-       grep -v "$blue_grep" output >normal
+       prepare_output
 
        grep Eight error >/dev/null &&
        grep HT normal >/dev/null &&
@@ -133,9 +137,7 @@ test_expect_success 'with cr-at-eol' '
 
        rm -f .gitattributes
        git config core.whitespace cr-at-eol
-       git diff --color >output
-       grep "$blue_grep" output >error
-       grep -v "$blue_grep" output >normal
+       prepare_output
 
        grep Eight normal >/dev/null &&
        grep HT error >/dev/null &&
@@ -149,9 +151,7 @@ test_expect_success 'with cr-at-eol (attribute)' '
 
        git config --unset core.whitespace
        echo "F whitespace=trailing,cr-at-eol" >.gitattributes
-       git diff --color >output
-       grep "$blue_grep" output >error
-       grep -v "$blue_grep" output >normal
+       prepare_output
 
        grep Eight normal >/dev/null &&
        grep HT error >/dev/null &&
@@ -195,7 +195,7 @@ test_expect_success 'color new trailing blank lines' '
        git add x &&
        { echo a; echo; echo; echo; echo c; echo; echo; echo; echo; } >x &&
        git diff --color x >output &&
-       cnt=$(grep "${blue_grep}" output | wc -l) &&
+       cnt=$($grep_a "${blue_grep}" output | wc -l) &&
        test $cnt = 2
 '
 
index b61e5169f4e9e8d9f87b9ea16e71dfcf1fb9f340..5ade44c043ca6577b2e331b152515359128dbd32 100755 (executable)
@@ -66,4 +66,21 @@ test_expect_success 'extra character after attribute' '
        invalid_color "dimX"
 '
 
+test_expect_success 'unknown color slots are ignored (diff)' '
+       git config --unset diff.color.new
+       git config color.diff.nosuchslotwilleverbedefined white &&
+       git diff --color
+'
+
+test_expect_success 'unknown color slots are ignored (branch)' '
+       git config color.branch.nosuchslotwilleverbedefined white &&
+       git branch -a
+'
+
+test_expect_success 'unknown color slots are ignored (status)' '
+       git config color.status.nosuchslotwilleverbedefined white || exit
+       git status
+       case $? in 0|1) : ok ;; *) false ;; esac
+'
+
 test_done
index 21db6e95c4dd7ccf2f25d887fc2c42f3adcd4124..2e2e103b31332ea2f74de5d5e6e49c00b13dfa8a 100755 (executable)
@@ -8,21 +8,13 @@ test_expect_success setup '
 
        git config diff.color.old red
        git config diff.color.new green
+       git config diff.color.func magenta
 
 '
 
-decrypt_color () {
-       sed \
-               -e 's/.\[1m/<WHITE>/g' \
-               -e 's/.\[31m/<RED>/g' \
-               -e 's/.\[32m/<GREEN>/g' \
-               -e 's/.\[36m/<BROWN>/g' \
-               -e 's/.\[m/<RESET>/g'
-}
-
 word_diff () {
        test_must_fail git diff --no-index "$@" pre post > output &&
-       decrypt_color < output > output.decrypted &&
+       test_decode_color <output >output.decrypted &&
        test_cmp expect output.decrypted
 }
 
@@ -47,9 +39,9 @@ cat > expect <<\EOF
 <WHITE>index 330b04f..5ed8eff 100644<RESET>
 <WHITE>--- a/pre<RESET>
 <WHITE>+++ b/post<RESET>
-<BROWN>@@ -1,3 +1,7 @@<RESET>
+<CYAN>@@ -1,3 +1,7 @@<RESET>
 <RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET>
-<RESET>
+
 a = b + c<RESET>
 
 <GREEN>aa = a<RESET>
@@ -68,9 +60,9 @@ cat > expect <<\EOF
 <WHITE>index 330b04f..5ed8eff 100644<RESET>
 <WHITE>--- a/pre<RESET>
 <WHITE>+++ b/post<RESET>
-<BROWN>@@ -1 +1 @@<RESET>
+<CYAN>@@ -1 +1 @@<RESET>
 <RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET>
-<BROWN>@@ -3,0 +4,4 @@ a = b + c<RESET>
+<CYAN>@@ -3,0 +4,4 @@<RESET> <RESET><MAGENTA>a = b + c<RESET>
 
 <GREEN>aa = a<RESET>
 
@@ -88,9 +80,9 @@ cat > expect <<\EOF
 <WHITE>index 330b04f..5ed8eff 100644<RESET>
 <WHITE>--- a/pre<RESET>
 <WHITE>+++ b/post<RESET>
-<BROWN>@@ -1,3 +1,7 @@<RESET>
+<CYAN>@@ -1,3 +1,7 @@<RESET>
 h(4),<GREEN>hh<RESET>[44]
-<RESET>
+
 a = b + c<RESET>
 
 <GREEN>aa = a<RESET>
@@ -124,9 +116,9 @@ cat > expect <<\EOF
 <WHITE>index 330b04f..5ed8eff 100644<RESET>
 <WHITE>--- a/pre<RESET>
 <WHITE>+++ b/post<RESET>
-<BROWN>@@ -1,3 +1,7 @@<RESET>
+<CYAN>@@ -1,3 +1,7 @@<RESET>
 h(4)<GREEN>,hh[44]<RESET>
-<RESET>
+
 a = b + c<RESET>
 
 <GREEN>aa = a<RESET>
@@ -166,9 +158,9 @@ cat > expect <<\EOF
 <WHITE>index 330b04f..5ed8eff 100644<RESET>
 <WHITE>--- a/pre<RESET>
 <WHITE>+++ b/post<RESET>
-<BROWN>@@ -1,3 +1,7 @@<RESET>
+<CYAN>@@ -1,3 +1,7 @@<RESET>
 h(4),<GREEN>hh[44<RESET>]
-<RESET>
+
 a = b + c<RESET>
 
 <GREEN>aa = a<RESET>
@@ -188,7 +180,7 @@ cat > expect <<\EOF
 <WHITE>index c29453b..be22f37 100644<RESET>
 <WHITE>--- a/pre<RESET>
 <WHITE>+++ b/post<RESET>
-<BROWN>@@ -1 +1 @@<RESET>
+<CYAN>@@ -1 +1 @@<RESET>
 aaa (aaa) <GREEN>aaa<RESET>
 EOF
 
@@ -207,7 +199,7 @@ cat > expect <<\EOF
 <WHITE>index 289cb9d..2d06f37 100644<RESET>
 <WHITE>--- a/pre<RESET>
 <WHITE>+++ b/post<RESET>
-<BROWN>@@ -1 +1 @@<RESET>
+<CYAN>@@ -1 +1 @@<RESET>
 (<RED>:<RESET>
 EOF
 
diff --git a/t/t4040-whitespace-status.sh b/t/t4040-whitespace-status.sh
new file mode 100755 (executable)
index 0000000..a30b03b
--- /dev/null
@@ -0,0 +1,63 @@
+#!/bin/sh
+
+test_description='diff --exit-code with whitespace'
+. ./test-lib.sh
+
+test_expect_success setup '
+       mkdir a b &&
+       echo >c &&
+       echo >a/d &&
+       echo >b/e &&
+       git add . &&
+       test_tick &&
+       git commit -m initial &&
+       echo " " >a/d &&
+       test_tick &&
+       git commit -a -m second &&
+       echo "  " >a/d &&
+       echo " " >b/e &&
+       git add a/d
+'
+
+test_expect_success 'diff-tree --exit-code' '
+       test_must_fail git diff --exit-code HEAD^ HEAD &&
+       test_must_fail git diff-tree --exit-code HEAD^ HEAD
+'
+
+test_expect_success 'diff-tree -b --exit-code' '
+       git diff -b --exit-code HEAD^ HEAD &&
+       git diff-tree -b -p --exit-code HEAD^ HEAD &&
+       git diff-tree -b --exit-code HEAD^ HEAD
+'
+
+test_expect_success 'diff-index --cached --exit-code' '
+       test_must_fail git diff --cached --exit-code HEAD &&
+       test_must_fail git diff-index --cached --exit-code HEAD
+'
+
+test_expect_success 'diff-index -b -p --cached --exit-code' '
+       git diff -b --cached --exit-code HEAD &&
+       git diff-index -b -p --cached --exit-code HEAD
+'
+
+test_expect_success 'diff-index --exit-code' '
+       test_must_fail git diff --exit-code HEAD &&
+       test_must_fail git diff-index --exit-code HEAD
+'
+
+test_expect_success 'diff-index -b -p --exit-code' '
+       git diff -b --exit-code HEAD &&
+       git diff-index -b -p --exit-code HEAD
+'
+
+test_expect_success 'diff-files --exit-code' '
+       test_must_fail git diff --exit-code &&
+       test_must_fail git diff-files --exit-code
+'
+
+test_expect_success 'diff-files -b -p --exit-code' '
+       git diff -b --exit-code &&
+       git diff-files -b -p --exit-code
+'
+
+test_done
index 405b97119175a1c0fa75a9db30c6b1ab076cc44e..a01e55bf6b96246c33332e5112bcb3d6583402ac 100755 (executable)
@@ -52,4 +52,32 @@ GIT_DIR=non-existing git shortlog -w < log > out
 
 test_expect_success 'shortlog from non-git directory' 'test_cmp expect out'
 
+iconvfromutf8toiso88591() {
+       printf "%s" "$*" | iconv -f UTF-8 -t ISO8859-1
+}
+
+DSCHO="Jöhännës \"Dschö\" Schindëlin"
+DSCHOE="$DSCHO <Johannes.Schindelin@gmx.de>"
+MSG1="set a1 to 2 and some non-ASCII chars: Äßø"
+MSG2="set a1 to 3 and some non-ASCII chars: áæï"
+cat > expect << EOF
+$DSCHO (2):
+      $MSG1
+      $MSG2
+
+EOF
+
+test_expect_success 'shortlog encoding' '
+       git reset --hard "$commit" &&
+       git config --unset i18n.commitencoding &&
+       echo 2 > a1 &&
+       git commit --quiet -m "$MSG1" --author="$DSCHOE" a1 &&
+       git config i18n.commitencoding "ISO8859-1" &&
+       echo 3 > a1 &&
+       git commit --quiet -m "$(iconvfromutf8toiso88591 "$MSG2")" \
+               --author="$(iconvfromutf8toiso88591 "$DSCHOE")" a1 &&
+       git config --unset i18n.commitencoding &&
+       git shortlog HEAD~2.. > out &&
+test_cmp expect out'
+
 test_done
index f2d5581b12f7d70c9f346da75dced81e12bd4c7f..c718253673ec8b3baf635c4dc0b05fe127c6f4cd 100755 (executable)
@@ -32,7 +32,7 @@ test_expect_success setup '
        done &&
        git update-ref HEAD "$commit" &&
        git clone ./. victim &&
-       ( cd victim && git log ) &&
+       ( cd victim && git config receive.denyCurrentBranch warn && git log ) &&
        git update-ref HEAD "$zero" &&
        parent=$zero &&
        i=0 &&
@@ -129,6 +129,7 @@ rewound_push_setup() {
            cd parent &&
            git init &&
            echo one >file && git add file && git commit -m one &&
+           git config receive.denyCurrentBranch warn &&
            echo two >file && git commit -a -m two
        ) &&
        git clone parent child &&
@@ -190,16 +191,11 @@ test_expect_success 'pushing wildcard refspecs respects forcing' '
        test "$parent_head" = "$child_head"
 '
 
-test_expect_success 'warn pushing to delete current branch' '
+test_expect_success 'deny pushing to delete current branch' '
        rewound_push_setup &&
        (
            cd child &&
-           git send-pack ../parent :refs/heads/master 2>errs
-       ) &&
-       grep "warning: to refuse deleting" child/errs &&
-       (
-               cd parent &&
-               test_must_fail git rev-parse --verify master
+           test_must_fail git send-pack ../parent :refs/heads/master 2>errs
        )
 '
 
index 64f66c94f36538b1c7d20045fc4233aa0b9d9a0d..325714e5299a5b59157c3741e7fa0d98d70b9990 100755 (executable)
@@ -18,6 +18,7 @@ test_expect_success setup '
        git update-ref refs/heads/master $commit0 &&
        git update-ref refs/heads/tofail $commit1 &&
        git clone ./. victim &&
+       GIT_DIR=victim/.git git config receive.denyCurrentBranch warn &&
        GIT_DIR=victim/.git git update-ref refs/heads/tofail $commit1 &&
        git update-ref refs/heads/master $commit1 &&
        git update-ref refs/heads/tofail $commit0
index 5858b868ed6ff05d458996cf8c359a7148591582..d05a9138b46eec072750142f912a41caa3e23d83 100755 (executable)
@@ -7,19 +7,19 @@ test_description='Test the post-checkout hook.'
 . ./test-lib.sh
 
 test_expect_success setup '
-        echo Data for commit0. >a &&
-        echo Data for commit0. >b &&
-        git update-index --add a &&
-        git update-index --add b &&
-        tree0=$(git write-tree) &&
-        commit0=$(echo setup | git commit-tree $tree0) &&
-        git update-ref refs/heads/master $commit0 &&
-        git clone ./. clone1 &&
-        git clone ./. clone2 &&
-        GIT_DIR=clone2/.git git branch -a new2 &&
-        echo Data for commit1. >clone2/b &&
-        GIT_DIR=clone2/.git git add clone2/b &&
-        GIT_DIR=clone2/.git git commit -m new2
+       echo Data for commit0. >a &&
+       echo Data for commit0. >b &&
+       git update-index --add a &&
+       git update-index --add b &&
+       tree0=$(git write-tree) &&
+       commit0=$(echo setup | git commit-tree $tree0) &&
+       git update-ref refs/heads/master $commit0 &&
+       git clone ./. clone1 &&
+       git clone ./. clone2 &&
+       GIT_DIR=clone2/.git git branch new2 &&
+       echo Data for commit1. >clone2/b &&
+       GIT_DIR=clone2/.git git add clone2/b &&
+       GIT_DIR=clone2/.git git commit -m new2
 '
 
 for clone in 1 2; do
index cb9aacc7bc62e2ecfdca0dc7f6071e5330fe09d0..4bda18a662da5a0c36ee007c37d9ded6be3b1832 100755 (executable)
@@ -8,6 +8,7 @@ test_expect_success setup '
 
        >file1 && git add file1 && test_tick &&
        git commit -m Initial &&
+       git config receive.denyCurrentBranch warn &&
 
        mkdir another && (
                cd another &&
diff --git a/t/t5501-post-upload-pack.sh b/t/t5501-post-upload-pack.sh
deleted file mode 100755 (executable)
index d89fb51..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/bin/sh
-
-test_description='post upload-hook'
-
-. ./test-lib.sh
-
-LOGFILE=".git/post-upload-pack-log"
-
-test_expect_success setup '
-       test_commit A &&
-       test_commit B &&
-       git reset --hard A &&
-       test_commit C &&
-       git branch prev B &&
-       mkdir -p .git/hooks &&
-       {
-               echo "#!$SHELL_PATH" &&
-               echo "cat >post-upload-pack-log"
-       } >".git/hooks/post-upload-pack" &&
-       chmod +x .git/hooks/post-upload-pack
-'
-
-test_expect_success initial '
-       rm -fr sub &&
-       git init sub &&
-       (
-               cd sub &&
-               git fetch --no-tags .. prev
-       ) &&
-       want=$(sed -n "s/^want //p" "$LOGFILE") &&
-       test "$want" = "$(git rev-parse --verify B)" &&
-       ! grep "^have " "$LOGFILE" &&
-       kind=$(sed -n "s/^kind //p" "$LOGFILE") &&
-       test "$kind" = fetch
-'
-
-test_expect_success second '
-       rm -fr sub &&
-       git init sub &&
-       (
-               cd sub &&
-               git fetch --no-tags .. prev:refs/remotes/prev &&
-               git fetch --no-tags .. master
-       ) &&
-       want=$(sed -n "s/^want //p" "$LOGFILE") &&
-       test "$want" = "$(git rev-parse --verify C)" &&
-       have=$(sed -n "s/^have //p" "$LOGFILE") &&
-       test "$have" = "$(git rev-parse --verify B)" &&
-       kind=$(sed -n "s/^kind //p" "$LOGFILE") &&
-       test "$kind" = fetch
-'
-
-test_expect_success all '
-       rm -fr sub &&
-       HERE=$(pwd) &&
-       git init sub &&
-       (
-               cd sub &&
-               git clone "file://$HERE/.git" new
-       ) &&
-       sed -n "s/^want //p" "$LOGFILE" | sort >actual &&
-       git rev-parse A B C | sort >expect &&
-       test_cmp expect actual &&
-       ! grep "^have " "$LOGFILE" &&
-       kind=$(sed -n "s/^kind //p" "$LOGFILE") &&
-       test "$kind" = clone
-'
-
-test_done
index 6889a53cf9bdea0aff88789f954ddf31d1eec010..516127b5390bafbd725564e91fb24ead224cf3a7 100755 (executable)
@@ -12,6 +12,7 @@ mk_empty () {
        (
                cd testrepo &&
                git init &&
+               git config receive.denyCurrentBranch warn &&
                mv .git/hooks .git/hooks-disabled
        )
 }
index ea49dedbf8867694d83cd550c8212ff107361920..e2ad2605084194868fc23e23fec1636af622b47f 100755 (executable)
@@ -19,7 +19,8 @@ mk_repo_pair () {
        mkdir mirror &&
        (
                cd mirror &&
-               git init
+               git init &&
+               git config receive.denyCurrentBranch warn
        ) &&
        mkdir master &&
        (
index 86bbd7d024ff6b1ee775ec737c550d54f9371ad7..7206817ca1c7a450b47f9a1d7d8a3af53452dac6 100755 (executable)
@@ -20,13 +20,19 @@ fi
 #
 # The working directory is subdir-link.
 
-mkdir subdir
-echo file >subdir/file
-git add subdir/file
-git commit -q -m file
-git clone -q . clone-repo
-ln -s clone-repo/subdir/ subdir-link
-
+test_expect_success setup '
+       mkdir subdir &&
+       echo file >subdir/file &&
+       git add subdir/file &&
+       git commit -q -m file &&
+       git clone -q . clone-repo &&
+       ln -s clone-repo/subdir/ subdir-link &&
+       (
+               cd clone-repo &&
+               git config receive.denyCurrentBranch warn
+       ) &&
+       git config receive.denyCurrentBranch warn
+'
 
 # Demonstrate that things work if we just avoid the symlink
 #
index 19b5c0d552fa8b4665b5e396833264e258365b28..8b4c356cd21846025d84a434077bfdc8ee2bab57 100755 (executable)
@@ -119,7 +119,9 @@ test_expect_success 'bundle clone with nonexistent HEAD' '
 test_expect_success 'clone empty repository' '
        cd "$D" &&
        mkdir empty &&
-       (cd empty && git init) &&
+       (cd empty &&
+        git init &&
+        git config receive.denyCurrentBranch warn) &&
        git clone empty empty-clone &&
        test_tick &&
        (cd empty-clone
index 7f61ab0e522fd28c5e10594f03ed9076f9f5ce42..571931588eda5799efbf2b0789abd10b8770a0a7 100755 (executable)
@@ -162,6 +162,28 @@ test_expect_success 'empty email' '
        }
 '
 
+test_expect_success 'del LF before empty (1)' '
+       git show -s --pretty=format:"%s%n%-b%nThanks%n" HEAD^^ >actual &&
+       test $(wc -l <actual) = 2
+'
+
+test_expect_success 'del LF before empty (2)' '
+       git show -s --pretty=format:"%s%n%-b%nThanks%n" HEAD >actual &&
+       test $(wc -l <actual) = 6 &&
+       grep "^$" actual
+'
+
+test_expect_success 'add LF before non-empty (1)' '
+       git show -s --pretty=format:"%s%+b%nThanks%n" HEAD^^ >actual &&
+       test $(wc -l <actual) = 2
+'
+
+test_expect_success 'add LF before non-empty (2)' '
+       git show -s --pretty=format:"%s%+b%nThanks%n" HEAD >actual &&
+       test $(wc -l <actual) = 6 &&
+       grep "^$" actual
+'
+
 test_expect_success '"%h %gD: %gs" is same as git-reflog' '
        git reflog >expect &&
        git log -g --format="%h %gD: %gs" >actual &&
index def397c53a76dead449710eaca6333c2e1fb36aa..c51865fdbc0a6fd98cca4a4accd35b302e5fd739 100755 (executable)
@@ -423,7 +423,7 @@ test_expect_success 'skipped merge base when good and bad are siblings' '
        grep "merge base must be tested" my_bisect_log.txt &&
        grep $HASH4 my_bisect_log.txt &&
        git bisect skip > my_bisect_log.txt 2>&1 &&
-       grep "Warning" my_bisect_log.txt &&
+       grep "warning" my_bisect_log.txt &&
        grep $SIDE_HASH6 my_bisect_log.txt &&
        git bisect reset
 '
index 00e1de9627e8e20f4d28e3122502a494d21899e5..664b0f805288863955d8b6389c95f0f8f542f136 100755 (executable)
@@ -69,7 +69,7 @@ test_expect_success 'status' '
                cd test &&
                git checkout b1 >/dev/null &&
                # reports nothing to commit
-               test_must_fail git status
+               test_must_fail git commit --dry-run
        ) >actual &&
        grep "have 1 and 1 different" actual
 '
index 1044aa65494f6c908aa4d70c1590a5577b288f50..fcac4725982096af98414c5d40df2d4c14be9454 100755 (executable)
@@ -31,8 +31,7 @@ test_expect_success 'Report new path with conflict' '
 cat >expect <<EOF
 # On branch side
 # Unmerged paths:
-#   (use "git reset HEAD <file>..." to unstage)
-#   (use "git add <file>..." to mark resolution)
+#   (use "git add/rm <file>..." as appropriate to mark resolution)
 #
 #      deleted by us:      foo
 #
@@ -50,9 +49,11 @@ test_expect_success 'M/D conflict does not segfault' '
                git rm foo &&
                git commit -m delete &&
                test_must_fail git merge master &&
-               test_must_fail git status > ../actual
-       ) &&
-       test_cmp expect actual
+               test_must_fail git commit --dry-run >../actual &&
+               test_cmp ../expect ../actual &&
+               git status >../actual &&
+               test_cmp ../expect ../actual
+       )
 '
 
 test_done
index e85ff02c3e636567a80586e92fdba1c5382fc995..b8cf2603a195af406d3606712e45fd1195c1588f 100755 (executable)
@@ -139,19 +139,19 @@ test_expect_success \
 test_expect_success \
        'resetting to HEAD with no changes should succeed and do nothing' '
        git reset --hard &&
-               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
        git reset --hard HEAD &&
-               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
        git reset --soft &&
-               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
        git reset --soft HEAD &&
-               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
        git reset --mixed &&
-               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
        git reset --mixed HEAD &&
-               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
        git reset &&
-               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
        git reset HEAD &&
                check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
 '
index 42bf518c68e6ef07c8be1af714723b2f900a573c..68041df5f49e046c2b7ccd1f3a1c71071498da39 100755 (executable)
@@ -11,16 +11,42 @@ test_expect_success 'setup non-bare' '
        git commit -a -m two
 '
 
+test_expect_success 'hard reset requires a worktree' '
+       (cd .git &&
+        test_must_fail git reset --hard)
+'
+
+test_expect_success 'merge reset requires a worktree' '
+       (cd .git &&
+        test_must_fail git reset --merge)
+'
+
+test_expect_success 'mixed reset is ok' '
+       (cd .git && git reset)
+'
+
+test_expect_success 'soft reset is ok' '
+       (cd .git && git reset --soft)
+'
+
 test_expect_success 'setup bare' '
        git clone --bare . bare.git &&
        cd bare.git
 '
 
-test_expect_success 'hard reset is not allowed' '
-       test_must_fail  git reset --hard HEAD^
+test_expect_success 'hard reset is not allowed in bare' '
+       test_must_fail git reset --hard HEAD^
+'
+
+test_expect_success 'merge reset is not allowed in bare' '
+       test_must_fail git reset --merge HEAD^
+'
+
+test_expect_success 'mixed reset is not allowed in bare' '
+       test_must_fail git reset --mixed HEAD^
 '
 
-test_expect_success 'soft reset is allowed' '
+test_expect_success 'soft reset is allowed in bare' '
        git reset --soft HEAD^ &&
        test "`git show --pretty=format:%s | head -n 1`" = "one"
 '
index d9a08aac56f8edf002a126cba83172abf5015034..3ca17abad1847704f222f1cfe744463debfc96ff 100755 (executable)
@@ -19,8 +19,8 @@ test_expect_success 'status clean' '
        git status |
        grep "nothing to commit"
 '
-test_expect_success 'status -a clean' '
-       git status -a |
+test_expect_success 'commit --dry-run -a clean' '
+       git commit --dry-run -a |
        grep "nothing to commit"
 '
 test_expect_success 'rm submodule contents' '
@@ -31,7 +31,7 @@ test_expect_success 'status clean (empty submodule dir)' '
        grep "nothing to commit"
 '
 test_expect_success 'status -a clean (empty submodule dir)' '
-       git status -a |
+       git commit --dry-run -a |
        grep "nothing to commit"
 '
 
index 93f875f50054225e0879843b6e29ac057ba6c020..cf67fe3a4a07181640e2c5074e6b86188efe1e74 100755 (executable)
@@ -8,26 +8,26 @@ test_description='git status'
 . ./test-lib.sh
 
 test_expect_success 'setup' '
-       : > tracked &&
-       : > modified &&
+       : >tracked &&
+       : >modified &&
        mkdir dir1 &&
-       : > dir1/tracked &&
-       : > dir1/modified &&
+       : >dir1/tracked &&
+       : >dir1/modified &&
        mkdir dir2 &&
-       : > dir1/tracked &&
-       : > dir1/modified &&
+       : >dir1/tracked &&
+       : >dir1/modified &&
        git add . &&
 
        git status >output &&
 
        test_tick &&
        git commit -m initial &&
-       : > untracked &&
-       : > dir1/untracked &&
-       : > dir2/untracked &&
-       echo 1 > dir1/modified &&
-       echo 2 > dir2/modified &&
-       echo 3 > dir2/added &&
+       : >untracked &&
+       : >dir1/untracked &&
+       : >dir2/untracked &&
+       echo 1 >dir1/modified &&
+       echo 2 >dir2/modified &&
+       echo 3 >dir2/added &&
        git add dir2/added
 '
 
@@ -37,7 +37,7 @@ test_expect_success 'status (1)' '
 
 '
 
-cat > expect << \EOF
+cat >expect <<\EOF
 # On branch master
 # Changes to be committed:
 #   (use "git reset HEAD <file>..." to unstage)
@@ -63,7 +63,25 @@ EOF
 
 test_expect_success 'status (2)' '
 
-       git status > output &&
+       git status >output &&
+       test_cmp expect output
+
+'
+
+cat >expect <<\EOF
+ M dir1/modified
+A  dir2/added
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? expect
+?? output
+?? untracked
+EOF
+
+test_expect_success 'status -s (2)' '
+
+       git status -s >output &&
        test_cmp expect output
 
 '
@@ -85,8 +103,8 @@ cat >expect <<EOF
 EOF
 test_expect_success 'status -uno' '
        mkdir dir3 &&
-       : > dir3/untracked1 &&
-       : > dir3/untracked2 &&
+       : >dir3/untracked1 &&
+       : >dir3/untracked2 &&
        git status -uno >output &&
        test_cmp expect output
 '
@@ -97,6 +115,22 @@ test_expect_success 'status (status.showUntrackedFiles no)' '
        test_cmp expect output
 '
 
+cat >expect << EOF
+ M dir1/modified
+A  dir2/added
+EOF
+test_expect_success 'status -s -uno' '
+       git config --unset status.showuntrackedfiles
+       git status -s -uno >output &&
+       test_cmp expect output
+'
+
+test_expect_success 'status -s (status.showUntrackedFiles no)' '
+       git config status.showuntrackedfiles no
+       git status -s >output &&
+       test_cmp expect output
+'
+
 cat >expect <<EOF
 # On branch master
 # Changes to be committed:
@@ -132,6 +166,29 @@ test_expect_success 'status (status.showUntrackedFiles normal)' '
        test_cmp expect output
 '
 
+cat >expect <<EOF
+ M dir1/modified
+A  dir2/added
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? dir3/
+?? expect
+?? output
+?? untracked
+EOF
+test_expect_success 'status -s -unormal' '
+       git config --unset status.showuntrackedfiles
+       git status -s -unormal >output &&
+       test_cmp expect output
+'
+
+test_expect_success 'status -s (status.showUntrackedFiles normal)' '
+       git config status.showuntrackedfiles normal
+       git status -s >output &&
+       test_cmp expect output
+'
+
 cat >expect <<EOF
 # On branch master
 # Changes to be committed:
@@ -169,7 +226,30 @@ test_expect_success 'status (status.showUntrackedFiles all)' '
        test_cmp expect output
 '
 
-cat > expect << \EOF
+cat >expect <<EOF
+ M dir1/modified
+A  dir2/added
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? expect
+?? output
+?? untracked
+EOF
+test_expect_success 'status -s -uall' '
+       git config --unset status.showuntrackedfiles
+       git status -s -uall >output &&
+       test_cmp expect output
+'
+test_expect_success 'status -s (status.showUntrackedFiles all)' '
+       git config status.showuntrackedfiles all
+       git status -s >output &&
+       rm -rf dir3 &&
+       git config --unset status.showuntrackedfiles &&
+       test_cmp expect output
+'
+
+cat >expect <<\EOF
 # On branch master
 # Changes to be committed:
 #   (use "git reset HEAD <file>..." to unstage)
@@ -195,12 +275,156 @@ EOF
 
 test_expect_success 'status with relative paths' '
 
-       (cd dir1 && git status) > output &&
+       (cd dir1 && git status) >output &&
+       test_cmp expect output
+
+'
+
+cat >expect <<\EOF
+ M modified
+A  ../dir2/added
+?? untracked
+?? ../dir2/modified
+?? ../dir2/untracked
+?? ../expect
+?? ../output
+?? ../untracked
+EOF
+test_expect_success 'status -s with relative paths' '
+
+       (cd dir1 && git status -s) >output &&
        test_cmp expect output
 
 '
 
-cat > expect << \EOF
+cat >expect <<\EOF
+ M dir1/modified
+A  dir2/added
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? expect
+?? output
+?? untracked
+EOF
+
+test_expect_success 'status --porcelain ignores relative paths setting' '
+
+       (cd dir1 && git status --porcelain) >output &&
+       test_cmp expect output
+
+'
+
+test_expect_success 'setup unique colors' '
+
+       git config status.color.untracked blue
+
+'
+
+cat >expect <<\EOF
+# On branch master
+# Changes to be committed:
+#   (use "git reset HEAD <file>..." to unstage)
+#
+#      <GREEN>new file:   dir2/added<RESET>
+#
+# Changed but not updated:
+#   (use "git add <file>..." to update what will be committed)
+#   (use "git checkout -- <file>..." to discard changes in working directory)
+#
+#      <RED>modified:   dir1/modified<RESET>
+#
+# Untracked files:
+#   (use "git add <file>..." to include in what will be committed)
+#
+#      <BLUE>dir1/untracked<RESET>
+#      <BLUE>dir2/modified<RESET>
+#      <BLUE>dir2/untracked<RESET>
+#      <BLUE>expect<RESET>
+#      <BLUE>output<RESET>
+#      <BLUE>untracked<RESET>
+EOF
+
+test_expect_success 'status with color.ui' '
+
+       git config color.ui always &&
+       git status | test_decode_color >output &&
+       test_cmp expect output
+
+'
+
+test_expect_success 'status with color.status' '
+
+       git config --unset color.ui &&
+       git config color.status always &&
+       git status | test_decode_color >output &&
+       test_cmp expect output
+
+'
+
+cat >expect <<\EOF
+ <RED>M<RESET> dir1/modified
+<GREEN>A<RESET>  dir2/added
+<BLUE>??<RESET> dir1/untracked
+<BLUE>??<RESET> dir2/modified
+<BLUE>??<RESET> dir2/untracked
+<BLUE>??<RESET> expect
+<BLUE>??<RESET> output
+<BLUE>??<RESET> untracked
+EOF
+
+test_expect_success 'status -s with color.ui' '
+
+       git config --unset color.status &&
+       git config color.ui always &&
+       git status -s | test_decode_color >output &&
+       test_cmp expect output
+
+'
+
+test_expect_success 'status -s with color.status' '
+
+       git config --unset color.ui &&
+       git config color.status always &&
+       git status -s | test_decode_color >output &&
+       test_cmp expect output
+
+'
+
+cat >expect <<\EOF
+ M dir1/modified
+A  dir2/added
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? expect
+?? output
+?? untracked
+EOF
+
+test_expect_success 'status --porcelain ignores color.ui' '
+
+       git config --unset color.status &&
+       git config color.ui always &&
+       git status --porcelain | test_decode_color >output &&
+       test_cmp expect output
+
+'
+
+test_expect_success 'status --porcelain ignores color.status' '
+
+       git config --unset color.ui &&
+       git config color.status always &&
+       git status --porcelain | test_decode_color >output &&
+       test_cmp expect output
+
+'
+
+# recover unconditionally from color tests
+git config --unset color.status
+git config --unset color.ui
+
+cat >expect <<\EOF
 # On branch master
 # Changes to be committed:
 #   (use "git reset HEAD <file>..." to unstage)
@@ -224,10 +448,29 @@ cat > expect << \EOF
 #      untracked
 EOF
 
+
 test_expect_success 'status without relative paths' '
 
        git config status.relativePaths false
-       (cd dir1 && git status) > output &&
+       (cd dir1 && git status) >output &&
+       test_cmp expect output
+
+'
+
+cat >expect <<\EOF
+ M dir1/modified
+A  dir2/added
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? expect
+?? output
+?? untracked
+EOF
+
+test_expect_success 'status -s without relative paths' '
+
+       (cd dir1 && git status -s) >output &&
        test_cmp expect output
 
 '
@@ -248,8 +491,8 @@ cat <<EOF >expect
 #      output
 #      untracked
 EOF
-test_expect_success 'status of partial commit excluding new file in index' '
-       git status dir1/modified >output &&
+test_expect_success 'dry-run of partial commit excluding new file in index' '
+       git commit --dry-run dir1/modified >output &&
        test_cmp expect output
 '
 
@@ -298,6 +541,28 @@ test_expect_success 'status --untracked-files=all does not show submodule' '
        test_cmp expect output
 '
 
+cat >expect <<EOF
+ M dir1/modified
+A  dir2/added
+A  sm
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? expect
+?? output
+?? untracked
+EOF
+test_expect_success 'status -s submodule summary is disabled by default' '
+       git status -s >output &&
+       test_cmp expect output
+'
+
+# we expect the same as the previous test
+test_expect_success 'status -s --untracked-files=all does not show submodule' '
+       git status -s --untracked-files=all >output &&
+       test_cmp expect output
+'
+
 head=$(cd sm && git rev-parse --short=7 --verify HEAD)
 
 cat >expect <<EOF
@@ -335,6 +600,21 @@ test_expect_success 'status submodule summary' '
        test_cmp expect output
 '
 
+cat >expect <<EOF
+ M dir1/modified
+A  dir2/added
+A  sm
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? expect
+?? output
+?? untracked
+EOF
+test_expect_success 'status -s submodule summary' '
+       git status -s >output &&
+       test_cmp expect output
+'
 
 cat >expect <<EOF
 # On branch master
@@ -358,7 +638,23 @@ EOF
 test_expect_success 'status submodule summary (clean submodule)' '
        git commit -m "commit submodule" &&
        git config status.submodulesummary 10 &&
-       test_must_fail git status >output &&
+       test_must_fail git commit --dry-run >output &&
+       test_cmp expect output &&
+       git status >output &&
+       test_cmp expect output
+'
+
+cat >expect <<EOF
+ M dir1/modified
+?? dir1/untracked
+?? dir2/modified
+?? dir2/untracked
+?? expect
+?? output
+?? untracked
+EOF
+test_expect_success 'status -s submodule summary (clean submodule)' '
+       git status -s >output &&
        test_cmp expect output
 '
 
@@ -391,9 +687,9 @@ cat >expect <<EOF
 #      output
 #      untracked
 EOF
-test_expect_success 'status submodule summary (--amend)' '
+test_expect_success 'commit --dry-run submodule summary (--amend)' '
        git config status.submodulesummary 10 &&
-       git status --amend >output &&
+       git commit --dry-run --amend >output &&
        test_cmp expect output
 '
 
diff --git a/t/t7509-commit.sh b/t/t7509-commit.sh
new file mode 100755 (executable)
index 0000000..d52c060
--- /dev/null
@@ -0,0 +1,114 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Erick Mattos
+#
+
+test_description='git commit --reset-author'
+
+. ./test-lib.sh
+
+author_header () {
+       git cat-file commit "$1" |
+       sed -n -e '/^$/q' -e '/^author /p'
+}
+
+message_body () {
+       git cat-file commit "$1" |
+       sed -e '1,/^$/d'
+}
+
+test_expect_success '-C option copies authorship and message' '
+       echo "Initial" >foo &&
+       git add foo &&
+       test_tick &&
+       git commit -m "Initial Commit" --author Frigate\ \<flying@over.world\> &&
+       git tag Initial &&
+       echo "Test 1" >>foo &&
+       test_tick &&
+       git commit -a -C Initial &&
+       author_header Initial >expect &&
+       author_header HEAD >actual &&
+       test_cmp expect actual &&
+
+       message_body Initial >expect &&
+       message_body HEAD >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '-C option copies only the message with --reset-author' '
+       echo "Test 2" >>foo &&
+       test_tick &&
+       git commit -a -C Initial --reset-author &&
+       echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" >expect &&
+       author_header HEAD >actual
+       test_cmp expect actual &&
+
+       message_body Initial >expect &&
+       message_body HEAD >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '-c option copies authorship and message' '
+       echo "Test 3" >>foo &&
+       test_tick &&
+       EDITOR=: VISUAL=: git commit -a -c Initial &&
+       author_header Initial >expect &&
+       author_header HEAD >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '-c option copies only the message with --reset-author' '
+       echo "Test 4" >>foo &&
+       test_tick &&
+       EDITOR=: VISUAL=: git commit -a -c Initial --reset-author &&
+       echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" >expect &&
+       author_header HEAD >actual &&
+       test_cmp expect actual &&
+
+       message_body Initial >expect &&
+       message_body HEAD >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '--amend option copies authorship' '
+       git checkout Initial &&
+       echo "Test 5" >>foo &&
+       test_tick &&
+       git commit -a --amend -m "amend test" &&
+       author_header Initial >expect &&
+       author_header HEAD >actual &&
+
+       echo "amend test" >expect &&
+       message_body HEAD >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '--reset-author makes the commit ours even with --amend option' '
+       git checkout Initial &&
+       echo "Test 6" >>foo &&
+       test_tick &&
+       git commit -a --reset-author -m "Changed again" --amend &&
+       echo "author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" >expect &&
+       author_header HEAD >actual &&
+       test_cmp expect actual &&
+
+       echo "Changed again" >expect &&
+       message_body HEAD >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success '--reset-author and --author are mutually exclusive' '
+       git checkout Initial &&
+       echo "Test 7" >>foo &&
+       test_tick &&
+       test_must_fail git commit -a --reset-author --author="Xyzzy <frotz@nitfol.xz>"
+'
+
+test_expect_success '--reset-author should be rejected without -c/-C/--amend' '
+       git checkout Initial &&
+       echo "Test 7" >>foo &&
+       test_tick &&
+       test_must_fail git commit -a --reset-author -m done
+'
+
+test_done
index de977c5e2f0b270c57bbea9daaf8e22eefdf8560..269cfdf267443f67ef8c6c921610bc856f4e506b 100755 (executable)
@@ -22,15 +22,12 @@ test_expect_success 'setup' '
        git tag c2
 '
 
-cat >expected <<\EOF
-custom message
 
-Merge commit 'c2'
-EOF
 test_expect_success 'merge c2 with a custom message' '
        git reset --hard c1 &&
+       echo >expected "custom message" &&
        git merge -m "custom message" c2 &&
-       git cat-file commit HEAD | sed -e "1,/^$/d" > actual &&
+       git cat-file commit HEAD | sed -e "1,/^$/d" >actual &&
        test_cmp expected actual
 '
 
index 13c25f1d528ca1ec90575e42e0393accff5d8f35..ad834f200ac0d6ecf532374311d896557a9b941d 100755 (executable)
@@ -144,4 +144,17 @@ test_expect_success 'blame path that used to be a directory' '
        git blame HEAD^.. -- path
 '
 
+test_expect_success 'blame to a commit with no author name' '
+  TREE=`git rev-parse HEAD:`
+  cat >badcommit <<EOF
+tree $TREE
+author <noname> 1234567890 +0000
+committer David Reiss <dreiss@facebook.com> 1234567890 +0000
+
+some message
+EOF
+  COMMIT=`git hash-object -t commit -w badcommit`
+  git --no-pager blame $COMMIT -- uno >/dev/null
+'
+
 test_done
index 84a7f03d46ac4a624afd7d6d0d3c6198e41219c1..752adaac850862caf50e3573338b9cb742781147 100755 (executable)
@@ -95,6 +95,40 @@ test_expect_success \
     'Verify commandline' \
     'test_cmp expected commandline1'
 
+test_expect_success 'Send patches with --envelope-sender' '
+    clean_fake_sendmail &&
+     git send-email --envelope-sender="Patch Contributer <patch@example.com>" --suppress-cc=sob --from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors
+'
+
+cat >expected <<\EOF
+!patch@example.com!
+!-i!
+!nobody@example.com!
+!author@example.com!
+!one@example.com!
+!two@example.com!
+EOF
+test_expect_success \
+    'Verify commandline' \
+    'test_cmp expected commandline1'
+
+test_expect_success 'Send patches with --envelope-sender=auto' '
+    clean_fake_sendmail &&
+     git send-email --envelope-sender=auto --suppress-cc=sob --from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors
+'
+
+cat >expected <<\EOF
+!nobody@example.com!
+!-i!
+!nobody@example.com!
+!author@example.com!
+!one@example.com!
+!two@example.com!
+EOF
+test_expect_success \
+    'Verify commandline' \
+    'test_cmp expected commandline1'
+
 cat >expected-show-all-headers <<\EOF
 0001-Second.patch
 (mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
@@ -769,4 +803,53 @@ test_expect_success 'threading but no chain-reply-to' '
        grep "In-Reply-To: " stdout
 '
 
+test_expect_success 'warning with an implicit --chain-reply-to' '
+       git send-email \
+       --dry-run \
+       --from="Example <nobody@example.com>" \
+       --to=nobody@example.com \
+       outdir/000?-*.patch 2>errors >out &&
+       grep "no-chain-reply-to" errors
+'
+
+test_expect_success 'no warning with an explicit --chain-reply-to' '
+       git send-email \
+       --dry-run \
+       --from="Example <nobody@example.com>" \
+       --to=nobody@example.com \
+       --chain-reply-to \
+       outdir/000?-*.patch 2>errors >out &&
+       ! grep "no-chain-reply-to" errors
+'
+
+test_expect_success 'no warning with an explicit --no-chain-reply-to' '
+       git send-email \
+       --dry-run \
+       --from="Example <nobody@example.com>" \
+       --to=nobody@example.com \
+       --nochain-reply-to \
+       outdir/000?-*.patch 2>errors >out &&
+       ! grep "no-chain-reply-to" errors
+'
+
+test_expect_success 'no warning with sendemail.chainreplyto = false' '
+       git config sendemail.chainreplyto false &&
+       git send-email \
+       --dry-run \
+       --from="Example <nobody@example.com>" \
+       --to=nobody@example.com \
+       outdir/000?-*.patch 2>errors >out &&
+       ! grep "no-chain-reply-to" errors
+'
+
+test_expect_success 'no warning with sendemail.chainreplyto = true' '
+       git config sendemail.chainreplyto true &&
+       git send-email \
+       --dry-run \
+       --from="Example <nobody@example.com>" \
+       --to=nobody@example.com \
+       outdir/000?-*.patch 2>errors >out &&
+       ! grep "no-chain-reply-to" errors
+'
+
 test_done
index 70c52c1f9754934f1f868f998f3c007696799787..565365cbd3ff80d816dd02b2072045cd25ae4b93 100755 (executable)
@@ -105,4 +105,38 @@ test_expect_success 'empty directories in trunk exist' '
        )
 '
 
+test_expect_success 'remove a top-level directory from svn' '
+       svn_cmd rm -m "remove d" "$svnrepo"/d
+'
+
+test_expect_success 'removed top-level directory does not exist' '
+       git svn clone "$svnrepo" removed &&
+       test ! -e removed/d
+
+'
+unhandled=.git/svn/refs/remotes/git-svn/unhandled.log
+test_expect_success 'git svn gc-ed files work' '
+       (
+               cd removed &&
+               git svn gc &&
+               : Compress::Zlib may not be available &&
+               if test -f "$unhandled".gz
+               then
+                       svn_cmd mkdir -m gz "$svnrepo"/gz &&
+                       git reset --hard $(git rev-list HEAD | tail -1) &&
+                       git svn rebase &&
+                       test -f "$unhandled".gz &&
+                       test -f "$unhandled" &&
+                       for i in a b c "weird file name" gz "! !"
+                       do
+                               if ! test -d "$i"
+                               then
+                                       echo >&2 "$i does not exist"
+                                       exit 1
+                               fi
+                       done
+               fi
+       )
+'
+
 test_done
index f57daf401ac7c36c1d9f2380200e0efad2400f70..359eeaa738e28c6a3eb2ced6c0f24bbd8992ec4f 100755 (executable)
@@ -15,12 +15,27 @@ test_expect_success 'load svn dump' "
        git svn fetch --all
        "
 
-test_expect_success 'represent svn merges without intervening commits' "
-       [ `git cat-file commit HEAD^1 | grep parent | wc -l` -eq 2 ]
-       "
+test_expect_success 'all svn merges became git merge commits' '
+       unmarked=$(git rev-list --parents --all --grep=Merge |
+               grep -v " .* " | cut -f1 -d" ")
+       [ -z "$unmarked" ]
+       '
 
-test_expect_success 'represent svn merges with intervening commits' "
-       [ `git cat-file commit HEAD | grep parent | wc -l` -eq 2 ]
-       "
+test_expect_success 'cherry picks did not become git merge commits' '
+       bad_cherries=$(git rev-list --parents --all --grep=Cherry |
+               grep " .* " | cut -f1 -d" ")
+       [ -z "$bad_cherries" ]
+       '
+
+test_expect_success 'svn non-merge merge commits did not become git merge commits' '
+       bad_non_merges=$(git rev-list --parents --all --grep=non-merge |
+               grep " .* " | cut -f1 -d" ")
+       [ -z "$bad_non_merges" ]
+       '
+
+test_expect_success 'everything got merged in the end' '
+       unmerged=$(git rev-list --all --not master)
+       [ -z "$unmerged" ]
+       '
 
 test_done
index 7e3da75f8602621f0f202fc812fad2feefccda28..d917717cf3f177be7ed68306d078ac3362744bc8 100644 (file)
@@ -11,93 +11,151 @@ mkdir foo.svn
 svnadmin create foo.svn
 svn co file://`pwd`/foo.svn foo
 
+commit() {
+    i=$(( $1 + 1 ))
+    shift;
+    svn commit -m "(r$i) $*" >/dev/null || exit 1
+    echo $i
+}
+
+say() {
+    echo "\e[1m * $*\e[0m"
+}
+
+i=0
 cd foo
 mkdir trunk
 mkdir branches
 svn add trunk branches
-svn commit -m "Setup trunk and branches"
-cd trunk
+i=$(commit $i "Setup trunk and branches")
 
-git cat-file blob 6683463e:Makefile > Makefile
-svn add Makefile 
+git cat-file blob 6683463e:Makefile > trunk/Makefile
+svn add trunk/Makefile 
 
-echo "Committing ANCESTOR"
-svn commit -m "ancestor"
-cd ..
+say "Committing ANCESTOR"
+i=$(commit $i "ancestor")
 svn cp trunk branches/left
 
-echo "Committing BRANCH POINT"
-svn commit -m "make left branch"
+say "Committing BRANCH POINT"
+i=$(commit $i "make left branch")
 svn cp trunk branches/right
 
-echo "Committing other BRANCH POINT"
-svn commit -m "make right branch"
-cd branches/left/
+say "Committing other BRANCH POINT"
+i=$(commit $i "make right branch")
 
-#$sm init
-#svn commit -m "init svnmerge"
+say "Committing LEFT UPDATE"
+git cat-file blob 5873b67e:Makefile > branches/left/Makefile
+i=$(commit $i "left update 1")
 
-git cat-file blob 5873b67e:Makefile > Makefile
-echo "Committing BRANCH UPDATE 1"
-svn commit -m "left update 1"
-cd ../..
-
-cd trunk
-git cat-file blob 75118b13:Makefile > Makefile
-echo "Committing TRUNK UPDATE"
-svn commit -m "trunk update"
+git cat-file blob 75118b13:Makefile > branches/right/Makefile
+say "Committing RIGHT UPDATE"
+pre_right_update_1=$i
+i=$(commit $i "right update 1")
 
-cd ../branches/left
-git cat-file blob ff5ebe39:Makefile > Makefile
-echo "Committing BRANCH UPDATE 2"
-svn commit -m "left update 2"
+say "Making more commits on LEFT"
+git cat-file blob ff5ebe39:Makefile > branches/left/Makefile
+i=$(commit $i "left update 2")
+git cat-file blob b5039db6:Makefile > branches/left/Makefile
+i=$(commit $i "left update 3")
 
-git cat-file blob b5039db6:Makefile > Makefile
-echo "Committing BRANCH UPDATE 3"
-svn commit -m "left update 3"
+say "Making a LEFT SUB-BRANCH"
+svn cp branches/left branches/left-sub
+sub_left_make=$i
+i=$(commit $i "make left sub-branch")
 
-# merge to trunk
+say "Making a commit on LEFT SUB-BRANCH"
+echo "crunch" > branches/left-sub/README
+svn add branches/left-sub/README
+i=$(commit $i "left sub-branch update 1")
 
-cd ../..
+say "Merging LEFT to TRUNK"
 svn update
 cd trunk
-
 svn merge ../branches/left --accept postpone
-
-git cat-file blob b51ad431:Makefile > Makefile
-
+git cat-file blob b5039db6:Makefile > Makefile
 svn resolved Makefile
+i=$(commit $i "Merge left to trunk 1")
+cd ..
 
-svn commit -m "Merge trunk 1"
-
-# create commits on both branches
-
-cd ../branches/left
-git cat-file blob ff5ebe39:Makefile > Makefile
-echo "Committing BRANCH UPDATE 4"
-svn commit -m "left update 4"
-
-cd ../right
-git cat-file blob b5039db6:Makefile > Makefile
-echo "Committing other BRANCH UPDATE 1"
-svn commit -m "right update 1"
+say "Making more commits on LEFT and RIGHT"
+echo "touche" > branches/left/zlonk
+svn add branches/left/zlonk
+i=$(commit $i "left update 4")
+echo "thwacke" > branches/right/bang
+svn add branches/right/bang
+i=$(commit $i "right update 2")
 
-# merge to trun again
+say "Squash merge of RIGHT tip 2 commits onto TRUNK"
+svn update
+cd trunk
+svn merge -r$pre_right_update_1:$i ../branches/right
+i=$(commit $i "Cherry-pick right 2 commits to trunk")
+cd ..
 
-cd ../..
+say "Merging RIGHT to TRUNK"
 svn update
 cd trunk
+svn merge ../branches/right --accept postpone
+git cat-file blob b51ad431:Makefile > Makefile
+svn resolved Makefile
+i=$(commit $i "Merge right to trunk 1")
+cd ..
 
-svn merge ../branches/left --accept postpone
+say "Making more commits on RIGHT and TRUNK"
+echo "whamm" > branches/right/urkkk
+svn add branches/right/urkkk
+i=$(commit $i "right update 3")
+echo "pow" > trunk/vronk
+svn add trunk/vronk
+i=$(commit $i "trunk update 1")
 
+say "Merging RIGHT to LEFT SUB-BRANCH"
+svn update
+cd branches/left-sub
+svn merge ../right --accept postpone
 git cat-file blob b51ad431:Makefile > Makefile
-
 svn resolved Makefile
+i=$(commit $i "Merge right to left sub-branch")
+cd ../..
 
-svn commit -m "Merge trunk 2"
+say "Making more commits on LEFT SUB-BRANCH and LEFT"
+echo "zowie" > branches/left-sub/wham_eth
+svn add branches/left-sub/wham_eth
+pre_sub_left_update_2=$i
+i=$(commit $i "left sub-branch update 2")
+sub_left_update_2=$i
+echo "eee_yow" > branches/left/glurpp
+svn add branches/left/glurpp
+i=$(commit $i "left update 5")
+
+say "Cherry pick LEFT SUB-BRANCH commit to LEFT"
+svn update
+cd branches/left
+svn merge -r$pre_sub_left_update_2:$sub_left_update_2 ../left-sub
+i=$(commit $i "Cherry-pick left sub-branch commit to left")
+cd ../..
 
+say "Merging LEFT SUB-BRANCH back to LEFT"
+svn update
+cd branches/left
+# it's only a merge because the previous merge cherry-picked the top commit
+svn merge -r$sub_left_make:$sub_left_update_2 ../left-sub --accept postpone
+i=$(commit $i "Merge left sub-branch to left")
 cd ../..
 
+say "Merging EVERYTHING to TRUNK"
+svn update
+cd trunk
+svn merge ../branches/left --accept postpone
+svn resolved bang
+i=$(commit $i "Merge left to trunk 2")
+# this merge, svn happily updates the mergeinfo, but there is actually
+# nothing to merge.  git-svn will not make a meaningless merge commit.
+svn merge ../branches/right --accept postpone
+i=$(commit $i "non-merge right to trunk 2")
+cd ..
+
+cd ..
 svnadmin dump foo.svn > svn-mergeinfo.dump
 
 rm -rf foo foo.svn
index 11a883fda96ec81f3ed9ba3a9cfc424cc41d8103..9543e31c96ddd9a8d4a5f2a23d0fc9947d89bbbc 100644 (file)
@@ -1,6 +1,6 @@
 SVN-fs-dump-format-version: 2
 
-UUID: 1530d5a2-a1dc-4438-8ad5-d95e96db8945
+UUID: 64142547-0943-4db2-836a-d1e1eb2f9924
 
 Revision-number: 0
 Prop-content-length: 56
@@ -9,25 +9,25 @@ Content-length: 56
 K 8
 svn:date
 V 27
-2009-11-12T20:29:38.812226Z
+2009-12-19T16:17:51.232640Z
 PROPS-END
 
 Revision-number: 1
-Prop-content-length: 127
-Content-length: 127
+Prop-content-length: 128
+Content-length: 128
 
 K 7
 svn:log
-V 24
-Setup trunk and branches
+V 29
+(r1) Setup trunk and branches
 K 10
 svn:author
-V 8
-tallsopp
+V 4
+samv
 K 8
 svn:date
 V 27
-2009-11-12T20:29:39.045856Z
+2009-12-19T16:17:51.831965Z
 PROPS-END
 
 Node-path: branches
@@ -49,21 +49,21 @@ PROPS-END
 
 
 Revision-number: 2
-Prop-content-length: 110
-Content-length: 110
+Prop-content-length: 112
+Content-length: 112
 
 K 7
 svn:log
-V 8
-ancestor
+V 13
+(r2) ancestor
 K 10
 svn:author
-V 8
-tallsopp
+V 4
+samv
 K 8
 svn:date
 V 27
-2009-11-12T20:29:40.079587Z
+2009-12-19T16:17:52.300075Z
 PROPS-END
 
 Node-path: trunk/Makefile
@@ -156,21 +156,21 @@ backup: clean
 
 
 Revision-number: 3
-Prop-content-length: 119
-Content-length: 119
+Prop-content-length: 120
+Content-length: 120
 
 K 7
 svn:log
-V 16
-make left branch
+V 21
+(r3) make left branch
 K 10
 svn:author
-V 8
-tallsopp
+V 4
+samv
 K 8
 svn:date
 V 27
-2009-11-12T20:29:42.084439Z
+2009-12-19T16:17:52.768800Z
 PROPS-END
 
 Node-path: branches/left
@@ -190,21 +190,21 @@ Text-copy-source-sha1: 103205ce331f7d64086dba497574734f78439590
 
 
 Revision-number: 4
-Prop-content-length: 120
-Content-length: 120
+Prop-content-length: 121
+Content-length: 121
 
 K 7
 svn:log
-V 17
-make right branch
+V 22
+(r4) make right branch
 K 10
 svn:author
-V 8
-tallsopp
+V 4
+samv
 K 8
 svn:date
 V 27
-2009-11-12T20:29:44.065452Z
+2009-12-19T16:17:53.177879Z
 PROPS-END
 
 Node-path: branches/right
@@ -224,21 +224,21 @@ Text-copy-source-sha1: 103205ce331f7d64086dba497574734f78439590
 
 
 Revision-number: 5
-Prop-content-length: 116
-Content-length: 116
+Prop-content-length: 117
+Content-length: 117
 
 K 7
 svn:log
-V 13
-left update 1
+V 18
+(r5) left update 1
 K 10
 svn:author
-V 8
-tallsopp
+V 4
+samv
 K 8
 svn:date
 V 27
-2009-11-12T20:29:45.066262Z
+2009-12-19T16:17:53.604691Z
 PROPS-END
 
 Node-path: branches/left/Makefile
@@ -329,24 +329,24 @@ backup: clean
 
 
 Revision-number: 6
-Prop-content-length: 115
-Content-length: 115
+Prop-content-length: 118
+Content-length: 118
 
 K 7
 svn:log
-V 12
-trunk update
+V 19
+(r6) right update 1
 K 10
 svn:author
-V 8
-tallsopp
+V 4
+samv
 K 8
 svn:date
 V 27
-2009-11-12T20:29:46.278498Z
+2009-12-19T16:17:54.063555Z
 PROPS-END
 
-Node-path: trunk/Makefile
+Node-path: branches/right/Makefile
 Node-kind: file
 Node-action: change
 Text-content-length: 2521
@@ -437,21 +437,21 @@ backup: clean
 
 
 Revision-number: 7
-Prop-content-length: 116
-Content-length: 116
+Prop-content-length: 117
+Content-length: 117
 
 K 7
 svn:log
-V 13
-left update 2
+V 18
+(r7) left update 2
 K 10
 svn:author
-V 8
-tallsopp
+V 4
+samv
 K 8
 svn:date
 V 27
-2009-11-12T20:29:47.069090Z
+2009-12-19T16:17:54.523904Z
 PROPS-END
 
 Node-path: branches/left/Makefile
@@ -542,21 +542,21 @@ backup: clean
 
 
 Revision-number: 8
-Prop-content-length: 116
-Content-length: 116
+Prop-content-length: 117
+Content-length: 117
 
 K 7
 svn:log
-V 13
-left update 3
+V 18
+(r8) left update 3
 K 10
 svn:author
-V 8
-tallsopp
+V 4
+samv
 K 8
 svn:date
 V 27
-2009-11-12T20:29:48.053835Z
+2009-12-19T16:17:54.975970Z
 PROPS-END
 
 Node-path: branches/left/Makefile
@@ -647,33 +647,285 @@ backup: clean
 
 
 Revision-number: 9
-Prop-content-length: 116
-Content-length: 116
+Prop-content-length: 124
+Content-length: 124
 
 K 7
 svn:log
-V 13
-Merge trunk 1
+V 25
+(r9) make left sub-branch
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:17:55.459904Z
+PROPS-END
+
+Node-path: branches/left-sub
+Node-kind: dir
+Node-action: add
+Node-copyfrom-rev: 3
+Node-copyfrom-path: branches/left
+
+
+Node-path: branches/left-sub/Makefile
+Node-kind: file
+Node-action: delete
+
+Node-path: branches/left-sub/Makefile
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 8
+Node-copyfrom-path: branches/left/Makefile
+Text-copy-source-md5: 5ccff689fb290e00b85fe18ee50c54ba
+Text-copy-source-sha1: a13de8e23f1483efca3e57b2b64b0ae6f740ce10
+
+
+
+
+Revision-number: 10
+Prop-content-length: 129
+Content-length: 129
+
+K 7
+svn:log
+V 30
+(r10) left sub-branch update 1
 K 10
 svn:author
-V 8
-tallsopp
+V 4
+samv
 K 8
 svn:date
 V 27
-2009-11-12T20:29:51.098306Z
+2009-12-19T16:17:55.862113Z
+PROPS-END
+
+Node-path: branches/left-sub/README
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 7
+Text-content-md5: fdbcfb6be9afe1121862143f226b51cf
+Text-content-sha1: 1d1f5ea4ceb584337ffe59b8980d92e3b78dfef4
+Content-length: 17
+
+PROPS-END
+crunch
+
+
+Revision-number: 11
+Prop-content-length: 126
+Content-length: 126
+
+K 7
+svn:log
+V 27
+(r11) Merge left to trunk 1
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:17:56.413416Z
 PROPS-END
 
 Node-path: trunk
 Node-kind: dir
 Node-action: change
-Prop-content-length: 53
-Content-length: 53
+Prop-content-length: 54
+Content-length: 54
 
 K 13
 svn:mergeinfo
-V 18
-/branches/left:2-8
+V 19
+/branches/left:2-10
+PROPS-END
+
+
+Node-path: trunk/Makefile
+Node-kind: file
+Node-action: change
+Text-content-length: 2593
+Text-content-md5: 5ccff689fb290e00b85fe18ee50c54ba
+Text-content-sha1: a13de8e23f1483efca3e57b2b64b0ae6f740ce10
+Content-length: 2593
+
+# -DCOLLISION_CHECK if you believe that SHA1's
+# 1461501637330902918203684832716283019655932542976 hashes do not give you
+# enough guarantees about no collisions between objects ever hapenning.
+#
+# -DNSEC if you want git to care about sub-second file mtimes and ctimes.
+# Note that you need some new glibc (at least >2.2.4) for this, and it will
+# BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely randomly
+# break unless your underlying filesystem supports those sub-second times
+# (my ext3 doesn't).
+CFLAGS=-g -O3 -Wall
+
+CC=gcc
+
+
+PROG=   update-cache show-diff init-db write-tree read-tree commit-tree \
+       cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \
+       check-files ls-tree merge-base
+
+all: $(PROG)
+
+install: $(PROG)
+       install $(PROG) $(HOME)/bin/
+
+LIBS= -lssl -lz
+
+init-db: init-db.o
+
+update-cache: update-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS)
+
+show-diff: show-diff.o read-cache.o
+       $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS)
+
+write-tree: write-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS)
+
+read-tree: read-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS)
+
+commit-tree: commit-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS)
+
+cat-file: cat-file.o read-cache.o
+       $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS)
+
+fsck-cache: fsck-cache.o read-cache.o object.o commit.o tree.o blob.o
+       $(CC) $(CFLAGS) -o fsck-cache fsck-cache.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+checkout-cache: checkout-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o checkout-cache checkout-cache.o read-cache.o $(LIBS)
+
+diff-tree: diff-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o diff-tree diff-tree.o read-cache.o $(LIBS)
+
+rev-tree: rev-tree.o read-cache.o object.o commit.o tree.o blob.o
+       $(CC) $(CFLAGS) -o rev-tree rev-tree.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+show-files: show-files.o read-cache.o
+       $(CC) $(CFLAGS) -o show-files show-files.o read-cache.o $(LIBS)
+
+check-files: check-files.o read-cache.o
+       $(CC) $(CFLAGS) -o check-files check-files.o read-cache.o $(LIBS)
+
+ls-tree: ls-tree.o read-cache.o
+       $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS)
+
+merge-base: merge-base.o read-cache.o object.o commit.o tree.o blob.o
+       $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+read-cache.o: cache.h
+show-diff.o: cache.h
+
+clean:
+       rm -f *.o $(PROG)
+
+backup: clean
+       cd .. ; tar czvf dircache.tar.gz dir-cache
+
+
+Revision-number: 12
+Prop-content-length: 118
+Content-length: 118
+
+K 7
+svn:log
+V 19
+(r12) left update 4
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:17:56.831014Z
+PROPS-END
+
+Node-path: branches/left/zlonk
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 7
+Text-content-md5: 8b9d8c7c2aaa6167e7d3407a773bbbba
+Text-content-sha1: 9716527ebd70a75c27625cacbeb2d897c6e86178
+Content-length: 17
+
+PROPS-END
+touche
+
+
+Revision-number: 13
+Prop-content-length: 119
+Content-length: 119
+
+K 7
+svn:log
+V 20
+(r13) right update 2
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:17:57.341143Z
+PROPS-END
+
+Node-path: branches/right/bang
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 8
+Text-content-md5: 34c28f1d2dc6a9adeccc4265bf7516cb
+Text-content-sha1: 0bc5bb345c0e71d28f784f12e0bd2d384c283062
+Content-length: 18
+
+PROPS-END
+thwacke
+
+
+Revision-number: 14
+Prop-content-length: 141
+Content-length: 141
+
+K 7
+svn:log
+V 42
+(r14) Cherry-pick right 2 commits to trunk
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:17:57.841851Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: change
+Prop-content-length: 75
+Content-length: 75
+
+K 13
+svn:mergeinfo
+V 40
+/branches/left:2-10
+/branches/right:6-13
 PROPS-END
 
 
@@ -767,31 +1019,147 @@ backup: clean
        cd .. ; tar czvf dircache.tar.gz dir-cache
 
 
-Revision-number: 10
-Prop-content-length: 116
-Content-length: 116
+Node-path: trunk/bang
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 13
+Node-copyfrom-path: branches/right/bang
+Text-copy-source-md5: 34c28f1d2dc6a9adeccc4265bf7516cb
+Text-copy-source-sha1: 0bc5bb345c0e71d28f784f12e0bd2d384c283062
+
+
+Revision-number: 15
+Prop-content-length: 127
+Content-length: 127
 
 K 7
 svn:log
-V 13
-left update 4
+V 28
+(r15) Merge right to trunk 1
 K 10
 svn:author
-V 8
-tallsopp
+V 4
+samv
 K 8
 svn:date
 V 27
-2009-11-12T20:29:52.081644Z
+2009-12-19T16:17:58.368520Z
 PROPS-END
 
-Node-path: branches/left/Makefile
+Node-path: trunk
+Node-kind: dir
+Node-action: change
+Prop-content-length: 75
+Content-length: 75
+
+K 13
+svn:mergeinfo
+V 40
+/branches/left:2-10
+/branches/right:2-14
+PROPS-END
+
+
+Revision-number: 16
+Prop-content-length: 119
+Content-length: 119
+
+K 7
+svn:log
+V 20
+(r16) right update 3
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:17:58.779056Z
+PROPS-END
+
+Node-path: branches/right/urkkk
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 5889c8392e16251b0c80927607a03036
+Text-content-sha1: 3934264d277a0cf886b6b1c7f2b9e56da2525302
+Content-length: 16
+
+PROPS-END
+whamm
+
+
+Revision-number: 17
+Prop-content-length: 119
+Content-length: 119
+
+K 7
+svn:log
+V 20
+(r17) trunk update 1
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:17:59.221851Z
+PROPS-END
+
+Node-path: trunk/vronk
 Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 4
+Text-content-md5: b2f80fa02a7f1364b9c29d3da44bf9f9
+Text-content-sha1: e994d980c0f2d7a3f76138bf96d57f36f9633828
+Content-length: 14
+
+PROPS-END
+pow
+
+
+Revision-number: 18
+Prop-content-length: 135
+Content-length: 135
+
+K 7
+svn:log
+V 36
+(r18) Merge right to left sub-branch
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:17:59.781666Z
+PROPS-END
+
+Node-path: branches/left-sub
+Node-kind: dir
 Node-action: change
-Text-content-length: 2529
-Text-content-md5: f6b197cc3f2e89a83e545d4bb003de73
-Text-content-sha1: 2f656677cfec0bceec85e53036ffb63e25126f8e
-Content-length: 2529
+Prop-content-length: 55
+Content-length: 55
+
+K 13
+svn:mergeinfo
+V 20
+/branches/right:2-17
+PROPS-END
+
+
+Node-path: branches/left-sub/Makefile
+Node-kind: file
+Node-action: change
+Text-content-length: 2713
+Text-content-md5: 0afbe34f244cd662b1f97d708c687f90
+Text-content-sha1: 46d9377d783e67a9b581da110352e799517c8a14
+Content-length: 2713
 
 # -DCOLLISION_CHECK if you believe that SHA1's
 # 1461501637330902918203684832716283019655932542976 hashes do not give you
@@ -809,7 +1177,7 @@ CC=gcc
 
 PROG=   update-cache show-diff init-db write-tree read-tree commit-tree \
        cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \
-       check-files ls-tree merge-base
+       check-files ls-tree merge-base merge-cache
 
 all: $(PROG)
 
@@ -859,8 +1227,11 @@ check-files: check-files.o read-cache.o
 ls-tree: ls-tree.o read-cache.o
        $(CC) $(CFLAGS) -o ls-tree ls-tree.o read-cache.o $(LIBS)
 
-merge-base: merge-base.o read-cache.o
-       $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o $(LIBS)
+merge-base: merge-base.o read-cache.o object.o commit.o tree.o blob.o
+       $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
+
+merge-cache: merge-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o merge-cache merge-cache.o read-cache.o $(LIBS)
 
 read-cache.o: cache.h
 show-diff.o: cache.h
@@ -872,31 +1243,165 @@ backup: clean
        cd .. ; tar czvf dircache.tar.gz dir-cache
 
 
-Revision-number: 11
-Prop-content-length: 117
-Content-length: 117
+Node-path: branches/left-sub/bang
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 17
+Node-copyfrom-path: branches/right/bang
+Text-copy-source-md5: 34c28f1d2dc6a9adeccc4265bf7516cb
+Text-copy-source-sha1: 0bc5bb345c0e71d28f784f12e0bd2d384c283062
+
+
+Node-path: branches/left-sub/urkkk
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 17
+Node-copyfrom-path: branches/right/urkkk
+Text-copy-source-md5: 5889c8392e16251b0c80927607a03036
+Text-copy-source-sha1: 3934264d277a0cf886b6b1c7f2b9e56da2525302
+
+
+Revision-number: 19
+Prop-content-length: 129
+Content-length: 129
 
 K 7
 svn:log
-V 14
-right update 1
+V 30
+(r19) left sub-branch update 2
 K 10
 svn:author
-V 8
-tallsopp
+V 4
+samv
 K 8
 svn:date
 V 27
-2009-11-12T20:29:53.059636Z
+2009-12-19T16:18:00.200531Z
 PROPS-END
 
-Node-path: branches/right/Makefile
+Node-path: branches/left-sub/wham_eth
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 6
+Text-content-md5: 757bcd5818572ef3f9580052617c1c8b
+Text-content-sha1: b165019b005c199237ba822c4404e771e93b654a
+Content-length: 16
+
+PROPS-END
+zowie
+
+
+Revision-number: 20
+Prop-content-length: 118
+Content-length: 118
+
+K 7
+svn:log
+V 19
+(r20) left update 5
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:18:00.659636Z
+PROPS-END
+
+Node-path: branches/left/glurpp
 Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 8
+Text-content-md5: 14a169f628e0bb59df9c2160649d0a30
+Text-content-sha1: ef7d929e52177767ecfcd28941f6b7f04b4131e3
+Content-length: 18
+
+PROPS-END
+eee_yow
+
+
+Revision-number: 21
+Prop-content-length: 147
+Content-length: 147
+
+K 7
+svn:log
+V 48
+(r21) Cherry-pick left sub-branch commit to left
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:18:01.194402Z
+PROPS-END
+
+Node-path: branches/left
+Node-kind: dir
 Node-action: change
-Text-content-length: 2593
-Text-content-md5: 5ccff689fb290e00b85fe18ee50c54ba
-Text-content-sha1: a13de8e23f1483efca3e57b2b64b0ae6f740ce10
-Content-length: 2593
+Prop-content-length: 56
+Content-length: 56
+
+K 13
+svn:mergeinfo
+V 21
+/branches/left-sub:19
+PROPS-END
+
+
+Node-path: branches/left/wham_eth
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 19
+Node-copyfrom-path: branches/left-sub/wham_eth
+Text-copy-source-md5: 757bcd5818572ef3f9580052617c1c8b
+Text-copy-source-sha1: b165019b005c199237ba822c4404e771e93b654a
+
+
+Revision-number: 22
+Prop-content-length: 134
+Content-length: 134
+
+K 7
+svn:log
+V 35
+(r22) Merge left sub-branch to left
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:18:01.679218Z
+PROPS-END
+
+Node-path: branches/left
+Node-kind: dir
+Node-action: change
+Prop-content-length: 79
+Content-length: 79
+
+K 13
+svn:mergeinfo
+V 44
+/branches/left-sub:4-19
+/branches/right:2-17
+PROPS-END
+
+
+Node-path: branches/left/Makefile
+Node-kind: file
+Node-action: change
+Text-content-length: 2713
+Text-content-md5: 0afbe34f244cd662b1f97d708c687f90
+Text-content-sha1: 46d9377d783e67a9b581da110352e799517c8a14
+Content-length: 2713
 
 # -DCOLLISION_CHECK if you believe that SHA1's
 # 1461501637330902918203684832716283019655932542976 hashes do not give you
@@ -914,7 +1419,7 @@ CC=gcc
 
 PROG=   update-cache show-diff init-db write-tree read-tree commit-tree \
        cat-file fsck-cache checkout-cache diff-tree rev-tree show-files \
-       check-files ls-tree merge-base
+       check-files ls-tree merge-base merge-cache
 
 all: $(PROG)
 
@@ -967,6 +1472,9 @@ ls-tree: ls-tree.o read-cache.o
 merge-base: merge-base.o read-cache.o object.o commit.o tree.o blob.o
        $(CC) $(CFLAGS) -o merge-base merge-base.o read-cache.o object.o commit.o tree.o blob.o $(LIBS)
 
+merge-cache: merge-cache.o read-cache.o
+       $(CC) $(CFLAGS) -o merge-cache merge-cache.o read-cache.o $(LIBS)
+
 read-cache.o: cache.h
 show-diff.o: cache.h
 
@@ -977,34 +1485,141 @@ backup: clean
        cd .. ; tar czvf dircache.tar.gz dir-cache
 
 
-Revision-number: 12
-Prop-content-length: 116
-Content-length: 116
+Node-path: branches/left/README
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 18
+Node-copyfrom-path: branches/left-sub/README
+Text-copy-source-md5: fdbcfb6be9afe1121862143f226b51cf
+Text-copy-source-sha1: 1d1f5ea4ceb584337ffe59b8980d92e3b78dfef4
+
+
+Node-path: branches/left/bang
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 18
+Node-copyfrom-path: branches/left-sub/bang
+Text-copy-source-md5: 34c28f1d2dc6a9adeccc4265bf7516cb
+Text-copy-source-sha1: 0bc5bb345c0e71d28f784f12e0bd2d384c283062
+
+
+Node-path: branches/left/urkkk
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 18
+Node-copyfrom-path: branches/left-sub/urkkk
+Text-copy-source-md5: 5889c8392e16251b0c80927607a03036
+Text-copy-source-sha1: 3934264d277a0cf886b6b1c7f2b9e56da2525302
+
+
+Revision-number: 23
+Prop-content-length: 126
+Content-length: 126
 
 K 7
 svn:log
-V 13
-Merge trunk 2
+V 27
+(r23) Merge left to trunk 2
 K 10
 svn:author
-V 8
-tallsopp
+V 4
+samv
 K 8
 svn:date
 V 27
-2009-11-12T20:29:56.083003Z
+2009-12-19T16:18:02.212349Z
 PROPS-END
 
 Node-path: trunk
 Node-kind: dir
 Node-action: change
-Prop-content-length: 54
-Content-length: 54
+Prop-content-length: 99
+Content-length: 99
 
 K 13
 svn:mergeinfo
-V 19
-/branches/left:2-11
+V 64
+/branches/left:2-22
+/branches/left-sub:4-19
+/branches/right:2-17
+PROPS-END
+
+
+Node-path: trunk/README
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 22
+Node-copyfrom-path: branches/left/README
+Text-copy-source-md5: fdbcfb6be9afe1121862143f226b51cf
+Text-copy-source-sha1: 1d1f5ea4ceb584337ffe59b8980d92e3b78dfef4
+
+
+Node-path: trunk/glurpp
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 22
+Node-copyfrom-path: branches/left/glurpp
+Text-copy-source-md5: 14a169f628e0bb59df9c2160649d0a30
+Text-copy-source-sha1: ef7d929e52177767ecfcd28941f6b7f04b4131e3
+
+
+Node-path: trunk/urkkk
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 22
+Node-copyfrom-path: branches/left/urkkk
+Text-copy-source-md5: 5889c8392e16251b0c80927607a03036
+Text-copy-source-sha1: 3934264d277a0cf886b6b1c7f2b9e56da2525302
+
+
+Node-path: trunk/wham_eth
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 22
+Node-copyfrom-path: branches/left/wham_eth
+Text-copy-source-md5: 757bcd5818572ef3f9580052617c1c8b
+Text-copy-source-sha1: b165019b005c199237ba822c4404e771e93b654a
+
+
+Node-path: trunk/zlonk
+Node-kind: file
+Node-action: add
+Node-copyfrom-rev: 22
+Node-copyfrom-path: branches/left/zlonk
+Text-copy-source-md5: 8b9d8c7c2aaa6167e7d3407a773bbbba
+Text-copy-source-sha1: 9716527ebd70a75c27625cacbeb2d897c6e86178
+
+
+Revision-number: 24
+Prop-content-length: 131
+Content-length: 131
+
+K 7
+svn:log
+V 32
+(r24) non-merge right to trunk 2
+K 10
+svn:author
+V 4
+samv
+K 8
+svn:date
+V 27
+2009-12-19T16:18:02.672148Z
+PROPS-END
+
+Node-path: trunk
+Node-kind: dir
+Node-action: change
+Prop-content-length: 99
+Content-length: 99
+
+K 13
+svn:mergeinfo
+V 64
+/branches/left:2-22
+/branches/left-sub:4-19
+/branches/right:2-22
 PROPS-END
 
 
diff --git a/t/t9152-svn-empty-dirs-after-gc.sh b/t/t9152-svn-empty-dirs-after-gc.sh
new file mode 100755 (executable)
index 0000000..301e779
--- /dev/null
@@ -0,0 +1,40 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Robert Zeh
+
+test_description='git svn creates empty directories, calls git gc, makes sure they are still empty'
+. ./lib-git-svn.sh
+
+test_expect_success 'initialize repo' '
+       for i in a b c d d/e d/e/f "weird file name"
+       do
+               svn_cmd mkdir -m "mkdir $i" "$svnrepo"/"$i"
+       done
+'
+
+test_expect_success 'clone' 'git svn clone "$svnrepo" cloned'
+
+test_expect_success 'git svn gc runs' '
+       (
+               cd cloned &&
+               git svn gc
+       )
+'
+
+test_expect_success 'git svn mkdirs recreates empty directories after git svn gc' '
+       (
+               cd cloned &&
+               rm -r * &&
+               git svn mkdirs &&
+               for i in a b c d d/e d/e/f "weird file name"
+               do
+                       if ! test -d "$i"
+                       then
+                               echo >&2 "$i does not exist"
+                               exit 1
+                       fi
+               done
+       )
+'
+
+test_done
index 85377c8fc36e5bf204f95afe682506755307ea6b..c1476f9a2391396a201a919025963b82f7e14217 100644 (file)
@@ -213,6 +213,17 @@ test_set_editor () {
        export EDITOR
 }
 
+test_decode_color () {
+       sed     -e 's/.\[1m/<WHITE>/g' \
+               -e 's/.\[31m/<RED>/g' \
+               -e 's/.\[32m/<GREEN>/g' \
+               -e 's/.\[33m/<YELLOW>/g' \
+               -e 's/.\[34m/<BLUE>/g' \
+               -e 's/.\[35m/<MAGENTA>/g' \
+               -e 's/.\[36m/<CYAN>/g' \
+               -e 's/.\[m/<RESET>/g'
+}
+
 test_tick () {
        if test -z "${test_tick+set}"
        then
@@ -641,20 +652,29 @@ GIT_CONFIG_NOSYSTEM=1
 GIT_CONFIG_NOGLOBAL=1
 export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR GIT_CONFIG_NOSYSTEM GIT_CONFIG_NOGLOBAL
 
+. ../GIT-BUILD-OPTIONS
+
 GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git
 export GITPERLLIB
 test -d ../templates/blt || {
        error "You haven't built things yet, have you?"
 }
 
+if test -z "$GIT_TEST_INSTALLED" && test -z "$NO_PYTHON"
+then
+       GITPYTHONLIB="$(pwd)/../git_remote_helpers/build/lib"
+       export GITPYTHONLIB
+       test -d ../git_remote_helpers/build || {
+               error "You haven't built git_remote_helpers yet, have you?"
+       }
+fi
+
 if ! test -x ../test-chmtime; then
        echo >&2 'You need to build test-chmtime:'
        echo >&2 'Run "make test-chmtime" in the source (toplevel) directory'
        exit 1
 fi
 
-. ../GIT-BUILD-OPTIONS
-
 # Test repository
 test="trash directory.$(basename "$0" .sh)"
 test -n "$root" && test="$root/$test"
@@ -738,6 +758,7 @@ case $(uname -s) in
 esac
 
 test -z "$NO_PERL" && test_set_prereq PERL
+test -z "$NO_PYTHON" && test_set_prereq PYTHON
 
 # test whether the filesystem supports symbolic links
 ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS
index 5078c7100f16e9d7ac08aad05e3797ad5b6626bc..11f3d7ec52893dcdee127d8493f1897c6c091b31 100644 (file)
@@ -6,6 +6,7 @@
 #include "diff.h"
 #include "revision.h"
 #include "quote.h"
+#include "remote.h"
 
 struct helper_data
 {
@@ -13,8 +14,12 @@ struct helper_data
        struct child_process *helper;
        FILE *out;
        unsigned fetch : 1,
+               import : 1,
                option : 1,
                push : 1;
+       /* These go from remote name (as in "list") to private name */
+       struct refspec *refspecs;
+       int refspec_nr;
 };
 
 static struct child_process *get_helper(struct transport *transport)
@@ -22,6 +27,9 @@ static struct child_process *get_helper(struct transport *transport)
        struct helper_data *data = transport->data;
        struct strbuf buf = STRBUF_INIT;
        struct child_process *helper;
+       const char **refspecs = NULL;
+       int refspec_nr = 0;
+       int refspec_alloc = 0;
 
        if (data->helper)
                return data->helper;
@@ -55,7 +63,25 @@ static struct child_process *get_helper(struct transport *transport)
                        data->option = 1;
                if (!strcmp(buf.buf, "push"))
                        data->push = 1;
+               if (!strcmp(buf.buf, "import"))
+                       data->import = 1;
+               if (!data->refspecs && !prefixcmp(buf.buf, "refspec ")) {
+                       ALLOC_GROW(refspecs,
+                                  refspec_nr + 1,
+                                  refspec_alloc);
+                       refspecs[refspec_nr++] = strdup(buf.buf + strlen("refspec "));
+               }
+       }
+       if (refspecs) {
+               int i;
+               data->refspec_nr = refspec_nr;
+               data->refspecs = parse_fetch_refspec(refspec_nr, refspecs);
+               for (i = 0; i < refspec_nr; i++) {
+                       free((char *)refspecs[i]);
+               }
+               free(refspecs);
        }
+       strbuf_release(&buf);
        return data->helper;
 }
 
@@ -72,7 +98,6 @@ static int disconnect_helper(struct transport *transport)
                free(data->helper);
                data->helper = NULL;
        }
-       free(data);
        return 0;
 }
 
@@ -154,8 +179,18 @@ static void standard_options(struct transport *t)
        set_helper_option(t, "verbosity", buf);
 }
 
+static int release_helper(struct transport *transport)
+{
+       struct helper_data *data = transport->data;
+       free_refspec(data->refspec_nr, data->refspecs);
+       data->refspecs = NULL;
+       disconnect_helper(transport);
+       free(transport->data);
+       return 0;
+}
+
 static int fetch_with_fetch(struct transport *transport,
-                           int nr_heads, const struct ref **to_fetch)
+                           int nr_heads, struct ref **to_fetch)
 {
        struct helper_data *data = transport->data;
        int i;
@@ -197,8 +232,64 @@ static int fetch_with_fetch(struct transport *transport,
        return 0;
 }
 
+static int get_importer(struct transport *transport, struct child_process *fastimport)
+{
+       struct child_process *helper = get_helper(transport);
+       memset(fastimport, 0, sizeof(*fastimport));
+       fastimport->in = helper->out;
+       fastimport->argv = xcalloc(5, sizeof(*fastimport->argv));
+       fastimport->argv[0] = "fast-import";
+       fastimport->argv[1] = "--quiet";
+
+       fastimport->git_cmd = 1;
+       return start_command(fastimport);
+}
+
+static int fetch_with_import(struct transport *transport,
+                            int nr_heads, struct ref **to_fetch)
+{
+       struct child_process fastimport;
+       struct child_process *helper = get_helper(transport);
+       struct helper_data *data = transport->data;
+       int i;
+       struct ref *posn;
+       struct strbuf buf = STRBUF_INIT;
+
+       if (get_importer(transport, &fastimport))
+               die("Couldn't run fast-import");
+
+       for (i = 0; i < nr_heads; i++) {
+               posn = to_fetch[i];
+               if (posn->status & REF_STATUS_UPTODATE)
+                       continue;
+
+               strbuf_addf(&buf, "import %s\n", posn->name);
+               write_in_full(helper->in, buf.buf, buf.len);
+               strbuf_reset(&buf);
+       }
+       disconnect_helper(transport);
+       finish_command(&fastimport);
+       free(fastimport.argv);
+       fastimport.argv = NULL;
+
+       for (i = 0; i < nr_heads; i++) {
+               char *private;
+               posn = to_fetch[i];
+               if (posn->status & REF_STATUS_UPTODATE)
+                       continue;
+               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);
+       }
+       strbuf_release(&buf);
+       return 0;
+}
+
 static int fetch(struct transport *transport,
-                int nr_heads, const struct ref **to_fetch)
+                int nr_heads, struct ref **to_fetch)
 {
        struct helper_data *data = transport->data;
        int i, count;
@@ -214,6 +305,9 @@ static int fetch(struct transport *transport,
        if (data->fetch)
                return fetch_with_fetch(transport, nr_heads, to_fetch);
 
+       if (data->import)
+               return fetch_with_import(transport, nr_heads, to_fetch);
+
        return -1;
 }
 
@@ -343,6 +437,22 @@ static int push_refs(struct transport *transport,
        return 0;
 }
 
+static int has_attribute(const char *attrs, const char *attr) {
+       int len;
+       if (!attrs)
+               return 0;
+
+       len = strlen(attr);
+       for (;;) {
+               const char *space = strchrnul(attrs, ' ');
+               if (len == space - attrs && !strncmp(attrs, attr, len))
+                       return 1;
+               if (!*space)
+                       return 0;
+               attrs = space + 1;
+       }
+}
+
 static struct ref *get_refs_list(struct transport *transport, int for_push)
 {
        struct helper_data *data = transport->data;
@@ -379,6 +489,12 @@ static struct ref *get_refs_list(struct transport *transport, int for_push)
                        (*tail)->symref = xstrdup(buf.buf + 1);
                else if (buf.buf[0] != '?')
                        get_sha1_hex(buf.buf, (*tail)->old_sha1);
+               if (eon) {
+                       if (has_attribute(eon + 1, "unchanged")) {
+                               (*tail)->status |= REF_STATUS_UPTODATE;
+                               read_ref((*tail)->name, (*tail)->old_sha1);
+                       }
+               }
                tail = &((*tail)->next);
        }
        strbuf_release(&buf);
@@ -399,6 +515,6 @@ int transport_helper_init(struct transport *transport, const char *name)
        transport->get_refs_list = get_refs_list;
        transport->fetch = fetch;
        transport->push_refs = push_refs;
-       transport->disconnect = disconnect_helper;
+       transport->disconnect = release_helper;
        return 0;
 }
index 7362ec09b2cbc6752489286a8280c16d3519f163..3eea836a33a56aaa99eec78bb4850b02888e377b 100644 (file)
@@ -204,7 +204,7 @@ static struct ref *get_refs_via_rsync(struct transport *transport, int for_push)
 }
 
 static int fetch_objs_via_rsync(struct transport *transport,
-                               int nr_objs, const struct ref **to_fetch)
+                               int nr_objs, struct ref **to_fetch)
 {
        struct strbuf buf = STRBUF_INIT;
        struct child_process rsync;
@@ -379,7 +379,7 @@ static struct ref *get_refs_from_bundle(struct transport *transport, int for_pus
 }
 
 static int fetch_refs_from_bundle(struct transport *transport,
-                              int nr_heads, const struct ref **to_fetch)
+                              int nr_heads, struct ref **to_fetch)
 {
        struct bundle_transport_data *data = transport->data;
        return unbundle(&data->header, data->fd);
@@ -457,7 +457,7 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus
 }
 
 static int fetch_refs_via_pack(struct transport *transport,
-                              int nr_heads, const struct ref **to_fetch)
+                              int nr_heads, struct ref **to_fetch)
 {
        struct git_transport_data *data = transport->data;
        char **heads = xmalloc(nr_heads * sizeof(*heads));
@@ -788,8 +788,26 @@ struct transport *transport_get(struct remote *remote, const char *url)
                die("No remote provided to transport_get()");
 
        ret->remote = remote;
+
+       if (!url && remote && remote->url)
+               url = remote->url[0];
        ret->url = url;
 
+       /* maybe it is a foreign URL? */
+       if (url) {
+               const char *p = url;
+
+               while (isalnum(*p))
+                       p++;
+               if (!prefixcmp(p, "::"))
+                       remote->foreign_vcs = xstrndup(url, p - url);
+       }
+
+       if (remote && remote->foreign_vcs) {
+               transport_helper_init(ret, remote->foreign_vcs);
+               return ret;
+       }
+
        if (!prefixcmp(url, "rsync:")) {
                ret->get_refs_list = get_refs_via_rsync;
                ret->fetch = fetch_objs_via_rsync;
@@ -896,16 +914,17 @@ const struct ref *transport_get_remote_refs(struct transport *transport)
        return transport->remote_refs;
 }
 
-int transport_fetch_refs(struct transport *transport, const struct ref *refs)
+int transport_fetch_refs(struct transport *transport, struct ref *refs)
 {
        int rc;
        int nr_heads = 0, nr_alloc = 0, nr_refs = 0;
-       const struct ref **heads = NULL;
-       const struct ref *rm;
+       struct ref **heads = NULL;
+       struct ref *rm;
 
        for (rm = refs; rm; rm = rm->next) {
                nr_refs++;
                if (rm->peer_ref &&
+                   !is_null_sha1(rm->old_sha1) &&
                    !hashcmp(rm->peer_ref->old_sha1, rm->old_sha1))
                        continue;
                ALLOC_GROW(heads, nr_heads + 1, nr_alloc);
index e4e6177e2632b4a764703c11ef0a8033eafea037..9e74406fa203a59920cb24884d7893b45d876a70 100644 (file)
@@ -18,11 +18,48 @@ struct transport {
        int (*set_option)(struct transport *connection, const char *name,
                          const char *value);
 
+       /**
+        * Returns a list of the remote side's refs. In order to allow
+        * the transport to try to share connections, for_push is a
+        * hint as to whether the ultimate operation is a push or a fetch.
+        *
+        * If the transport is able to determine the remote hash for
+        * the ref without a huge amount of effort, it should store it
+        * in the ref's old_sha1 field; otherwise it should be all 0.
+        **/
        struct ref *(*get_refs_list)(struct transport *transport, int for_push);
-       int (*fetch)(struct transport *transport, int refs_nr, const struct ref **refs);
+
+       /**
+        * Fetch the objects for the given refs. Note that this gets
+        * an array, and should ignore the list structure.
+        *
+        * If the transport did not get hashes for refs in
+        * get_refs_list(), it should set the old_sha1 fields in the
+        * provided refs now.
+        **/
+       int (*fetch)(struct transport *transport, int refs_nr, struct ref **refs);
+
+       /**
+        * Push the objects and refs. Send the necessary objects, and
+        * then, for any refs where peer_ref is set and
+        * peer_ref->new_sha1 is different from old_sha1, tell the
+        * remote side to update each ref in the list from old_sha1 to
+        * peer_ref->new_sha1.
+        *
+        * Where possible, set the status for each ref appropriately.
+        *
+        * The transport must modify new_sha1 in the ref to the new
+        * value if the remote accepted the change. Note that this
+        * could be a different value from peer_ref->new_sha1 if the
+        * process involved generating new commits.
+        **/
        int (*push_refs)(struct transport *transport, struct ref *refs, int flags);
        int (*push)(struct transport *connection, int refspec_nr, const char **refspec, int flags);
 
+       /** get_refs_list(), fetch(), and push_refs() can keep
+        * resources (such as a connection) reserved for futher
+        * use. disconnect() releases these resources.
+        **/
        int (*disconnect)(struct transport *connection);
        char *pack_lockfile;
        signed verbose : 3;
@@ -74,7 +111,7 @@ int transport_push(struct transport *connection,
 
 const struct ref *transport_get_remote_refs(struct transport *transport);
 
-int transport_fetch_refs(struct transport *transport, const struct ref *refs);
+int transport_fetch_refs(struct transport *transport, struct ref *refs);
 void transport_unlock_pack(struct transport *transport);
 int transport_disconnect(struct transport *transport);
 char *transport_anonymize_url(const char *url);
index 0459e54d3d89a413330f52ab34662f51924b04ea..7d745b44061955ced9ce4624a498647a1d8d32a5 100644 (file)
@@ -286,7 +286,8 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, stru
        int baselen = strlen(base);
 
        for (;;) {
-               if (DIFF_OPT_TST(opt, QUIET) && DIFF_OPT_TST(opt, HAS_CHANGES))
+               if (DIFF_OPT_TST(opt, QUICK) &&
+                   DIFF_OPT_TST(opt, HAS_CHANGES))
                        break;
                if (opt->nr_paths) {
                        skip_uninteresting(t1, base, baselen, opt);
index 6bfb500eb4116bc74d6088b5b0621e89453fab3d..df151813f9c12a681dcac85608f5ff2262c12879 100644 (file)
@@ -148,66 +148,8 @@ static int do_rev_list(int fd, void *create_full_pack)
        return 0;
 }
 
-static int feed_msg_to_hook(int fd, const char *fmt, ...)
-{
-       int cnt;
-       char buf[1024];
-       va_list params;
-
-       va_start(params, fmt);
-       cnt = vsprintf(buf, fmt, params);
-       va_end(params);
-       return write_in_full(fd, buf, cnt) != cnt;
-}
-
-static int feed_obj_to_hook(const char *label, struct object_array *oa, int i, int fd)
-{
-       return feed_msg_to_hook(fd, "%s %s\n", label,
-                               sha1_to_hex(oa->objects[i].item->sha1));
-}
-
-static int run_post_upload_pack_hook(size_t total, struct timeval *tv)
-{
-       const char *argv[2];
-       struct child_process proc;
-       int err, i;
-
-       argv[0] = "hooks/post-upload-pack";
-       argv[1] = NULL;
-
-       if (access(argv[0], X_OK) < 0)
-               return 0;
-
-       memset(&proc, 0, sizeof(proc));
-       proc.argv = argv;
-       proc.in = -1;
-       proc.stdout_to_stderr = 1;
-       err = start_command(&proc);
-       if (err)
-               return err;
-       for (i = 0; !err && i < want_obj.nr; i++)
-               err |= feed_obj_to_hook("want", &want_obj, i, proc.in);
-       for (i = 0; !err && i < have_obj.nr; i++)
-               err |= feed_obj_to_hook("have", &have_obj, i, proc.in);
-       if (!err)
-               err |= feed_msg_to_hook(proc.in, "time %ld.%06ld\n",
-                                       (long)tv->tv_sec, (long)tv->tv_usec);
-       if (!err)
-               err |= feed_msg_to_hook(proc.in, "size %ld\n", (long)total);
-       if (!err)
-               err |= feed_msg_to_hook(proc.in, "kind %s\n",
-                                       (nr_our_refs == want_obj.nr && !have_obj.nr)
-                                       ? "clone" : "fetch");
-       if (close(proc.in))
-               err = 1;
-       if (finish_command(&proc))
-               err = 1;
-       return err;
-}
-
 static void create_pack_file(void)
 {
-       struct timeval start_tv, tv;
        struct async rev_list;
        struct child_process pack_objects;
        int create_full_pack = (nr_our_refs == want_obj.nr && !have_obj.nr);
@@ -215,12 +157,10 @@ static void create_pack_file(void)
        char abort_msg[] = "aborting due to possible repository "
                "corruption on the remote side.";
        int buffered = -1;
-       ssize_t sz, total_sz;
+       ssize_t sz;
        const char *argv[10];
        int arg = 0;
 
-       gettimeofday(&start_tv, NULL);
-       total_sz = 0;
        if (shallow_nr) {
                rev_list.proc = do_rev_list;
                rev_list.data = 0;
@@ -346,7 +286,7 @@ static void create_pack_file(void)
                        sz = xread(pack_objects.out, cp,
                                  sizeof(data) - outsz);
                        if (0 < sz)
-                               total_sz += sz;
+                               ;
                        else if (sz == 0) {
                                close(pack_objects.out);
                                pack_objects.out = -1;
@@ -383,16 +323,6 @@ static void create_pack_file(void)
        }
        if (use_sideband)
                packet_flush(1);
-
-       gettimeofday(&tv, NULL);
-       tv.tv_sec -= start_tv.tv_sec;
-       if (tv.tv_usec < start_tv.tv_usec) {
-               tv.tv_sec--;
-               tv.tv_usec += 1000000;
-       }
-       tv.tv_usec -= start_tv.tv_usec;
-       if (run_post_upload_pack_hook(total_sz, &tv))
-               warning("post-upload-hook failed");
        return;
 
  fail:
index 38eb24536b34e40af4c43eb1631e08624e8993d6..5d569880163cccec91fdf1e9d50e03a6b5314d58 100644 (file)
@@ -47,28 +47,33 @@ void wt_status_prepare(struct wt_status *s)
 static void wt_status_print_unmerged_header(struct wt_status *s)
 {
        const char *c = color(WT_STATUS_HEADER, s);
+
        color_fprintf_ln(s->fp, c, "# Unmerged paths:");
        if (!advice_status_hints)
                return;
-       if (!s->is_initial)
+       if (s->in_merge)
+               ;
+       else if (!s->is_initial)
                color_fprintf_ln(s->fp, c, "#   (use \"git reset %s <file>...\" to unstage)", s->reference);
        else
                color_fprintf_ln(s->fp, c, "#   (use \"git rm --cached <file>...\" to unstage)");
-       color_fprintf_ln(s->fp, c, "#   (use \"git add <file>...\" to mark resolution)");
+       color_fprintf_ln(s->fp, c, "#   (use \"git add/rm <file>...\" as appropriate to mark resolution)");
        color_fprintf_ln(s->fp, c, "#");
 }
 
 static void wt_status_print_cached_header(struct wt_status *s)
 {
        const char *c = color(WT_STATUS_HEADER, s);
+
        color_fprintf_ln(s->fp, c, "# Changes to be committed:");
        if (!advice_status_hints)
                return;
-       if (!s->is_initial) {
+       if (s->in_merge)
+               ; /* NEEDSWORK: use "git reset --unresolve"??? */
+       else if (!s->is_initial)
                color_fprintf_ln(s->fp, c, "#   (use \"git reset %s <file>...\" to unstage)", s->reference);
-       } else {
+       else
                color_fprintf_ln(s->fp, c, "#   (use \"git rm --cached <file>...\" to unstage)");
-       }
        color_fprintf_ln(s->fp, c, "#");
 }
 
@@ -76,6 +81,7 @@ static void wt_status_print_dirty_header(struct wt_status *s,
                                         int has_deleted)
 {
        const char *c = color(WT_STATUS_HEADER, s);
+
        color_fprintf_ln(s->fp, c, "# Changed but not updated:");
        if (!advice_status_hints)
                return;
@@ -277,6 +283,7 @@ static void wt_status_collect_changes_worktree(struct wt_status *s)
        rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
        rev.diffopt.format_callback = wt_status_collect_changed_cb;
        rev.diffopt.format_callback_data = s;
+       rev.prune_data = s->pathspec;
        run_diff_files(&rev, 0);
 }
 
@@ -293,6 +300,7 @@ static void wt_status_collect_changes_index(struct wt_status *s)
        rev.diffopt.detect_rename = 1;
        rev.diffopt.rename_limit = 200;
        rev.diffopt.break_opt = 0;
+       rev.prune_data = s->pathspec;
        run_diff_index(&rev, 1);
 }
 
@@ -305,6 +313,8 @@ static void wt_status_collect_changes_initial(struct wt_status *s)
                struct wt_status_change_data *d;
                struct cache_entry *ce = active_cache[i];
 
+               if (!ce_path_match(ce, s->pathspec))
+                       continue;
                it = string_list_insert(ce->name, &s->change);
                d = it->util;
                if (!d) {
@@ -338,6 +348,8 @@ static void wt_status_collect_untracked(struct wt_status *s)
                struct dir_entry *ent = dir.entries[i];
                if (!cache_name_is_other(ent->name, ent->len))
                        continue;
+               if (!match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
+                       continue;
                s->workdir_untracked = 1;
                string_list_insert(ent->name, &s->untracked);
        }
@@ -541,10 +553,8 @@ static void wt_status_print_tracking(struct wt_status *s)
 
 void wt_status_print(struct wt_status *s)
 {
-       unsigned char sha1[20];
        const char *branch_color = color(WT_STATUS_HEADER, s);
 
-       s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0;
        if (s->branch) {
                const char *on_what = "On branch ";
                const char *branch_name = s->branch;
@@ -561,8 +571,6 @@ void wt_status_print(struct wt_status *s)
                        wt_status_print_tracking(s);
        }
 
-       wt_status_collect(s);
-
        if (s->is_initial) {
                color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#");
                color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "# Initial commit");
@@ -598,3 +606,107 @@ void wt_status_print(struct wt_status *s)
                        printf("nothing to commit (working directory clean)\n");
        }
 }
+
+static void wt_shortstatus_unmerged(int null_termination, struct string_list_item *it,
+                          struct wt_status *s)
+{
+       struct wt_status_change_data *d = it->util;
+       const char *how = "??";
+
+       switch (d->stagemask) {
+       case 1: how = "DD"; break; /* both deleted */
+       case 2: how = "AU"; break; /* added by us */
+       case 3: how = "UD"; break; /* deleted by them */
+       case 4: how = "UA"; break; /* added by them */
+       case 5: how = "DU"; break; /* deleted by us */
+       case 6: how = "AA"; break; /* both added */
+       case 7: how = "UU"; break; /* both modified */
+       }
+       color_fprintf(s->fp, color(WT_STATUS_UNMERGED, s), "%s", how);
+       if (null_termination) {
+               fprintf(stdout, " %s%c", it->string, 0);
+       } else {
+               struct strbuf onebuf = STRBUF_INIT;
+               const char *one;
+               one = quote_path(it->string, -1, &onebuf, s->prefix);
+               printf(" %s\n", one);
+               strbuf_release(&onebuf);
+       }
+}
+
+static void wt_shortstatus_status(int null_termination, struct string_list_item *it,
+                        struct wt_status *s)
+{
+       struct wt_status_change_data *d = it->util;
+
+       if (d->index_status)
+               color_fprintf(s->fp, color(WT_STATUS_UPDATED, s), "%c", d->index_status);
+       else
+               putchar(' ');
+       if (d->worktree_status)
+               color_fprintf(s->fp, color(WT_STATUS_CHANGED, s), "%c", d->worktree_status);
+       else
+               putchar(' ');
+       putchar(' ');
+       if (null_termination) {
+               fprintf(stdout, "%s%c", it->string, 0);
+               if (d->head_path)
+                       fprintf(stdout, "%s%c", d->head_path, 0);
+       } else {
+               struct strbuf onebuf = STRBUF_INIT;
+               const char *one;
+               if (d->head_path) {
+                       one = quote_path(d->head_path, -1, &onebuf, s->prefix);
+                       printf("%s -> ", one);
+                       strbuf_release(&onebuf);
+               }
+               one = quote_path(it->string, -1, &onebuf, s->prefix);
+               printf("%s\n", one);
+               strbuf_release(&onebuf);
+       }
+}
+
+static void wt_shortstatus_untracked(int null_termination, struct string_list_item *it,
+                           struct wt_status *s)
+{
+       if (null_termination) {
+               fprintf(stdout, "?? %s%c", it->string, 0);
+       } else {
+               struct strbuf onebuf = STRBUF_INIT;
+               const char *one;
+               one = quote_path(it->string, -1, &onebuf, s->prefix);
+               color_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), "??");
+               printf(" %s\n", one);
+               strbuf_release(&onebuf);
+       }
+}
+
+void wt_shortstatus_print(struct wt_status *s, int null_termination)
+{
+       int i;
+       for (i = 0; i < s->change.nr; i++) {
+               struct wt_status_change_data *d;
+               struct string_list_item *it;
+
+               it = &(s->change.items[i]);
+               d = it->util;
+               if (d->stagemask)
+                       wt_shortstatus_unmerged(null_termination, it, s);
+               else
+                       wt_shortstatus_status(null_termination, it, s);
+       }
+       for (i = 0; i < s->untracked.nr; i++) {
+               struct string_list_item *it;
+
+               it = &(s->untracked.items[i]);
+               wt_shortstatus_untracked(null_termination, it, s);
+       }
+}
+
+void wt_porcelain_print(struct wt_status *s, int null_termination)
+{
+       s->use_color = 0;
+       s->relative_paths = 0;
+       s->prefix = NULL;
+       wt_shortstatus_print(s, null_termination);
+}
index a0e75177bec925e95b4257811aae0bdee93c41f1..c60f40a34a89ccdacdc864203d4441cf3e20bcc5 100644 (file)
@@ -31,8 +31,10 @@ struct wt_status {
        int is_initial;
        char *branch;
        const char *reference;
+       const char **pathspec;
        int verbose;
        int amend;
+       int in_merge;
        int nowarn;
        int use_color;
        int relative_paths;
@@ -55,4 +57,7 @@ void wt_status_prepare(struct wt_status *s);
 void wt_status_print(struct wt_status *s);
 void wt_status_collect(struct wt_status *s);
 
+void wt_shortstatus_print(struct wt_status *s, int null_termination);
+void wt_porcelain_print(struct wt_status *s, int null_termination);
+
 #endif /* STATUS_H */